Skip to content

Commit

Permalink
Remove code to automatically create eclair-managed wallet (#2746)
Browse files Browse the repository at this point in the history
Remove code to automatically create eclair-managed wallet

This code made the onchain key manager more complex, and for older wallets (when nodes are moved to a new machine for example) we need
to provide a manual process for creating a new empty wallet and importing descriptors generated by Eclair.

=> It is simpler to always use this manual process.
  • Loading branch information
sstone authored Sep 21, 2023
1 parent 7cd8b14 commit bf6f240
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 115 deletions.
2 changes: 1 addition & 1 deletion docs/Guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This section contains how-to guides for more advanced scenarios:

* [Customize Logging](./Logging.md)
* [Customize Features](./Features.md)
* [Manage Bitcoin Core's private keys](./BitcoinCoreKeys.md)
* [Manage Bitcoin Core's private keys](./ManagingBitcoinCoreKeys.md)
* [Use Tor with Eclair](./Tor.md)
* [Multipart Payments](./MultipartPayments.md)
* [Trampoline Payments](./TrampolinePayments.md)
Expand Down
92 changes: 25 additions & 67 deletions docs/BitcoinCoreKeys.md → docs/ManagingBitcoinCoreKeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ You can use any BIP39-compatible tool, including most hardware wallets.

A signer configuration file uses the HOCON format that we already use for `eclair.conf` and must include the following options:

key | description
--------------------------|--------------------------------------------------------------------------
eclair.signer.wallet | wallet name
eclair.signer.mnemonics | BIP39 mnemonic words
eclair.signer.passphrase | passphrase
eclair.signer.timestamp | wallet creation UNIX timestamp. Set to the current time for new wallets.
key | description
--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
eclair.signer.wallet | wallet name
eclair.signer.mnemonics | BIP39 mnemonic words
eclair.signer.passphrase | passphrase
eclair.signer.timestamp | wallet creation UNIX timestamp. Bitcoin core will rescan the blockchain from this UNIX timestamp. Set it to the wallet creation timestamp for simplicity, or a later date if you only have recent UTXOs and you know what you are doing.

This is an example of `eclair-signer.conf` configuration file:

Expand All @@ -38,85 +38,43 @@ This is an example of `eclair-signer.conf` configuration file:
}
```

### 3. Configure Eclair to handle private keys for this wallet
### 3. Use Eclair to generate descriptors and import them into a new bitcoin wallet

Set `eclair.bitcoind.wallet` to the name of the wallet in your `eclair-signer.conf` file (`eclair` in the example above) and restart Eclair.
Eclair will automatically create a new, empty, descriptor-enabled, watch-only wallet in Bitcoin Core and import its descriptors.
Restart eclair, without changing `eclair.bitcoind.wallet` (so it uses the default wallet or the previously used bitcoin wallet for existing nodes).

:warning: Eclair will not import descriptors if the timestamp set in your `eclair-signer.conf` is more than 2 hours old. If the mnemonics and
passphrase that your are using are new, you can safely update this timestamp, but if they have been used before you will need to follow
the steps described in the next section.

You now have a Bitcoin Core watch-only wallet for which only your Eclair node can sign transactions. This Bitcoin Core wallet can
safely be copied to another Bitcoin Core node to monitor your on-chain funds.

You can also use `eclair-cli getmasterxpub` to get a BIP32 extended public key that you can import into any compatible Bitcoin wallet
to create a watch-only wallet (Electrum for example) that you can use to monitor your Bitcoin Core balance.

:warning: this means that your Bitcoin Core wallet cannot send funds on its own (since it cannot access private keys to sign transactions).
To send funds on-chain you must use `eclair-cli sendonchain`.

:warning: to backup the private keys of this wallet you must either backup your mnemonic code and passphrase, or backup the `eclair-signer.conf` file in your eclair
directory (default is `~/.eclair`) along with your channels and node seed files.

:warning: You can also initialize a backup on-chain wallet with the same mnemonic code and passphrase (on a hardware wallet for example), but be warned that using them may interfere with your node's operations (for example you may end up
double-spending funding transactions generated by your node).

## Importing an existing Eclair-backed Bitcoin Core wallet

The steps above described how you can simply switch to a new Eclair-backed bitcoin wallet.
Follow the steps below if you are already using an Eclair-backed bitcoin wallet that you want to move to another Bitcoin Core node.

### 1. Create an empty, descriptor-enabled, watch-only wallet in Bitcoin Core

Start by creating a watch-only wallet on your new Bitcoin Core node.
Create a new empty, decriptor-enabled wallet on your new Bitcoin Core node.

:warning: The name must match the one that you set in `eclair-signer.conf` (here we use "eclair")

```shell
$ bitcoin-cli -named createwallet wallet_name=eclair disable_private_keys=true blank=true descriptors=true load_on_startup=true
```

### 2. Import public descriptors generated by Eclair

Calling `eclair-cli getdescriptors` on your existing Eclair node will return public wallet descriptors in a format that is compatible with Bitcoin Core.
This is an example of descriptors generated by Eclair:

```json
[
{
"desc": "wpkh([0d9250da/84h/1h/0h]tpubDDGF9PnrXww2h1mKNjKiXoqdDFGEcZGCZUNq7g26LdzKXKiE31RrFWsogPy1uMLrbG8ksQ8eJS6u6KFLjYUUSVJRuwmMD2SYCr8uG1TcRgM/0/*)#jz5n2pcp",
"internal": false,
"timestamp": 1686055705,
"active": true
},
{
"desc": "wpkh([0d9250da/84h/1h/0h]tpubDDGF9PnrXww2h1mKNjKiXoqdDFGEcZGCZUNq7g26LdzKXKiE31RrFWsogPy1uMLrbG8ksQ8eJS6u6KFLjYUUSVJRuwmMD2SYCr8uG1TcRgM/1/*)#rk3jh5ge",
"internal": true,
"timestamp": 1686055705,
"active": true
}
]
```

Generate the descriptors with your Eclair node and import them into a Bitcoin node with the following commands:

```shell
$ eclair-cli getdescriptors | jq --raw-output -c > descriptors.json
$ cat descriptors.json | xargs -0 bitcoin-cli -rpcwallet=eclair importdescriptors
```

:warning: Importing descriptors can take a long time, and your Bitcoin Core node will not be usable until it's done
Bitcoin core will import descriptors and rescan the blockchain from the time set in `eclair-signer.conf`.
This can take a long time (if you're moving an old existing node to a new setup for example) and your Bitcoin Core node will not be usable until it's done.

### 3. Configure Eclair to use your new Bitcoin Core node
### 4. Configure Eclair to use the wallet you created and restart Eclair

Once your new Bitcoin Core node has finished importing the descriptors, it is ready to be used by Eclair.
In your `eclair.conf`, set `eclair.bitcoind.wallet` to the name of the wallet in `eclair-signer.conf`, and restart Eclair.

In your `eclair.conf`:
You now have a Bitcoin Core watch-only wallet for which only your Eclair node can sign transactions. This Bitcoin Core wallet can
safely be copied to another Bitcoin Core node to monitor your on-chain funds.

- set `eclair.bitcoind.host` to the address of your new Bitcoin Core node
- set `eclair.bitcoind.wallet` to the name of the wallet in `eclair-signer.conf`
- set `eclair.bitcoind.zmqblock` and `eclair.bitcoind.zmqtx` to use your new Bitcoin Core node
- update other field in the `eclair.bitcoind` section if necessary (`rpcport`, `auth`, `rpcuser`, `rpcuser`, etc)
:warning: this means that your Bitcoin Core wallet cannot send funds on its own (since it cannot access private keys to sign transactions).
To send funds on-chain you must use `eclair-cli sendonchain`.

Restart Eclair and it will start using your new Bitcoin Core node.
:warning: to backup the private keys of this wallet you must either backup your mnemonic code and passphrase, or backup the `eclair-signer.conf` file in your eclair
directory (default is `~/.eclair`) along with your channels and node seed files.

:warning: You can also initialize a backup on-chain wallet with the same mnemonic code and passphrase (on a hardware wallet for example), but be warned that using them may interfere with your node's operations (for example you may end up
double-spending funding transactions generated by your node).

You can also use `eclair-cli getmasterxpub` to get a BIP32 extended public key that you can import into any compatible Bitcoin wallet
to create a watch-only wallet (Electrum for example) that you can use to monitor your Bitcoin Core balance.
5 changes: 0 additions & 5 deletions eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,6 @@ class Setup(val datadir: File,
.collect {
case JArray(values) => values.map(value => value.extract[String])
}
eclairBackedWalletOk <- onChainKeyManager_opt match {
case Some(keyManager) if !wallets.contains(keyManager.walletName) => keyManager.createWallet(bitcoinClient)
case _ => Future.successful(true)
}
_ = assert(eclairBackedWalletOk || onChainKeyManager_opt.map(_.walletName) != wallet, s"cannot create eclair-backed wallet=${onChainKeyManager_opt.map(_.walletName)}, check logs for details")
progress = (json \ "verificationprogress").extract[Double]
ibd = (json \ "initialblockdownload").extract[Boolean]
blocks = (json \ "blocks").extract[Long]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object LocalOnChainKeyManager extends Logging {
* Eclair is in charge of signing transactions.
* This is an advanced feature particularly suited when Eclair runs in a secure runtime.
*/
class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector, override val walletTimestamp: TimestampSecond, chainHash: ByteVector32) extends OnChainKeyManager with Logging {
class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector, val walletTimestamp: TimestampSecond, chainHash: ByteVector32) extends OnChainKeyManager with Logging {

import LocalOnChainKeyManager._

Expand Down Expand Up @@ -103,27 +103,6 @@ class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector,
(pub, address)
}

override def createWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionContext): Future[Boolean] = {
if (walletTimestamp < (TimestampSecond.now() - 2.hours)) {
logger.warn(s"eclair-backed wallet descriptors for wallet=$walletName are too old to be automatically imported into bitcoin core, you will need to manually import them and select how far back to rescan")
Future.successful(false)
} else {
logger.info(s"creating a new on-chain eclair-backed wallet in bitcoind: $walletName")
rpcClient.invoke("createwallet", walletName, /* disable_private_keys */ true, /* blank */ true, /* passphrase */ "", /* avoid_reuse */ false, /* descriptors */ true, /* load_on_startup */ true).flatMap(_ => {
logger.info(s"importing new descriptors ${descriptors(0).descriptors}")
rpcClient.invoke("importdescriptors", descriptors(0).descriptors).collect {
case JArray(results) => results.forall(item => {
val JBool(success) = item \ "success"
success
})
}
}).recover { e =>
logger.error("cannot create eclair-backed on-chain wallet: ", e)
false
}
}
}

override def descriptors(account: Long): Descriptors = {
// TODO: we should use 'h' everywhere once bitcoin-kmp supports it.
val keyPath = s"$rootPath/$account'".replace('\'', 'h')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,6 @@ import scala.util.Try
trait OnChainKeyManager {
def walletName: String

/**
* @return the creation time of the wallet managed by this key manager
*/
def walletTimestamp(): TimestampSecond

/**
* Create a bitcoin core watch-only wallet with private keys owned by this key manager instance.
* This should only be called if the corresponding wallet doesn't already exist.
*/
def createWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionContext): Future[Boolean]

/**
* @param account account number (0 is used by most wallets)
* @return the on-chain pubkey for this account, which can then be imported into a BIP39-compatible wallet such as Electrum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1418,8 +1418,7 @@ class BitcoinCoreClientWithEclairSignerSpec extends BitcoinCoreClientSpec {
val seed = MnemonicCode.toSeed(MnemonicCode.toMnemonics(entropy), "")
val master = DeterministicWallet.generate(seed)
val (wallet, keyManager) = createWallet(seed)
keyManager.createWallet(wallet.rpcClient).pipeTo(sender.ref)
sender.expectMsg(true)
createEclairBackedWallet(wallet.rpcClient, keyManager)

// this account xpub can be used to create a watch-only wallet
val accountXPub = DeterministicWallet.encode(
Expand Down Expand Up @@ -1453,8 +1452,7 @@ class BitcoinCoreClientWithEclairSignerSpec extends BitcoinCoreClientSpec {

(1 to 10).foreach { _ =>
val (wallet, keyManager) = createWallet(randomBytes32())
keyManager.createWallet(wallet.rpcClient).pipeTo(sender.ref)
sender.expectMsg(true)
createEclairBackedWallet(wallet.rpcClient, keyManager)
wallet.getReceiveAddress().pipeTo(sender.ref)
val address = sender.expectMsgType[String]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, Btc, BtcAmount, MilliBtc, MnemonicCo
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinJsonRPCAuthMethod.{SafeCookie, UserPassword}
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinCoreClient, BitcoinJsonRPCAuthMethod, BitcoinJsonRPCClient}
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKB, FeeratePerKw}
import fr.acinq.eclair.crypto.keymanager.LocalOnChainKeyManager
import fr.acinq.eclair.crypto.keymanager.{LocalOnChainKeyManager, OnChainKeyManager}
import fr.acinq.eclair.integration.IntegrationSpec
import fr.acinq.eclair.{BlockHeight, TestUtils, TimestampSecond, randomKey}
import grizzled.slf4j.Logging
Expand Down Expand Up @@ -171,8 +171,7 @@ trait BitcoindService extends Logging {
val sender = TestProbe()
waitForBitcoindUp(sender)
if (useEclairSigner) {
onChainKeyManager.createWallet(bitcoinrpcclient).pipeTo(sender.ref)
sender.expectMsg(true)
createEclairBackedWallet(bitcoinrpcclient, onChainKeyManager)
} else {
sender.send(bitcoincli, BitcoinReq("createwallet", defaultWallet))
sender.expectMsgType[JValue]
Expand All @@ -182,6 +181,17 @@ trait BitcoindService extends Logging {
awaitCond(currentBlockHeight(sender) >= BlockHeight(150), max = 3 minutes, interval = 2 second)
}

def createEclairBackedWallet(bitcoinClient: BitcoinJsonRPCClient, keyManager: OnChainKeyManager): Unit = {
val sender = TestProbe()
// wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup, external_signer
bitcoinClient.invoke("createwallet", keyManager.walletName, true, false, "", false, true, true, false).pipeTo(sender.ref)
sender.expectMsgType[JValue]
val descriptors = keyManager.descriptors(0).descriptors
bitcoinClient.invoke("importdescriptors", descriptors).pipeTo(sender.ref)
sender.expectMsgType[JValue]
}


/** Generate blocks to a given address, or to our wallet if no address is provided. */
def generateBlocks(blockCount: Int, address: Option[String] = None, timeout: FiniteDuration = 10 seconds)(implicit system: ActorSystem): Unit = {
val sender = TestProbe()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1665,8 +1665,7 @@ class ReplaceableTxPublisherWithEclairSignerSpec extends ReplaceableTxPublisherS

override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey
}
keyManager.createWallet(walletRpcClient).pipeTo(probe.ref)
probe.expectMsg(true)
createEclairBackedWallet(walletRpcClient, keyManager)

(walletRpcClient, walletClient)
}
Expand Down

0 comments on commit bf6f240

Please sign in to comment.