This tool provides helper functions that can be used to rescue funds locked in
lnd
channels in case lnd
itself cannot run properly anymore.
WARNING: This tool was specifically built for a certain rescue operation and might not be well-suited for your use case. Or not all edge cases for your needs are coded properly. Please look at the code to understand what it does before you use it for anything serious.
WARNING 2: This tool will query public block explorer APIs for some
commands, your privacy might not be preserved. Use at your own risk or supply
a private API URL with --apiurl
.
The easiest way to install chantools
is to download a pre-built binary for
your operating system and
architecture.
Example (make sure you always use the latest version!):
$ cd /tmp
$ wget -O chantools.tar.gz https://github.com/lightninglabs/chantools/releases/download/v0.13.4/chantools-linux-amd64-v0.13.4.tar.gz
$ tar -zxvf chantools.tar.gz
$ sudo mv chantools-*/chantools /usr/local/bin/
If there isn't a pre-built binary for your operating system or architecture
available or you want to build chantools
from source for another reason, you
need to make sure you have go 1.22.3
(or later) and make
installed and can
then run the following commands:
git clone https://github.com/lightninglabs/chantools.git
cd chantools
make install
This list contains a list of scenarios that users seem to run into sometimes.
Before you start running any chantools
command, you MUST read the
"What should I NEVER do?" section below!
Scenarios:
My node/disk/database crashed and I only have the seed and channel.backup
file.
This is the "normal" recovery scenario for which you don't need chantools
.
Just follow the lnd
recovery guide.
All channels will be closed to recover funds, so you should still try to avoid
This scenario. You only need chantools
if you had zombie
channels or a channel that did not confirm in time (see
below).
My node/disk/database crashed and I only have the seed.
This is very bad and recovery will take manual steps and might not be
successful for private channels. If you do not have any data left from your
node, you need to follow the chantools fakechanbackup
command
help text. If you do have an old version of
your channel.db
file, DO NOT UNDER ANY CIRCUMSTANCES start your node with
it. Instead, try to extract a channel.backup
from it using the chantools chanbackup
command. If that is successful,
follow the steps in the lnd
recovery guide.
This will not cover new channels opened after the backup of the channel.db
file was created. You might still need to create the fake channel backup.
I suspect my channel.db file to be corrupt.
This can happen due to unclean shutdowns or power outages. Try running
chantools compactdb
. If there are NO ERRORS
during the execution of that command, things should be back to normal, and you
can continue running your node. If you get errors, you should probably follow
the recovery scenario described below to avoid
future issues. This will close all channels, however.
I don't have a channel.backup
file but all my peers force closed my
channels, why don't I see the funds with just my seed?
When a channel is force closed by the remote party, the funds don't
automatically go to a normal on-chain address. You need to sweep those funds
using the chantools sweepremoteclosed
command.
My channel peer is online, but they don't force close a channel when using
a channel.backup
file.
This can have many reasons. Often it means the channels is a legacy channel
type (not an anchor output channel) and the force close transaction the peer
has doesn't have enough fees to make it into the mempool. In that case waiting
for an empty mempool might be the only option.
Another reason might be that the peer is a CLN node with a specific version
that doesn't react to force close requests normally. You can use the
chantools triggerforceclose
command in
that case (should work with CLN peers of a certain version that don't respond
to normal force close requests).
lnd
discussions) or use
Google.
Create a backup of all your files in the lnd
data directory (just in case,
but never start a node from a file based backup)
before running any command. Also read the lnd
Operational Safety
Guidelines.lncli abandonchannel
on a channel that was confirmed on chain. Even if you
have an SCB (Static Channel Backup, unfortunately poorly named) file
(channel.backup
) or export from lncli exportchanbackup
. Those files DO NOT
contain enough information to close a channel if your peer does not have the
channel data either (which might happen if the channel took longer than 2
weeks to confirm). If the channel confirmed on chain, you need to force close
it from your node if it does not operate normally. Running abandonchannel
deletes the information needed to be able to force close.channel.backup
file
or the output of lncli exportchanbackup
is not optimal as it implies the
channels can be fully restored or brought back to an operational state. But
the content of those files are for absolute emergencies only. Channels are
always closed when using such a file (by asking the remote peer to issue their
latest force close transaction they have). So chain fees occur. And there are
some edge cases where funds are not covered by those files, for example when
a channel funding transaction is not confirmed in time. Or for channels where
the peer is no longer online. So deleting your lnd
data directory should
never ever be something to be done lightly (see Umbrel above).The following flow chart shows the main recovery scenario this tool was built
for. This scenario assumes that you do have access to the crashed node's seed,
channel.backup
file and some state of a channel.db
file (perhaps from a
file based backup or the recovered file from the crashed node).
Following this guide will help you get your channel funds back! The channels themselves can't be restored to work normally unless step 1 is successful ( compacting the DB).
Explanation:
Node crashed: For some reason your lnd
node crashed and isn't starting
anymore. If you get errors similar to
this,
this or
this, it is possible
that a simple compaction (a full copy in safe mode) can solve your problem.
See chantools compactdb
.
If that doesn't work and you need to continue the recovery, make sure you can
at least extract the channel.backup
file and if somehow possible any
version
of the channel.db
from the node.
Whatever you do, do never, ever replace your channel.db
file with an
old
version (from a file based backup) and start your node that way.
Read this explanation why that can lead to loss of
funds.
Rescue on-chain balance: To start the recovery process, we are going to
re-create the node from scratch. To make sure we don't overwrite any old data
in the process, make sure the old data directory of your node (usually .lnd
in the user's home directory) is safely moved away (or the whole folder
renamed) before continuing.
To start the on-chain recovery, follow the sub step "Starting On-Chain
Recovery" of this guide.
Don't follow the whole guide, only this single chapter!
This step is completed once the lncli getinfo
command shows both
"synced_to_chain": true
and "synced_to_graph": true
which can take
several
hours depending on the speed of your hardware. Do not be alarmed that the
lncli getinfo
command shows 0 channels. This is normal as we haven't
started
the off-chain recovery yet.
Recover channels using SCB: Now that the node is fully synced, we can try
to recover the channels using the Static Channel Backups (SCB).
For this, you need a file called channel.backup
. Simply run the command
lncli restorechanbackup --multi_file <path-to-your-channel.backup>
. This
will take a while!. The command itself can take several minutes to
complete,
depending on the number of channels. The recovery can easily take a day or
two as a lot of chain rescanning needs to happen. It is recommended to wait
at
least one full day. You can watch the progress with
the lncli pendingchannels
command. If the list is empty, congratulations, you've recovered all
channels!
If the list stays un-changed for several hours, it means not all channels
could be restored using this method.
One explanation can be found here.
Install chantools: To try to recover the remaining channels, we are going
to use chantools
.
Simply follow the installation instructions.
The recovery can only be continued if you have access to some version of the
crashed node's channel.db
. This could be the latest state as recovered from
the crashed file system, or a version from a regular file based backup. If
you
do not have any version of a channel DB, chantools
won't be able to help
with the recovery. See step 11 for some possible manual steps.
Create copy of channel DB: To make sure we can read the channel DB, we
are going to create a copy in safe mode (called compaction). Simply run
chantools compactdb --sourcedb <recovered-channel.db> --destdb ./results/compacted.db
We are going to assume that the compacted copy of the channel DB is located
in
./results/compacted.db
in the following commands.
chantools summary: First, chantools
needs to find out the state of each
channel on chain. For this, a blockchain API (by
default node-recovery.com)
is queried. The result will be written to a file called
./results/summary-yyyy-mm-dd.json
. This result file will be needed for the
next command.
chantools --fromchanneldb ./results/compacted.db summary
chantools rescueclosed: It is possible that by now the remote peers have
force-closed some of the remaining channels. What we now do is try to find
the
private keys to sweep our balance of those channels. For this we need a
shared
secret which is called the commit_point
and is changed whenever a channel
is
updated. We do have the latest known version of this point in the channel DB.
The following command tries to find all private keys for channels that have
been closed by the other party. The command needs to know what channels it is
operating on, so we have to supply the summary-yyy-mm-dd.json
created by
the
previous command:
chantools --fromsummary ./results/<summary-file-created-in-last-step>.json rescueclosed --channeldb ./results/compacted.db
This will create a new file called ./results/rescueclosed-yyyy-mm-dd.json
which will contain any found private keys and will also be needed for the
next
command. Use bitcoind
or Electrum Wallet to sweep all of the private keys.
chantools forceclose: This command will now close all channels that
chantools
thinks are still open. This is achieved by publishing the latest
known channel state of the channel.db
file.
Please read the full warning text of the
forceclose
command below as this command can
put
your funds at risk if the state in the channel DB is not the most recent
one. This command should only be executed for channels where the remote peer
is not online anymore.
chantools --fromsummary ./results/<rescueclosed-file-created-in-last-step>.json forceclose --channeldb ./results/compacted.db --publish
This will create a new file called ./results/forceclose-yyyy-mm-dd.json
which will be needed for the next command.
If you get the
error non-mandatory-script-verify-flag (Signature must be zero for failed CHECK(MULTI)SIG operation)
, you might be affected by an old bug
of lnd
that was fixed in the meantime. But it means the signature in the
force-close transaction is invalid and needs to be fixed. There is a guide
on how to do exactly that here.
Wait for timelocks: The previous command closed the remaining open
channels by publishing your node's state of the channel. By design of the
Lightning Network, you now have to wait until the channel funds belonging to
you are not time locked any longer. Depending on the size of the channel, you
have to wait for somewhere between 144 and 2000 confirmations of the
force-close transactions. Only continue with the next step after the channel
with the highest csv_delay
has reached that many confirmations of its
closing transaction. You can check this by looking up each force closed
channel transaction on a block explorer (like
blockstream.info for example). Open the result
JSON file of the last command (./results/forceclose-yyyy-mm-dd.json
) and
look up every TXID in "force_close" -> "txid"
on the explorer. If the
number
of confirmations is equal to or greater to the value shown in
"force_close" -> "csv_delay"
for each of the channels, you can proceed.
chantools sweeptimelock: Once all force-close transactions have reached
the number of transactions as the csv_timeout
in the JSON demands, these
time locked funds can now be swept. Use the following command to sweep all
the
channel funds to an address of your wallet:
chantools --fromsummary ./results/<forceclose-file-created-in-last-step>.json sweeptimelock --publish --sweepaddr <bech32-address-from-your-wallet>
Manual intervention necessary: You got to this step because you either
don't have a channel.db
file or because chantools
couldn't rescue all
your
node's channels. There are a few things you can try manually that have some
chance of working:
lncli connect <node-pubkey>@<updated-ip-address>:<port>
in the recovered
lnd
node from step 3 and wait a few hours to see if the channel is now
being force closed by the remote node.option_static_remote_key
, (lnd v0.8.0
and later), the funds
can
be swept by your node.Use Zombie Channel Recovery Matcher: As a final, last resort, you can
go to node-recovery.com and register your
node's ID for being matched up against other nodes with the same problem.
Once you were contacted with a match, follow the instructions on the
Zombie Channel Recovery Guide page.
If you know the peer of a zombie channel and have a way to contact them, you
can also skip the registration/matching process and create your own match
file.
All commands that require the seed (and, if set, the seed's passphrase) offer three distinct possibilities to specify it:
lnd
's --noseedbackup
flag and extracted the xprv
from the wallet
database with the walletinfo
command. Those users can specify the master
root key by passing the --rootkey
command line flag to each command that
requires the seed.chantools
by removing the need to type into the terminal. There are three
environment variables that can be set to skip entering values through the
terminal:
AEZEED_MNEMONIC
: Specifies the 24 word lnd
aezeed.AEZEED_PASSPHRASE
: Specifies the passphrase for the aezeed. If no
passphrase was used during the creation of the seed, the special value
AEZEED_PASSPHRASE="-"
needs to be passed to indicate no passphrase
should be used or read from the terminal.WALLET_PASSWORD
: Specifies the encryption password that is needed to
access a wallet.db
file. This is currently only used by the walletinfo
command.Example using environment variables:
# We add a space in front of each command to tell bash we don't want this
# command stored in the history.
$ export AEZEED_MNEMONIC="abandon able ... ... ..."
# We didn't set a passphrase for this example seed, we need to indicate this by
# passing in a single dash character.
$ export AEZEED_PASSPHRASE="-"
$ chantools showrootkey
2020-10-29 20:22:42.329 [INF] CHAN: chantools version v0.12.0 commit v0.12.0
Your BIP32 HD root key is: xprv9s21ZrQH1...
Some commands require the seed. But your seed will never leave your computer.
Most commands don't require an internet connection: you can and should run them on a computer with a firewall that blocks outgoing connections.
This tool provides helper functions that can be used rescue
funds locked in lnd channels in case lnd itself cannot run properly anymore.
Complete documentation is available at
https://github.com/lightninglabs/chantools/.
Usage:
chantools [command]
Available Commands:
chanbackup Create a channel.backup file from a channel database
closepoolaccount Tries to close a Pool account that has expired
createwallet Create a new lnd compatible wallet.db file from an existing seed or by generating a new one
compactdb Create a copy of a channel.db file in safe/read-only mode
deletepayments Remove all (failed) payments from a channel DB
derivekey Derive a key with a specific derivation path
doublespendinputs Replace a transaction by double spending its input
dropchannelgraph Remove all graph related data from a channel DB
dropgraphzombies Remove all channels identified as zombies from the graph to force a re-sync of the graph
dumpbackup Dump the content of a channel.backup file
dumpchannels Dump all channel information from an lnd channel database
fakechanbackup Fake a channel backup file to attempt fund recovery
filterbackup Filter an lnd channel.backup file and remove certain channels
fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key)
forceclose Force-close the last state that is in the channel.db provided
genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind
migratedb Apply all recent lnd channel database migrations
pullanchor Attempt to CPFP an anchor output of a channel
recoverloopin Recover a loop in swap that the loop daemon is not able to sweep
removechannel Remove a single channel from the given channel DB
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels
rescuefunding Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the initiator of the channel needs to run
rescuetweakedkey Attempt to rescue funds locked in an address with a key that was affected by a specific bug in lnd
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed
signmessage Sign a message with the node's private key.
signrescuefunding Rescue funds locked in a funding multisig output that never resulted in a proper channel; this is the command the remote node (the non-initiator) of the channel needs to run
signpsbt Sign a Partially Signed Bitcoin Transaction (PSBT)
summary Compile a summary about the current state of channels
sweeptimelock Sweep the force-closed state after the time lock has expired
sweeptimelockmanual Sweep the force-closed state of a single channel manually if only a channel backup file is available
sweepremoteclosed Go through all the addresses that could have funds of channels that were force-closed by the remote party. A public block explorer is queried for each address and if any balance is found, all funds are swept to a given address
triggerforceclose Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given prefix
walletinfo Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
zombierecovery Try rescuing funds stuck in channels with zombie nodes
help Help about any command
Flags:
-h, --help help for chantools
-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
-v, --version version for chantools
Use "chantools [command] --help" for more information about a command.
Detailed documentation for each sub command is available in the docs folder.
The following table provides quick access to each command's documentation. Legend:
Command | Use when |
---|---|
chanbackup | :pencil: Extract a channel.backup file from a channel.db file |
closepoolaccount | :pencil: Manually close an expired Lightning Pool account |
compactdb | Run database compaction manually to reclaim space |
createwallet | :pencil: Create a new lnd compatible wallet.db file from an existing seed or by generating a new one |
deletepayments | Remove ALL payments from a channel.db file to reduce size |
derivekey | :pencil: Derive a single private/public key from lnd 's seed, use to test seed |
doublespendinputs | :pencil: Tries to double spend the given inputs by deriving the private for the address and sweeping the funds to the given address |
dropchannelgraph | (:warning:) Completely drop the channel graph from a channel.db to force re-sync |
dropgraphzombies | Drop all zombie channels from a channel.db to force a graph re-sync |
dumpbackup | :pencil: Show the content of a channel.backup file as text |
dumpchannels | Show the content of a channel.db file as text |
fakechanbackup | :pencil: Create a fake channel.backup file from public information |
filterbackup | :pencil: Remove a channel from a channel.backup file |
fixoldbackup | :pencil: (:pushpin:) Fixes an issue with old channel.backup files |
forceclose | :pencil: (:skull: :warning:) Publish an old channel state from a channel.db file |
genimportscript | :pencil: Create a script/text file that can be used to import lnd keys into other software |
migratedb | Upgrade the channel.db file to the latest version |
pullanchor | :pencil: Attempt to CPFP an anchor output of a channel |
recoverloopin | :pencil: Recover funds from a failed Lightning Loop inbound swap |
removechannel | (:skull: :warning:) Remove a single channel from a channel.db file |
rescueclosed | :pencil: (:pushpin:) Rescue funds in a legacy (pre STATIC_REMOTE_KEY ) channel output |
rescuefunding | :pencil: (:pushpin:) Rescue funds from a funding transaction. Deprecated, use zombierecovery instead |
showrootkey | :pencil: Display the master root key (xprv ) from your seed (DO NOT SHARE WITH ANYONE) |
signmessage | :pencil: Sign a message with the nodes identity pubkey. |
signpsbt | :pencil: Sign a Partially Signed Bitcoin Transaction (PSBT) |
signrescuefunding | :pencil: (:pushpin:) Sign to funds from a funding transaction. Deprecated, use zombierecovery instead |
summary | Create a summary of channel funds from a channel.db file |
sweepremoteclosed | :pencil: Find channel funds from remotely force closed channels and sweep them |
sweeptimelock | :pencil: Sweep funds in locally force closed channels once time lock has expired (requires channel.db ) |
sweeptimelockmanual | :pencil: Manually sweep funds in a locally force closed channel where no channel.db file is available |
triggerforceclose | :pencil: (:pushpin:) Request a peer to force close a channel |
vanitygen | Generate an lnd seed for a node public key that starts with a certain sequence of hex digits |
walletinfo | Show information from a wallet.db file, requires access to the wallet password |
zombierecovery | :pencil: Cooperatively rescue funds from channels where normal recovery is not possible (see full guide here) |