Skip to content

Commit f6f82bd

Browse files
author
Conor Okus
committed
Add Spendable Outputs Kotlin example
1 parent a49b9c0 commit f6f82bd

File tree

1 file changed

+104
-56
lines changed

1 file changed

+104
-56
lines changed

docs/key_management.md

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ However, LDK also allows to customize the way key material and entropy are sourc
66

77
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`):
88

9-
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin', swift:'Swift'}">
9+
<CodeSwitcher :languages="{rust:'Rust', kotlin:'Kotlin', swift:'Swift'}">
1010
<template v-slot:rust>
1111

1212
```rust
@@ -18,19 +18,6 @@ let keys_interface_impl = lightning::sign::KeysManager::new(&random_32_bytes, st
1818

1919
</template>
2020

21-
<template v-slot:java>
22-
23-
```java
24-
// Fill in key_seed with secure random data, or, on restart, reload the seed from disk.
25-
byte[] key_seed = new byte[32];
26-
KeysManager keys_manager = KeysManager.of(key_seed,
27-
System.currentTimeMillis() / 1000,
28-
(int) (System.currentTimeMillis() * 1000)
29-
);
30-
```
31-
32-
</template>
33-
3421
<template v-slot:kotlin>
3522

3623
```kotlin
@@ -52,8 +39,8 @@ let seed = [UInt8](repeating: 0, count: 32)
5239
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
5340
let timestampNanos = UInt32(truncating: NSNumber(value: timestampSeconds * 1000 * 1000))
5441
self.myKeysManager = KeysManager(
55-
seed: seed,
56-
startingTimeSecs: timestampSeconds,
42+
seed: seed,
43+
startingTimeSecs: timestampSeconds,
5744
startingTimeNanos: timestampNanos
5845
)
5946
```
@@ -73,7 +60,7 @@ Using a [BDK](https://bitcoindevkit.org/)-based wallet the steps would be as fol
7360
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.
7461
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)).
7562

76-
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin', swift:'Swift'}">
63+
<CodeSwitcher :languages="{rust:'Rust', kotlin:'Kotlin', swift:'Swift'}">
7764
<template v-slot:rust>
7865

7966
```rust
@@ -96,36 +83,6 @@ let keys_manager = KeysManager::new(&ldk_seed, cur.as_secs(), cur.subsec_nanos()
9683

9784
</template>
9885

99-
<template v-slot:java>
100-
101-
```java
102-
// Use BDK to create and build the HD wallet
103-
Mnemonic mnemonic = Mnemonic.Companion.fromString("sock lyrics " +
104-
"village put galaxy " +
105-
"famous pass act ship second diagram pull");
106-
107-
// Other supported networks include mainnet (Bitcoin), Regtest, Signet
108-
DescriptorSecretKey bip32RootKey = new DescriptorSecretKey(Network.TESTNET, mnemonic, null);
109-
110-
DerivationPath ldkDerivationPath = new DerivationPath("m/535h");
111-
DescriptorSecretKey ldkChild = bip32RootKey.derive(ldkDerivationPath);
112-
113-
ByteArrayOutputStream bos = new ByteArrayOutputStream();
114-
ObjectOutputStream oos = new ObjectOutputStream(bos);
115-
oos.writeObject(ldkChild.secretBytes());
116-
byte[] entropy = bos.toByteArray();
117-
118-
// Seed the LDK KeysManager with the private key at m/535h
119-
var startupTime = System.currentTimeMillis();
120-
KeysManager keysManager = KeysManager.of(
121-
entropy,
122-
startupTime / 1000,
123-
(int) (startupTime * 1000)
124-
);
125-
```
126-
127-
</template>
128-
12986
<template v-slot:kotlin>
13087

13188
```kotlin
@@ -164,8 +121,8 @@ let timestampNanos = UInt32(truncating: NSNumber(value: timestampSeconds * 1000
164121

165122
// Seed the LDK KeysManager with the private key at m/535h
166123
let keysManager = KeysManager(
167-
seed: ldkSeed,
168-
startingTimeSecs: timestampSeconds,
124+
seed: ldkSeed,
125+
startingTimeSecs: timestampSeconds,
169126
startingTimeNanos: timestampNanos
170127
)
171128
```
@@ -192,7 +149,7 @@ In order to make the outputs from channel closing spendable by a third-party wal
192149

193150
For example, a wrapper based on BDK's [`Wallet`](https://docs.rs/bdk/*/bdk/wallet/struct.Wallet.html) could look like this:
194151

195-
<CodeSwitcher :languages="{rust:'Rust', swift:'Swift'}">
152+
<CodeSwitcher :languages="{rust:'Rust', kotlin: 'Kotlin', swift:'Swift'}">
196153
<template v-slot:rust>
197154

198155
```rust
@@ -312,6 +269,96 @@ where
312269
// ... snip
313270
}
314271

272+
```
273+
274+
</template>
275+
276+
<template v-slot:kotlin>
277+
278+
```kotlin
279+
class LDKKeysManager(seed: ByteArray, startTimeSecs: Long, startTimeNano: Int, wallet: Wallet) {
280+
var inner: KeysManager
281+
var wallet: Wallet
282+
var signerProvider: LDKSignerProvider
283+
284+
init {
285+
this.inner = KeysManager.of(seed, startTimeSecs, startTimeNano)
286+
this.wallet = wallet
287+
signerProvider = LDKSignerProvider()
288+
signerProvider.ldkkeysManager = this
289+
}
290+
291+
// We drop all occurences of `SpendableOutputDescriptor::StaticOutput` (since they will be
292+
// spendable by the BDK wallet) and forward any other descriptors to
293+
// `KeysManager::spend_spendable_outputs`.
294+
//
295+
// Note you should set `locktime` to the current block height to mitigate fee sniping.
296+
// See https://bitcoinops.org/en/topics/fee-sniping/ for more information.
297+
fun spend_spendable_outputs(
298+
descriptors: Array<SpendableOutputDescriptor>,
299+
outputs: Array<TxOut>,
300+
changeDestinationScript: ByteArray,
301+
feerateSatPer1000Weight: Int,
302+
locktime: Option_u32Z
303+
): Result_TransactionNoneZ {
304+
val onlyNonStatic: Array<SpendableOutputDescriptor> = descriptors.filter { it !is SpendableOutputDescriptor.StaticOutput }.toTypedArray()
305+
306+
return inner.spend_spendable_outputs(
307+
onlyNonStatic,
308+
outputs,
309+
changeDestinationScript,
310+
feerateSatPer1000Weight,
311+
locktime,
312+
)
313+
}
314+
}
315+
316+
class LDKSignerProvider : SignerProvider.SignerProviderInterface {
317+
var ldkkeysManager: LDKKeysManager? = null
318+
319+
override fun generate_channel_keys_id(p0: Boolean, p1: Long, p2: UInt128?): ByteArray {
320+
return ldkkeysManager!!.inner.as_SignerProvider().generate_channel_keys_id(p0, p1, p2)
321+
}
322+
323+
override fun derive_channel_signer(p0: Long, p1: ByteArray?): WriteableEcdsaChannelSigner {
324+
return ldkkeysManager!!.inner.as_SignerProvider().derive_channel_signer(p0, p1)
325+
}
326+
327+
override fun read_chan_signer(p0: ByteArray?): Result_WriteableEcdsaChannelSignerDecodeErrorZ {
328+
return ldkkeysManager!!.inner.as_SignerProvider().read_chan_signer(p0)
329+
}
330+
331+
// We return the destination and shutdown scripts derived by the BDK wallet.
332+
@OptIn(ExperimentalUnsignedTypes::class)
333+
override fun get_destination_script(): Result_CVec_u8ZNoneZ {
334+
val address = ldkkeysManager!!.wallet.getAddress(AddressIndex.New)
335+
return Result_CVec_u8ZNoneZ.ok(address.address.scriptPubkey().toBytes().toUByteArray().toByteArray())
336+
}
337+
338+
// Only applies to cooperative close transactions.
339+
override fun get_shutdown_scriptpubkey(): Result_ShutdownScriptNoneZ {
340+
val address = ldkkeysManager!!.wallet.getAddress(AddressIndex.New).address
341+
342+
return when (val payload: Payload = address.payload()) {
343+
is Payload.WitnessProgram -> {
344+
val ver = when (payload.version.name) {
345+
in "V0".."V16" -> payload.version.name.substring(1).toIntOrNull() ?: 0
346+
else -> 0 // Default to 0 if it doesn't match any "V0" to "V16"
347+
}
348+
349+
val result = ShutdownScript.new_witness_program(
350+
WitnessVersion(ver.toByte()),
351+
payload.program.toUByteArray().toByteArray()
352+
)
353+
Result_ShutdownScriptNoneZ.ok((result as Result_ShutdownScriptInvalidShutdownScriptZ.Result_ShutdownScriptInvalidShutdownScriptZ_OK).res)
354+
}
355+
else -> {
356+
Result_ShutdownScriptNoneZ.err()
357+
}
358+
}
359+
}
360+
}
361+
315362
```
316363

317364
</template>
@@ -323,7 +370,7 @@ class MyKeysManager {
323370
let inner: KeysManager
324371
let wallet: BitcoinDevKit.Wallet
325372
let signerProvider: MySignerProvider
326-
373+
327374
init(seed: [UInt8], startingTimeSecs: UInt64, startingTimeNanos: UInt32, wallet: BitcoinDevKit.Wallet) {
328375
self.inner = KeysManager(seed: seed, startingTimeSecs: startingTimeSecs, startingTimeNanos: startingTimeNanos)
329376
self.wallet = wallet
@@ -359,7 +406,7 @@ class MyKeysManager {
359406

360407
class MySignerProvider: SignerProvider {
361408
weak var myKeysManager: MyKeysManager?
362-
409+
363410
// We return the destination and shutdown scripts derived by the BDK wallet.
364411
override func getDestinationScript() -> Bindings.Result_ScriptNoneZ {
365412
do {
@@ -369,7 +416,7 @@ class MySignerProvider: SignerProvider {
369416
return .initWithErr()
370417
}
371418
}
372-
419+
373420
override func getShutdownScriptpubkey() -> Bindings.Result_ShutdownScriptNoneZ {
374421
do {
375422
let address = try myKeysManager!.wallet.getAddress(addressIndex: .new).address
@@ -422,27 +469,28 @@ class MySignerProvider: SignerProvider {
422469
return .initWithErr()
423470
}
424471
}
425-
472+
426473
// ... and redirect all other trait method implementations to the `inner` `KeysManager`.
427474
override func deriveChannelSigner(channelValueSatoshis: UInt64, channelKeysId: [UInt8]) -> Bindings.WriteableEcdsaChannelSigner {
428475
return myKeysManager!.inner.asSignerProvider().deriveChannelSigner(
429476
channelValueSatoshis: channelValueSatoshis,
430477
channelKeysId: channelKeysId
431478
)
432479
}
433-
480+
434481
override func generateChannelKeysId(inbound: Bool, channelValueSatoshis: UInt64, userChannelId: [UInt8]) -> [UInt8] {
435482
return myKeysManager!.inner.asSignerProvider().generateChannelKeysId(
436483
inbound: inbound,
437484
channelValueSatoshis: channelValueSatoshis,
438485
userChannelId: userChannelId
439486
)
440487
}
441-
488+
442489
override func readChanSigner(reader: [UInt8]) -> Bindings.Result_WriteableEcdsaChannelSignerDecodeErrorZ {
443490
return myKeysManager!.inner.asSignerProvider().readChanSigner(reader: reader)
444491
}
445492
}
446493
```
494+
447495
</template>
448496
</CodeSwitcher>

0 commit comments

Comments
 (0)