#wallet #descriptor #federation #on-chain #secret-key #funds #private-key

app fedimint-recoverytool

Tool for retrieving on-chain funds from a decommissioned Fedimint federation

42 releases

new 0.5.0 Dec 19, 2024
0.4.3 Oct 10, 2024
0.4.2 Sep 16, 2024
0.4.0-rc.4 Jul 26, 2024
0.2.0-rc3 Nov 29, 2023

#2 in #funds

37 downloads per month

MIT license

1MB
24K SLoC

Recovery tool

The recoverytool allows extracting wallet descriptors and their corresponding private keys from an offline federation. This is useful in cases where a federation has been shut down and users have withdrawn their funds, but due to lost e-cash notes or other operational difficulties there are funds left in the federation's on-chain wallet.

IMPORTANT:

  • This is an expert tool, handle with care
  • Do not share any output with untrusted parties, it contains secret key material
  • Do not use while federation is running or if it is expected to be ever started again

Usage

Tool to recover the on-chain wallet of a Fedimint federation

Usage: recoverytool [OPTIONS] --password <PASSWORD> <--cfg <CONFIG>|--descriptor <DESCRIPTOR>> <COMMAND>

Commands:
  direct  Derive the wallet descriptor using a single tweak
  utxos   Derive all wallet descriptors of confirmed UTXOs in the on-chain wallet. Note that unconfirmed change UTXOs will not appear here
  epochs  Derive all wallet descriptors of tweaks that were ever used according to the epoch log. In a long-running and busy federation this list will contain many empty descriptors
  help    Print this message or the help of the given subcommand(s)

Options:
      --cfg <CONFIG>             Directory containing server config files
      --password <PASSWORD>      The password that encrypts the configs [env: FM_PASSWORD=]
      --descriptor <DESCRIPTOR>  Wallet descriptor, can be used instead of --cfg
      --key <KEY>                Wallet secret key, can be used instead of config together with --descriptor
      --network <NETWORK>        Network to operate on, has to be specified if --cfg isn't present [default: bitcoin]
  -h, --help                     Print help

To recover a wallet this tool requires the guardian's secret key, the federation's wallet descriptor and the tweaks used to derive wallet descriptors.

The secret key and wallet descriptor can be supplied in two ways:

  1. Config: By specifying the --cfg flag the tool will read the key and descriptor directly from the config files
  2. Direct argument: Otherwise the --key and --descriptor flags have to be provided. Optionally the --network flag can be provided to specify the network in this case since it cannot be determined from the config.

The tweaks making up the wallet can be provided in three ways, these correspond to the commands listed in the help above:

  1. direct: Provide a single tweak manually (e.g. extracted from user wallet that attempted a peg-in after federation shutdown)
  2. utxos: Read all confirmed UTXOs from the provided database, this will leave out in-flight UTXOs
  3. epochs: Scan entire epoch history in the provided database for tweaks, this will return the most wallet descriptors, most of them empty, but should cover funds and edge cases.

In case of utxos and epochs the provided database does not need to be the one of the guardian that is trying to recover, but of any guardian that stayed in-consensus till the end. This means funds are recoverable even with 1 database and t of n configs/secret keys.

Recovery

This tool is meant to be used together with a Bitcoin Core wallet and the jq tool. While we include a brief overview of a possible usage pattern below, please consult the Bitcoin Core documentation for more information about wallet and transaction handling.

The output of recoverytool consists of an array of wallet descriptors with optionally some meta-data. The descriptors include n-1 public keys and 1 private key (belonging to the guardian running the script):

$ recoverytool --cfg fedimintd-1 --password pass1 utxos --db fedimintd-1/database/ | jq
[
  {
    "outpoint": "c76d51c9c6dc6b8e462c37b3fa376e7c2055f04f16a8fffc2ab66c5bf0deff55:1",
    "descriptor": "wsh(sortedmulti(3,0280bf3115766b7e1cb23f2f57caf4de5a9171d3985934f2895cc568b384b52319,cSRM825vXJwf8iXcngedon8nQsPC9VMB18wSiG1Fgw6Y1Kzi16ta,021123625d9b21822e1178fc0d2b3f737d0397584cfac821df3250e49c3127cab5,03865705be62a71a3776cca1169908099ad6d138a0c0ed4aeeaf2c8e64616f4085))#h2zg3uhw",
    "amount_sat": 20000
  },
  {
    "outpoint": "d19b1545907679882e925ea0c2130969eab26ab2e27431404fef7d30a5fd649c:1",
    "descriptor": "wsh(sortedmulti(3,025fbaf8b94d101215d608ae6485b5746142933c261dc5616190f3a331eeaf5f3a,cTgHDPq43RFCy5i9jr2XwMQQNEJ1Y2cYHxhyvXj2wLpF8pi2LxUj,02e25f08feee064cb4bbbb3b33c7e0ddf4d06ed261201f40532c4f5d2152072ecf,03b215967b608d4309fec126a42d49950f6049406b439d4f743cb368362a0cfce0))#v6s5xpsa",
    "amount_sat": 10000
  }
]

To import it into bitcoin core use the following jq command to transform the tool's output into a valid input format for bitcoin-cli importdescriptors:

$ WALLETS="$(recoverytool --cfg fedimintd-1 --password pass1 utxos --db fedimintd-1/database/ | jq '. | map({"desc": .descriptor, "timestamp":0})')"
[
  {
    "desc": "wsh(sortedmulti(3,0280bf3115766b7e1cb23f2f57caf4de5a9171d3985934f2895cc568b384b52319,cSRM825vXJwf8iXcngedon8nQsPC9VMB18wSiG1Fgw6Y1Kzi16ta,021123625d9b21822e1178fc0d2b3f737d0397584cfac821df3250e49c3127cab5,03865705be62a71a3776cca1169908099ad6d138a0c0ed4aeeaf2c8e64616f4085))#h2zg3uhw",
    "timestamp": 0
  },
  {
    "desc": "wsh(sortedmulti(3,025fbaf8b94d101215d608ae6485b5746142933c261dc5616190f3a331eeaf5f3a,cTgHDPq43RFCy5i9jr2XwMQQNEJ1Y2cYHxhyvXj2wLpF8pi2LxUj,02e25f08feee064cb4bbbb3b33c7e0ddf4d06ed261201f40532c4f5d2152072ecf,03b215967b608d4309fec126a42d49950f6049406b439d4f743cb368362a0cfce0))#v6s5xpsa",
    "timestamp": 0
  }
]

And run importdescriptors in bitcoin-cli:

$ bitcoin-cli importdescriptors "$WALLETS"
[                                                                                                                                       
  {                                                                                                                                                                                                                                                                             
    "success": true,                                                                                                                                                                                                                                                            
    "warnings": [                                                                                                                                                                                                                                                               
      "Not all private keys provided. Some wallet functionality may return unexpected errors"                                                                                                                                                                                       ]                                                                                                                                                                                                                                                                             },                                                                                                                                                                                                                                                                            
  {                                                                                                                                                                                                                                                                             
    "success": true,                                                                                                                                                                                                                                                            
    "warnings": [                                                                                                                                                                                                                                                               
      "Not all private keys provided. Some wallet functionality may return unexpected errors"
    ]                                                                                                                                   
  }                                                                                                                                                                                                                                                                             ]

To move these funds create a transaction using walletcreatefundedpsbt, sign it with t of the wallets using walletprocesspsbt and extract the final transaction with finalizepsbt. The resulting transaction can be broadcasted using sendrawtransaction.

This workflow has been tested with n different wallets in Bitcoin Core and with PSBTs to collaboratively sign transactions. You might be able to import all keys into one wallet though and sign transactions right away.

Dependencies

~103MB
~2M SLoC