Skip to content

Commit f4e3d7f

Browse files
Added Swift example for Key Management (#219)
* Added Swift example for Key Management * Fixed fall back, indentation and comment * Fixed Comment
1 parent a2d8b2b commit f4e3d7f

File tree

1 file changed

+177
-6
lines changed

1 file changed

+177
-6
lines changed

docs/key_management.md

Lines changed: 177 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ 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'}">
9+
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin', swift:'Swift'}">
1010
<template v-slot:rust>
1111

1212
```rust
13-
let mut random_32_bytes = [0; 32];
1413
// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
14+
let mut random_32_bytes = [0; 32];
1515
let start_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
1616
let keys_interface_impl = lightning::chain::keysinterface::KeysManager::new(&random_32_bytes, start_time.as_secs(), start_time.subsec_nanos());
1717
```
@@ -21,8 +21,8 @@ let keys_interface_impl = lightning::chain::keysinterface::KeysManager::new(&ran
2121
<template v-slot:java>
2222

2323
```java
24+
// Fill in key_seed with secure random data, or, on restart, reload the seed from disk.
2425
byte[] key_seed = new byte[32];
25-
// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
2626
KeysManager keys_manager = KeysManager.of(key_seed,
2727
System.currentTimeMillis() / 1000,
2828
(int) (System.currentTimeMillis() * 1000)
@@ -34,15 +34,32 @@ KeysManager keys_manager = KeysManager.of(key_seed,
3434
<template v-slot:kotlin>
3535

3636
```kotlin
37+
// Fill in key_seed with secure random data, or, on restart, reload the seed from disk.
3738
val key_seed = ByteArray(32)
38-
// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
3939
val keys_manager = KeysManager.of(
4040
key_seed,
4141
System.currentTimeMillis() / 1000, (System.currentTimeMillis() * 1000).toInt()
4242
)
4343
```
4444

4545
</template>
46+
47+
<template v-slot:swift>
48+
49+
```swift
50+
// Fill in seed with secure random data, or, on restart, reload the seed from disk.
51+
let seed = [UInt8](repeating: 0, count: 32)
52+
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
53+
let timestampNanos = UInt32(truncating: NSNumber(value: timestampSeconds * 1000 * 1000))
54+
self.myKeysManager = KeysManager(
55+
seed: seed,
56+
startingTimeSecs: timestampSeconds,
57+
startingTimeNanos: timestampNanos
58+
)
59+
```
60+
61+
</template>
62+
4663
</CodeSwitcher>
4764

4865
# Creating a Unified Wallet
@@ -56,7 +73,7 @@ Using a [BDK](https://bitcoindevkit.org/)-based wallet the steps would be as fol
5673
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.
5774
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)).
5875

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

6279
```rust
@@ -127,6 +144,30 @@ val keysManager = KeysManager.of(
127144
System.currentTimeMillis() / 1000,
128145
(System.currentTimeMillis() * 1000).toInt()
129146
);
147+
```
148+
149+
</template>
150+
151+
<template v-slot:swift>
152+
153+
```swift
154+
// Use BDK to create and build the HD wallet
155+
let mnemonic = try Mnemonic.fromString(mnemonic: "sock lyrics village put galaxy famous pass act ship second diagram pull")
156+
// Other supported networks include mainnet (Bitcoin), Regtest, Signet
157+
let bip32RootKey = DescriptorSecretKey(network: .testnet, mnemonic: mnemonic, password: nil)
158+
let ldkDerivationPath = try DerivationPath(path: "m/535h")
159+
let ldkChild = try bip32RootKey.derive(path: ldkDerivationPath)
160+
let ldkSeed = ldkChild.secretBytes()
161+
162+
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
163+
let timestampNanos = UInt32(truncating: NSNumber(value: timestampSeconds * 1000 * 1000))
164+
165+
// Seed the LDK KeysManager with the private key at m/535h
166+
let keysManager = KeysManager(
167+
seed: ldkSeed,
168+
startingTimeSecs: timestampSeconds,
169+
startingTimeNanos: timestampNanos
170+
)
130171
```
131172

132173
</template>
@@ -151,7 +192,7 @@ In order to make the outputs from channel closing spendable by a third-party wal
151192

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

154-
<CodeSwitcher :languages="{rust:'Rust'}">
195+
<CodeSwitcher :languages="{rust:'Rust', swift:'Swift'}">
155196
<template v-slot:rust>
156197

157198
```rust
@@ -274,4 +315,134 @@ where
274315
```
275316

276317
</template>
318+
319+
<template v-slot:swift>
320+
321+
```swift
322+
class MyKeysManager {
323+
let inner: KeysManager
324+
let wallet: BitcoinDevKit.Wallet
325+
let signerProvider: MySignerProvider
326+
327+
init(seed: [UInt8], startingTimeSecs: UInt64, startingTimeNanos: UInt32, wallet: BitcoinDevKit.Wallet) {
328+
self.inner = KeysManager(seed: seed, startingTimeSecs: startingTimeSecs, startingTimeNanos: startingTimeNanos)
329+
self.wallet = wallet
330+
signerProvider = MySignerProvider()
331+
signerProvider.myKeysManager = self
332+
}
333+
334+
// We drop all occurences of `SpendableOutputDescriptor::StaticOutput` (since they will be
335+
// spendable by the BDK wallet) and forward any other descriptors to
336+
// `KeysManager::spend_spendable_outputs`.
337+
//
338+
// Note you should set `locktime` to the current block height to mitigate fee sniping.
339+
// See https://bitcoinops.org/en/topics/fee-sniping/ for more information.
340+
func spendSpendableOutputs(descriptors: [SpendableOutputDescriptor], outputs: [Bindings.TxOut],
341+
changeDestinationScript: [UInt8], feerateSatPer1000Weight: UInt32,
342+
locktime: UInt32?) -> Result_TransactionNoneZ {
343+
let onlyNonStatic: [SpendableOutputDescriptor] = descriptors.filter { desc in
344+
if desc.getValueType() == .StaticOutput {
345+
return false
346+
}
347+
return true
348+
}
349+
let res = self.inner.spendSpendableOutputs(
350+
descriptors: onlyNonStatic,
351+
outputs: outputs,
352+
changeDestinationScript: changeDestinationScript,
353+
feerateSatPer1000Weight: feerateSatPer1000Weight,
354+
locktime: locktime
355+
)
356+
return res
357+
}
358+
}
359+
360+
class MySignerProvider: SignerProvider {
361+
weak var myKeysManager: MyKeysManager?
362+
363+
// We return the destination and shutdown scripts derived by the BDK wallet.
364+
override func getDestinationScript() -> Bindings.Result_ScriptNoneZ {
365+
do {
366+
let address = try myKeysManager!.wallet.getAddress(addressIndex: .new)
367+
return Bindings.Result_ScriptNoneZ.initWithOk(o: address.address.scriptPubkey().toBytes())
368+
} catch {
369+
return .initWithErr()
370+
}
371+
}
372+
373+
override func getShutdownScriptpubkey() -> Bindings.Result_ShutdownScriptNoneZ {
374+
do {
375+
let address = try myKeysManager!.wallet.getAddress(addressIndex: .new).address
376+
let payload = address.payload()
377+
if case let .witnessProgram(`version`, `program`) = payload {
378+
let ver: UInt8
379+
switch version {
380+
case .v0:
381+
ver = 0
382+
case .v1:
383+
ver = 1
384+
case .v2:
385+
ver = 2
386+
case .v3:
387+
ver = 3
388+
case .v4:
389+
ver = 4
390+
case .v5:
391+
ver = 5
392+
case .v6:
393+
ver = 6
394+
case .v7:
395+
ver = 7
396+
case .v8:
397+
ver = 8
398+
case .v9:
399+
ver = 9
400+
case .v10:
401+
ver = 10
402+
case .v11:
403+
ver = 11
404+
case .v12:
405+
ver = 12
406+
case .v13:
407+
ver = 13
408+
case .v14:
409+
ver = 14
410+
case .v15:
411+
ver = 15
412+
case .v16:
413+
ver = 16
414+
}
415+
let res = ShutdownScript.newWitnessProgram(version: ver, program: program)
416+
if res.isOk() {
417+
return Bindings.Result_ShutdownScriptNoneZ.initWithOk(o: res.getValue()!)
418+
}
419+
}
420+
return .initWithErr()
421+
} catch {
422+
return .initWithErr()
423+
}
424+
}
425+
426+
// ... and redirect all other trait method implementations to the `inner` `KeysManager`.
427+
override func deriveChannelSigner(channelValueSatoshis: UInt64, channelKeysId: [UInt8]) -> Bindings.WriteableEcdsaChannelSigner {
428+
return myKeysManager!.inner.asSignerProvider().deriveChannelSigner(
429+
channelValueSatoshis: channelValueSatoshis,
430+
channelKeysId: channelKeysId
431+
)
432+
}
433+
434+
override func generateChannelKeysId(inbound: Bool, channelValueSatoshis: UInt64, userChannelId: [UInt8]) -> [UInt8] {
435+
return myKeysManager!.inner.asSignerProvider().generateChannelKeysId(
436+
inbound: inbound,
437+
channelValueSatoshis: channelValueSatoshis,
438+
userChannelId: userChannelId
439+
)
440+
}
441+
442+
override func readChanSigner(reader: [UInt8]) -> Bindings.Result_WriteableEcdsaChannelSignerDecodeErrorZ {
443+
return myKeysManager!.inner.asSignerProvider().readChanSigner(reader: reader)
444+
}
445+
}
446+
```
447+
</template>
277448
</CodeSwitcher>

0 commit comments

Comments
 (0)