From 6719c2d8f18ffcb218c63c75bd1e159276f0711a Mon Sep 17 00:00:00 2001 From: Dominique Date: Thu, 1 Feb 2018 22:04:51 +0100 Subject: [PATCH] Added an optional seed to Setup (#424) If this seed is not provided, it is generated and stored in a seed.dat file. The electrum watcher uses this seed for its key. --- .../scala/fr/acinq/eclair/NodeParams.scala | 20 ++++++++----- .../main/scala/fr/acinq/eclair/Setup.scala | 20 +++++++++---- .../electrum/ElectrumEclairWallet.scala | 2 -- .../blockchain/electrum/ElectrumWallet.scala | 30 +++---------------- .../ElectrumWalletSimulatedClientSpec.scala | 5 ++-- .../electrum/ElectrumWalletSpec.scala | 2 +- 6 files changed, 34 insertions(+), 45 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index ba53a6661..aa4e994e5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -84,17 +84,21 @@ object NodeParams { .withFallback(overrideDefaults) .withFallback(ConfigFactory.load()).getConfig("eclair") - def makeNodeParams(datadir: File, config: Config): NodeParams = { + def makeNodeParams(datadir: File, config: Config, seed_opt: Option[BinaryData] = None): NodeParams = { datadir.mkdirs() - val seedPath = new File(datadir, "seed.dat") - val seed: BinaryData = seedPath.exists() match { - case true => Files.readAllBytes(seedPath.toPath) - case false => - val seed = randomKey.toBin - Files.write(seedPath.toPath, seed) - seed + val seed: BinaryData = seed_opt match { + case Some(s) => s + case None => + val seedPath = new File(datadir, "seed.dat") + seedPath.exists() match { + case true => Files.readAllBytes(seedPath.toPath) + case false => + val seed = randomKey.toBin + Files.write(seedPath.toPath, seed) + seed + } } val master = DeterministicWallet.generate(seed) val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 317752928..26fdd8ec3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -30,15 +30,22 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext, Future, Promise} /** + * Setup eclair from a datadir. + *

* Created by PM on 25/01/2016. + * + * @param datadir directory where eclair-core will write/read its data + * @param overrideDefaults + * @param actorSystem + * @param seed_opt optional seed, if set eclair will use it instead of generating one and won't create a seed.dat file. */ -class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), actorSystem: ActorSystem = ActorSystem()) extends Logging { +class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), actorSystem: ActorSystem = ActorSystem(), seed_opt: Option[BinaryData] = None) extends Logging { logger.info(s"hello!") logger.info(s"version=${getClass.getPackage.getImplementationVersion} commit=${getClass.getPackage.getSpecificationVersion}") val config: Config = NodeParams.loadConfiguration(datadir, overrideDefaults) - val nodeParams: NodeParams = NodeParams.makeNodeParams(datadir, config) + val nodeParams: NodeParams = NodeParams.makeNodeParams(datadir, config, seed_opt) val chain: String = config.getString("chain") // early checks @@ -140,10 +147,11 @@ class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), act val wallet = bitcoin match { case Bitcoind(bitcoinClient) => new BitcoinCoreWallet(bitcoinClient.rpcClient) case Bitcoinj(bitcoinj) => new BitcoinjWallet(bitcoinj.initialized.map(_ => bitcoinj.wallet())) - case Electrum(electrumClient) => - val electrumSeedPath = new File(datadir, "electrum_seed.dat") - val electrumWallet = system.actorOf(ElectrumWallet.props(electrumSeedPath, electrumClient, ElectrumWallet.WalletParameters(Block.RegtestGenesisBlock.hash, allowSpendUnconfirmed = true)), "electrum-wallet") - new ElectrumEclairWallet(electrumWallet) + case Electrum(electrumClient) => seed_opt match { + case Some(seed) => val electrumWallet = system.actorOf(ElectrumWallet.props(seed, electrumClient, ElectrumWallet.WalletParameters(Block.TestnetGenesisBlock.hash)), "electrum-wallet") + new ElectrumEclairWallet(electrumWallet) + case _ => throw new RuntimeException("electrum wallet requires a seed to set up") + } } wallet.getFinalAddress.map { case address => logger.info(s"initial wallet address=$address") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala index 44f7d3333..b9dbe46dd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala @@ -63,7 +63,5 @@ class ElectrumEclairWallet(val wallet: ActorRef)(implicit system: ActorSystem, e } } - def getMnemonics: Future[Seq[String]] = (wallet ? GetMnemonicCode).mapTo[GetMnemonicCodeResponse].map(_.mnemonics) - override def rollback(tx: Transaction): Future[Boolean] = (wallet ? CancelTransaction(tx)).map(_ => true) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala index 0ffdc11bc..dd401b3f5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala @@ -1,15 +1,11 @@ package fr.acinq.eclair.blockchain.electrum -import java.io.File - import akka.actor.{ActorRef, LoggingFSM, Props} -import com.google.common.io.Files import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, derivePrivateKey, hardened} -import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, Crypto, DeterministicWallet, MnemonicCode, OP_PUSHDATA, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptFlags, ScriptWitness, SigVersion, Transaction, TxIn, TxOut} +import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Block, Crypto, DeterministicWallet, OP_PUSHDATA, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptWitness, SigVersion, Transaction, TxIn, TxOut} import fr.acinq.eclair.blockchain.bitcoind.rpc.Error import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{GetTransaction, GetTransactionResponse, TransactionHistoryItem, computeScriptHash} -import fr.acinq.eclair.randomBytes import fr.acinq.eclair.transactions.Transactions import grizzled.slf4j.Logging @@ -28,16 +24,15 @@ import scala.util.{Failure, Success, Try} * client <--- ask tx ----- wallet * client ---- tx ----> wallet * - * @param mnemonics + * @param seed * @param client * @param params */ -class ElectrumWallet(mnemonics: Seq[String], client: ActorRef, params: ElectrumWallet.WalletParameters) extends LoggingFSM[ElectrumWallet.State, ElectrumWallet.Data] { +class ElectrumWallet(seed: BinaryData, client: ActorRef, params: ElectrumWallet.WalletParameters) extends LoggingFSM[ElectrumWallet.State, ElectrumWallet.Data] { import ElectrumWallet._ import params._ - val seed = MnemonicCode.toSeed(mnemonics, "") val master = DeterministicWallet.generate(seed) val accountMaster = accountKey(master) @@ -241,7 +236,6 @@ class ElectrumWallet(mnemonics: Seq[String], client: ActorRef, params: ElectrumW } whenUnhandled { - case Event(GetMnemonicCode, _) => stay replying GetMnemonicCodeResponse(mnemonics) case Event(GetCurrentReceiveAddress, data) => stay replying GetCurrentReceiveAddressResponse(data.currentReceiveAddress) @@ -263,20 +257,7 @@ object ElectrumWallet { // use 32 bytes seed, which will generate a 24 words mnemonic code val SEED_BYTES_LENGTH = 32 - def props(mnemonics: Seq[String], client: ActorRef, params: WalletParameters): Props = Props(new ElectrumWallet(mnemonics, client, params)) - - def props(file: File, client: ActorRef, params: WalletParameters): Props = { - val entropy: BinaryData = (file.exists(), file.canRead(), file.isFile) match { - case (true, true, true) => Files.toByteArray(file) - case (false, _, _) => - val buffer = randomBytes(SEED_BYTES_LENGTH) - Files.write(buffer, file) - buffer - case _ => throw new IllegalArgumentException(s"cannot create wallet:$file exist but cannot read from") - } - val mnemonics = MnemonicCode.toMnemonics(entropy) - Props(new ElectrumWallet(mnemonics, client, params)) - } + def props(seed: BinaryData, client: ActorRef, params: WalletParameters): Props = Props(new ElectrumWallet(seed, client, params)) case class WalletParameters(chainHash: BinaryData, minimumFee: Satoshi = Satoshi(2000), dustLimit: Satoshi = Satoshi(546), swipeRange: Int = 10, allowSpendUnconfirmed: Boolean = true) @@ -289,9 +270,6 @@ object ElectrumWallet { sealed trait Request sealed trait Response - case object GetMnemonicCode extends RuntimeException - case class GetMnemonicCodeResponse(mnemonics: Seq[String]) extends Response - case object GetBalance extends Request case class GetBalanceResponse(confirmed: Satoshi, unconfirmed: Satoshi) extends Response diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala index 0cf0292cf..f70524094 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala @@ -6,8 +6,9 @@ import fr.acinq.bitcoin.{BinaryData, Block, MnemonicCode, Satoshi} import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ScriptHashSubscription, ScriptHashSubscriptionResponse} import fr.acinq.eclair.blockchain.electrum.ElectrumWallet.{NewWalletReceiveAddress, WalletEvent, WalletParameters, WalletReady} import org.junit.runner.RunWith -import org.scalatest.{BeforeAndAfterAll, FunSuite, FunSuiteLike} +import org.scalatest.FunSuiteLike import org.scalatest.junit.JUnitRunner + import scala.concurrent.duration._ @RunWith(classOf[JUnitRunner]) @@ -26,7 +27,7 @@ class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) wit val listener = TestProbe() system.eventStream.subscribe(listener.ref, classOf[WalletEvent]) - val wallet = TestFSMRef(new ElectrumWallet(mnemonics, system.actorOf(Props(new SimulatedClient())), WalletParameters(Block.RegtestGenesisBlock.hash, minimumFee = Satoshi(5000)))) + val wallet = TestFSMRef(new ElectrumWallet(seed, system.actorOf(Props(new SimulatedClient())), WalletParameters(Block.RegtestGenesisBlock.hash, minimumFee = Satoshi(5000)))) // wallet sends a receive address notification as soon as it is created listener.expectMsgType[NewWalletReceiveAddress] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala index 8b5167331..ad78a907d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala @@ -21,7 +21,7 @@ class ElectrumWalletSpec extends IntegrationSpec { var wallet: ActorRef = _ test("wait until wallet is ready") { - wallet = system.actorOf(Props(new ElectrumWallet(mnemonics, electrumClient, WalletParameters(Block.RegtestGenesisBlock.hash, minimumFee = Satoshi(5000)))), "wallet") + wallet = system.actorOf(Props(new ElectrumWallet(seed, electrumClient, WalletParameters(Block.RegtestGenesisBlock.hash, minimumFee = Satoshi(5000)))), "wallet") val probe = TestProbe() awaitCond({ probe.send(wallet, GetData)