1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

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.
This commit is contained in:
Dominique 2018-02-01 22:04:51 +01:00 committed by GitHub
parent 7a6fa8a619
commit 6719c2d8f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 34 additions and 45 deletions

View File

@ -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)

View File

@ -30,15 +30,22 @@ import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext, Future, Promise}
/**
* Setup eclair from a datadir.
* <p>
* 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")

View File

@ -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)
}

View File

@ -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

View File

@ -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]

View File

@ -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)