Skip to content

Commit 0319242

Browse files
author
Conor Okus
authored
Merge branch 'main' into cash-app-date-edit
2 parents b8f1d4b + 3c63cc9 commit 0319242

File tree

2 files changed

+131
-9
lines changed

2 files changed

+131
-9
lines changed

docs/_blog/cashapp-enables-lightning-withdrawals-and-deposits-using-ldk.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,22 @@ tags:
88
- Case Studies
99
---
1010

11-
Our team builds the Cash App lightning wallet. We power all lightning bitcoin withdrawals and deposits within Cash App.
11+
Cash App allows users to instantly deposit and withdraw bitcoin over the lightning network. As an engineer on the Cash App Lightning Wallet team, I was very involved in the entire implementation process.
1212

13-
Cash App is the #1 finance app in the App Store – we have extremely high standards for product development and the infrastructure that powers all our cool features. We were looking for a solution that would ensure high reliability, high scalability, and a stellar developer experience. Cash App is the financial hub for millions of people and often the first place an individual acquires bitcoin, so having a slick user experience, and the infrastructure to onboard lots of new users is extremely important. As a publicly traded company, we take security very seriously, so having the ability to customize our wallet implementation to adhere to our security standards was necessary.
13+
Cash App is the #1 Finance App in the app store – we have extremely high standards for product development and the infrastructure that powers all our cool features. We were looking for a solution that would ensure high reliability, high scalability, and a stellar developer experience. Cash App is the financial hub for millions of people and often the first place an individual acquires bitcoin. It’s important to have a slick user experience and robust infrastructure that can onboard many new users. As a publicly traded company we take security seriously and need the ability to customize our wallet implementation in order to adhere to strict standards.
1414

15-
We chose LDK for various reasons over other implementations. It provided us with a lot of customizability and flexibility. The [Java bindings](https://github.com/lightningdevkit/ldk-garbagecollected) let us develop in Kotlin, which is the preferred language we use at Cash for backend services. We also had a great working relationship with the [Spiral](https://spiral.xyz/) folks and so it was great to collaboratively work together on more advanced feature asks (phantom invoices!).
15+
We chose LDK for various reasons over other implementations. It provided us with a lot of customizability and flexibility. The [Java bindings](https://github.com/lightningdevkit/ldk-garbagecollected) let us develop in Kotlin, which is the preferred language for backend services. We also have a great working relationship with the [Spiral](https://spiral.xyz/) folks, making it easy to collaborate on more advanced feature requests such as phantom invoices.
1616

1717
# What we did
18-
LDK allowed us to develop an extremely scalable lightning node infrastructure. We run multiple wallet nodes and have strict requirements on what peers we can connect to and strict parameters around opening channels. We have logic that does advanced probing in the background, to ensure we have an up to date snapshot of the liquidity on the lighting network.
18+
LDK allowed us to develop a scalable lightning node infrastructure. Cash App runs multiple wallet nodes and has strict parameters around opening channels and connecting to peers. We have logic that does advanced probing in the background in order to ensure an up-to-date snapshot of liquidity on the lightning network.
1919

2020
![CashApp architecture](../assets/cash-app-architecture.svg)
2121

22-
We also do probing before every send, so that we can pre-fetch a route and execute the send after a customer has confirmed the send. We have utilized LDK’s [phantom node](https://lightningdevkit.org/blog/introducing-phantom-node-payments/) feature, so we can generate invoices that can be claimed by more than one node. We use MySQL to save our channel state data and node metadata, this allows us to quickly shut down and start up nodes at will on different servers. LDK allows us to run A/B tests on different pathfinding algorithms.
22+
Probing is done before every send, allowing us to pre-fetch a route and execute the send once the customer confirms. LDK’s [phantom node](https://lightningdevkit.org/blog/introducing-phantom-node-payments/) feature makes it possible to generate invoices that can be claimed by more than one node. We use MySQL to save our channel state data + node metadata in order to quickly shut down and start up nodes at will on different servers. Additionally, LDK lets us run AB tests on different pathfinding algorithms.
2323

2424
# Results
2525

26-
The outcome was that we were able to relatively quickly build a lightning wallet to power bitcoin withdrawals and deposits on Cash App taking into account the complicated constraints we had as a publicly traded company with tens of millions of users.
27-
28-
Users can now deposit and withdraw their bitcoin to Cash App over lightning, so essentially instant BTC deposits and withdrawals for free.
26+
LDK made it possible to relatively quickly build an easy-to-use lightning wallet while adhering to the complicated constraints Cash App faces as a publicly traded company with tens of millions of users. Users can now instantly deposit and withdraw bitcoin to Cash App over lightning.
2927

3028
To learn more check out this [presentation](https://www.youtube.com/watch?v=kbhL5RqL8Aw) at btc++ explaining some of the trade-offs the Cash App team had to think about when comparing LDK to other solutions.
3129

docs/key_management.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,143 @@
22

33
Relevant reference: [Rust docs](https://docs.rs/lightning/*/lightning/chain/keysinterface/struct.KeysManager.html)
44

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.
68

79
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.
810

911
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`).
1012

13+
<CodeSwitcher :languages="{rust:'Rust', java:'Java', kotlin:'Kotlin'}">
14+
<template v-slot:rust>
15+
1116
```rust
1217
let mut random_32_bytes = [0; 32];
1318
// Fill in random_32_bytes with secure random data, or, on restart, reload the seed from disk.
1419
let start_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
1520
let keys_interface_impl = lightning::chain::keysinterface::KeysManager::new(&random_32_bytes, start_time.as_secs(), start_time.subsec_nanos());
1621
```
1722

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+
18142
Spending On-Chain Funds
19143
=======================
20144
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

Comments
 (0)