Did you lose funds on the Lightning Network because of a disk crash? Here’s a way you might get them back.

Oliver Gugger
9 min readJul 26, 2019

EDIT: This article is quite old and only applies to situations where you have no data from your crashed node but the seed. If you have any recovery data (for example a channel.backup file or an old copy of the channel.db file), please follow my more recent recovery guide.

EDIT: There is now a way to get another chance with Zombie Channels. Visit https://www.node-recovery.com for more information.

If you’ve been using the Lightning Network at any point prior to this article being written, you probably know that doing so is still considered #reckless and that there’s a real possibility of losing your bitcoins if anything goes wrong (with your hardware for example).
This is due to a protocol design decision that was only mitigated very recently with the introduction of Static Channel Backups (SCB).
Even though SCBs are great, work as advertised and will help many users being safe in the future, there are a lot of people trying out the Lightning Network that don’t read the warnings and aren’t aware SCBs even exist.

I’ve recently been approached by two people independently who have wiped their systems and wanted to reinstall everything, thinking they would be safe by having the 24 word mnemonic only. They did not back up their channel.backup file containing the SCBs and would have lost all bitcoins that they used to open their channels. So I decided to try and help them recover their funds, that’s how this article and the modified version of LND described here were created.

But before we start, here a short disclaimer: I’ve only really tested this once and was able to recover a few million satoshis from one channel. The other person that approached me is still gathering the information that is needed to start the recovery process.

So to not give you false hope, here are the conditions that need to be met for this to even be possible:

  1. You used LND. This method might be ported to other implementations but it currently only works for LND.
  2. The remote node you opened the channel with is still online with the same public key and can be looked up on 1ml.com. This is absolutely crucial. If the other node is not online and/or also suffered from data loss, there is simply no way of recovery, sorry.
  3. You do have the 24 word seed of the LND wallet that you got when setting it up. If you set a seed passphrase you also need to know that.
  4. You know exactly which channel(s) you want to recover funds from. And the channels can be seen on 1ml.com too (therefore, no private channels, unless you know the remote node ID and the channel funding TXID from somewhere).
  5. It hasn’t been a long time since you had the accident. I’m not sure how important this is but I guess the longer you haven’t been re-establishing the channel with the remote node the more likely it is they simply “forgot” the information we need.
  6. It’s an old, legacy channel, not one you opened with lnd v0.8.0 or later (those might be even easier to recover, see my new guide linked at the top).

If you can answer all of this with yes, then there is hope! Then you should at least try this guide/tutorial. This will be quite technical and it might not work in all cases. Maybe I’ll also need to tweak the code a bit more. You can DM me on the Lightning Network Community Slack if you need assistance though please bear in mind that I’m very busy and might not answer right away.

Ok, let’s get to it. The following things you will need in order to do this:

  1. Everything you need to compile LND (git, make, go), follow this tutorial.
  2. A bitcoind or btcd Bitcoin full node to connect LND to. It should also work over Neutrino.
  3. The latest version of my Channel Tools (chantools).

Now that we have everything set up, here is what you have to do:

  1. Clone and build my modified version of LND (based on 0.12.0-beta-rc6).
$ git clone https://github.com/guggero/lnd.git
$ cd lnd
$ git checkout fund-recovery
$ make && make install

All further commands need to be run inside this directory that was just created.

2. Run the following command and have a look at the parameters you have to provide:

$ chantools fakechanbackup --help
If for any reason a node suffers from data loss and there is no
channel.backup for one or more channels, then the funds in the channel would
theoretically be lost forever.
If the remote node is still online and still knows about the channel, there is
hope. We can initiate DLP (Data Loss Protocol) and ask the remote node to
force-close the channel and to provide us with the per_commit_point that is
needed to derive the private key for our part of the force-close transaction
output. But to initiate DLP, we would need to have a channel.backup file.
Fortunately, if we have enough information about the channel, we can create a
faked/skeleton channel.backup file that at least lets us talk to the other node
and ask them to do their part. Then we can later brute-force the private key for
the transaction output of our part of the funds (see rescueclosed command).
chantools fakechanbackup [flags]
chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
--capacity 123456 \
--channelpoint f39310xxxxxxxxxx:1 \
--initiator \
--remote_node_addr 022c260xxxxxxxx@ \
--short_channel_id 566222x300x1 \
--multi_file fake.backup
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
--capacity uint the channel's capacity in satoshis
--channelpoint string funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is displayed on 1ml.com
-h, --help help for fakechanbackup
--initiator whether our node was the initiator (funder) of the channel
--multi_file string the fake channel backup file to create (default "results/fake-2021-01-23-14-41-03.backup")
--remote_node_addr string the remote node connection information in the format pubkey@host:port
--rootkey string BIP32 HD root key of the wallet to use for encrypting the backup; leave empty to prompt for lnd 24 word aezeed
--short_channel_id string the short channel ID in the format <blockheight>x<transactionindex>x<outputindex>
Global Flags:
-r, --regtest Indicates if regtest parameters should be used
-t, --testnet Indicates if testnet parameters should be used

The root key you can get using chantools. Simply run “chantools showrootkey” and enter the seed (and passhprase if one was used).

3. In this step we are going to start LND from the modified binary that you created earlier. But before we do so, we have to make sure we don’t overwrite something accidentally. If you have any previous installation of LND and there is a folder called .lnd in your home directory (or wherever LND stored its data), please move it away to a safe location. The next few commands will create a new wallet.db file in this folder and we don’t want your old one to be overwritten.

In terminal window 1 you start the modified LND and connect it to the Bitcoin full node (please follow any other tutorial you find online to do this, I won’t have time to support you on this, sorry). Your command might look something like this:

./lnd-debug --bitcoin.active --bitcoin.mainnet \
--bitcoin.node=bitcoind --bitcoind.rpchost=xxx:8332 \
--bitcoind.rpcuser=xxx --bitcoind.rpcpass=xxx

This will produce a few lines of output and then wait for you to create or unlock the wallet. You don’t need to do anything more in this window, just let the process run. But once the recovery process is successful, there will be a very important log message visible in this window. You will need this, this is very important!

In terminal window 2, run the following command:

./lncli-debug create --multi_file ./fake.backup

This will create a new wallet file and folder structure. But we want to use the original seed to recover from. So when it asks you if you already have a seed and you have to answer yes! Enter the 24 words (and the passphrase if applicable) to setup the wallet again from the original seed you lost funds on.
Once the command completes, follow the output on terminal window 1. LND will sync the whole chain, look for on-chain balances and finally try to connect to the remote node that we specified in the fake SCB file. This might take a while, even with an SSD it can take a few hours.
Once the Data Loss Protocol has been initiated, the following output should be seen in the console output of terminal window 1 (it will also be save to the default log file so you can also look it up there with an editor or by using tools like grep):

2021-01-23 14:09:44.306 [DBG] LNWL: ChannelPoint(f39310xxxxxx:1): Peer sent commit_point=020df4d7c539a63589dbb0115a49484d70096b2792bc733fc56ea02d2f3bb4a00c

If you see this, great! We don’t need the actual commit point anymore as newer (after 2019) channel types can be swept without it. But seeing the log message means that the remote node has accepted our request to force close.

Now all we can do is wait and hope that the remote force close transaction makes it into the mempool and then the chain soon. But unfortunately confirming the force close transaction is not enough as the funds wil not go to an address that is watched by the wallet by default but to a special, address not in the default derivation path.

To now recover the private key for the address that our part of the channel balance should now be on, we first need to make sure the force close transaction has at least 1 confirmation on-chain. For this, it’s easiest if we have a look at the channel funding transaction (found on 1ml.com, we used this in a previous step) in a block explorer like www.blockstream.info for example. We should now see that this funding transaction has been spent. If that is not the case, then the remote node has not yet force-closed the channel. There might be a way to approach the owner of the node and ask them to force-close manually.
Once we see that the transaction that spends the funding transaction was confirmed, we can continue.

Now that we know the force close transaction was confirmed, we can run the last command. The root key is the same we used in the first command.
The only two flags we normally have to specify is the fee rate, adjust that to the sat/vByte rate that is currently enough to get confirmed (take a look at mempool.space to find a value that works for the current time) and the sweep address, which is the destination address the funds will be sent to. So derive a new address in a wallet of your choice.

$ chantools sweepremoteclosed --help
This command helps users sweep funds that are in
outputs of channels that were force-closed by the remote party. This command
only needs to be used if no channel.backup file is available. By manually
contacting the remote peers and asking them to force-close the channels, the
funds can be swept after the force-close transaction was confirmed.
Supported remote force-closed channel types are:
- STATIC_REMOTE_KEY (a.k.a. tweakless channels)
- ANCHOR (a.k.a. anchor output channels)
chantools sweepremoteclosed [flags]
chantools sweepremoteclosed \
--recoverywindow 300 \
--feerate 20 \
--sweepaddr bc1q..... \
--apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api")
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
--feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30)
-h, --help help for sweepremoteclosed
--publish publish sweep TX to the chain API instead of just printing the TX
--recoverywindow uint32 number of keys to scan per derivation path (default 200)
--rootkey string BIP32 HD root key of the wallet to use for sweeping the wallet; leave empty to prompt for lnd 24 word aezeed
--sweepaddr string address to sweep the funds to
Global Flags:
-r, --regtest Indicates if regtest parameters should be used
-s, --signet Indicates if the public signet parameters should be used
-t, --testnet Indicates if testnet parameters should be used

If we are lucky, we should get a message that says: “Published TX …”. Followed by the transaction ID that sweeps the funds back to your wallet.

Great, you just got your money back!
Feel free to send me a tip ;-) bc1qcpm6nw32ng4qv3htu8lu80ucnaw7qg276fdxqy

If you don’t get a transaction out, there is a whole list of reasons what could have gone wrong. If you need assistance, it’s best to join the LND developer slack (https://lightning.engineering/slack.html) and ask questions there, as I’m too busy to support user requests directly (that’s why I write guides like this to help people without hopefully needing my direct assistance).