diff --git a/app/server/src/main/scala/org/bitcoins/server/Main.scala b/app/server/src/main/scala/org/bitcoins/server/Main.scala index afabdcb51b82..8f6346b7e5ba 100644 --- a/app/server/src/main/scala/org/bitcoins/server/Main.scala +++ b/app/server/src/main/scala/org/bitcoins/server/Main.scala @@ -38,13 +38,13 @@ object Main extends App { val peerSocket = parseInetSocketAddress(nodeConf.peers.head, nodeConf.network.port) val peer = Peer.fromSocket(peerSocket) - + val bip39PasswordOpt = None //todo need to prompt user for this val startFut = for { _ <- conf.initialize() uninitializedNode <- createNode chainApi <- uninitializedNode.chainApiFromDb() - wallet <- createWallet(uninitializedNode, chainApi) + wallet <- createWallet(uninitializedNode, chainApi, bip39PasswordOpt) node <- initializeNode(uninitializedNode, wallet) _ <- node.start() @@ -94,13 +94,14 @@ object Main extends App { private def createWallet( nodeApi: Node, - chainQueryApi: ChainQueryApi): Future[UnlockedWalletApi] = { + chainQueryApi: ChainQueryApi, + bip39PasswordOpt: Option[String]): Future[UnlockedWalletApi] = { if (hasWallet()) { logger.info(s"Using pre-existing wallet") val locked = LockedWallet(nodeApi, chainQueryApi) // TODO change me when we implement proper password handling - locked.unlock(BIP39KeyManager.badPassphrase) match { + locked.unlock(BIP39KeyManager.badPassphrase, bip39PasswordOpt) match { case Right(wallet) => Future.successful(wallet) case Left(kmError) => @@ -108,8 +109,10 @@ object Main extends App { } } else { logger.info(s"Initializing key manager") + val bip39PasswordOpt = None val keyManagerE: Either[KeyManagerInitializeError, BIP39KeyManager] = - BIP39KeyManager.initialize(walletConf.kmParams) + BIP39KeyManager.initialize(kmParams = walletConf.kmParams, + bip39PasswordOpt = bip39PasswordOpt) val keyManager = keyManagerE match { case Right(keyManager) => keyManager @@ -120,7 +123,8 @@ object Main extends App { logger.info(s"Creating new wallet") val unInitializedWallet = Wallet(keyManager, nodeApi, chainQueryApi) - Wallet.initialize(wallet = unInitializedWallet) + Wallet.initialize(wallet = unInitializedWallet, + bip39PasswordOpt = bip39PasswordOpt) } } diff --git a/docs/applications/wallet.md b/docs/applications/wallet.md index 7b33be660c2a..d067341b121b 100644 --- a/docs/applications/wallet.md +++ b/docs/applications/wallet.md @@ -111,7 +111,10 @@ val syncF: Future[ChainApi] = configF.flatMap { _ => //initialize our key manager, where we store our keys import org.bitcoins.keymanager.bip39._ -val keyManager = BIP39KeyManager.initialize(walletConfig.kmParams).getOrElse { +//you can add a password here if you want +//val bip39PasswordOpt = Some("my-password-here") +val bip39PasswordOpt = None +val keyManager = BIP39KeyManager.initialize(walletConfig.kmParams, bip39PasswordOpt).getOrElse { throw new RuntimeException(s"Failed to initalize key manager") } @@ -135,7 +138,7 @@ val wallet = Wallet(keyManager, new NodeApi { override def getFiltersBetweenHeights(startHeight: Int, endHeight: Int): Future[Vector[FilterResponse]] = Future.successful(Vector.empty) }) val walletF: Future[LockedWalletApi] = configF.flatMap { _ => - Wallet.initialize(wallet) + Wallet.initialize(wallet,bip39PasswordOpt) } // when this future completes, ww have sent a transaction diff --git a/docs/key-manager/key-manager.md b/docs/key-manager/key-manager.md index b4714bf9e8e7..1fb298a65efa 100644 --- a/docs/key-manager/key-manager.md +++ b/docs/key-manager/key-manager.md @@ -78,7 +78,7 @@ val network = RegTest val kmParams = KeyManagerParams(seedPath, purpose, network) -val km = BIP39KeyManager.initializeWithMnemonic(mnemonic, kmParams) +val km = BIP39KeyManager.initializeWithMnemonic(mnemonic, None, kmParams) val rootXPub = km.right.get.getRootXPub diff --git a/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39KeyManagerTest.scala b/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39KeyManagerTest.scala index 7be1a6035913..b36579896975 100644 --- a/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39KeyManagerTest.scala +++ b/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39KeyManagerTest.scala @@ -38,7 +38,8 @@ class BIP39KeyManagerTest extends KeyManagerUnitTest { val kmParams = buildParams() val keyManager = withInitializedKeyManager(kmParams = kmParams, - entropy = mnemonic.toEntropy) + entropy = mnemonic.toEntropy, + bip39PasswordOpt = None) keyManager.deriveXPub(hdAccount).get.toString must be ("xpub6D36zpm3tLPy3dBCpiScEpmmgsivFBcHxX5oXmPBW982BmLiEkjBEDdswxFUoeXpp272QuSpNKZ3f2TdEMkAHyCz1M7P3gFkYJJVEsM66SE") @@ -46,11 +47,14 @@ class BIP39KeyManagerTest extends KeyManagerUnitTest { it must "initialize a key manager to the same xpub if we call constructor directly or use CreateKeyManagerApi" in { val kmParams = buildParams() - val direct = BIP39KeyManager(mnemonic, kmParams) + val direct = BIP39KeyManager(mnemonic, kmParams, None) val directXpub = direct.getRootXPub - val api = BIP39KeyManager.initializeWithEntropy(mnemonic.toEntropy, kmParams).right.get + val api = BIP39KeyManager.initializeWithEntropy( + entropy = mnemonic.toEntropy, + bip39PasswordOpt = None, + kmParams = kmParams).right.get val apiXpub = api.getRootXPub @@ -61,9 +65,48 @@ class BIP39KeyManagerTest extends KeyManagerUnitTest { assert(api.deriveXPub(hdAccount) == direct.deriveXPub(hdAccount)) } + it must "initialize a key manager with a bip39 password to the same xpub if we call constructor directly or use CreateKeyManagerApi" in { + val kmParams = buildParams() + val bip39Pw = KeyManagerTestUtil.bip39Password + val direct = BIP39KeyManager(mnemonic, kmParams,Some(bip39Pw)) + + val directXpub = direct.getRootXPub + + val api = BIP39KeyManager.initializeWithEntropy( + mnemonic.toEntropy, + Some(bip39Pw), + kmParams).right.get + + val apiXpub = api.getRootXPub + + assert(apiXpub == directXpub, s"We don't have initialization symmetry between our constructors!") + + + //we should be able to derive the same child xpub + assert(api.deriveXPub(hdAccount) == direct.deriveXPub(hdAccount)) + } + + it must "NOT initialize a key manager to the same xpub if one has a password and one does not" in { + val kmParams = buildParams() + val bip39Pw = KeyManagerTestUtil.bip39Password + + val withPassword = BIP39KeyManager(mnemonic, kmParams, Some(bip39Pw)) + val withPasswordXpub = withPassword.getRootXPub + + val noPassword = BIP39KeyManager(mnemonic, kmParams, None) + + val noPwXpub = noPassword.getRootXPub + + assert(withPasswordXpub != noPwXpub, s"A key manager with a BIP39 passwrod should not generate the same xpub as a key manager without a password!") + + } + + it must "return a mnemonic not found if we have not initialized the key manager" in { val kmParams = buildParams() - val kmE = BIP39KeyManager.fromParams(kmParams, BIP39KeyManager.badPassphrase) + val kmE = BIP39KeyManager.fromParams(kmParams = kmParams, + password = BIP39KeyManager.badPassphrase, + bip39PasswordOpt = None) assert(kmE == Left(ReadMnemonicError.NotFoundError)) } @@ -80,7 +123,10 @@ class BIP39KeyManagerTest extends KeyManagerUnitTest { val badEntropy = BitVector.empty - val init = BIP39KeyManager.initializeWithEntropy(badEntropy, buildParams()) + val init = BIP39KeyManager.initializeWithEntropy( + entropy = badEntropy, + bip39PasswordOpt = KeyManagerTestUtil.bip39PasswordOpt, + kmParams = buildParams()) assert(init == Left(InitializeKeyManagerError.BadEntropy)) } diff --git a/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManagerTest.scala b/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManagerTest.scala index 76aff50466fe..e819b137593e 100644 --- a/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManagerTest.scala +++ b/key-manager-test/src/test/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManagerTest.scala @@ -6,9 +6,15 @@ import org.bitcoins.keymanager.{KeyManagerTestUtil, KeyManagerUnitTest, KeyManag class BIP39LockedKeyManagerTest extends KeyManagerUnitTest { it must "be able to read a locked mnemonic from disk" in { - val km = withInitializedKeyManager() + val bip39PwOpt = KeyManagerTestUtil.bip39PasswordOpt + val km = withInitializedKeyManager(bip39PasswordOpt = bip39PwOpt) + + val unlockedE = BIP39LockedKeyManager.unlock( + KeyManagerTestUtil.badPassphrase, + bip39PasswordOpt = bip39PwOpt, + km.kmParams) - val unlockedKm = BIP39LockedKeyManager.unlock(KeyManagerTestUtil.badPassphrase, km.kmParams) match { + val unlockedKm = unlockedE match { case Right(km) => km case Left(err) => fail(s"Failed to unlock key manager ${err}") } @@ -20,7 +26,11 @@ class BIP39LockedKeyManagerTest extends KeyManagerUnitTest { it must "fail to read bad json in the seed file" in { val km = withInitializedKeyManager() val badPassword = AesPassword.fromString("other bad password").get - BIP39LockedKeyManager.unlock(passphrase = badPassword, kmParams = km.kmParams) match { + val unlockedE = BIP39LockedKeyManager.unlock(passphrase = badPassword, + bip39PasswordOpt = None, + kmParams = km.kmParams) + + unlockedE match { case Left(KeyManagerUnlockError.BadPassword) => succeed case result @ (Left(_) | Right(_)) => fail(s"Expected to fail test with ${KeyManagerUnlockError.BadPassword} got ${result}") @@ -34,7 +44,9 @@ class BIP39LockedKeyManagerTest extends KeyManagerUnitTest { val badPath = km.kmParams.copy(seedPath = badSeedPath) val badPassword = AesPassword.fromString("other bad password").get - BIP39LockedKeyManager.unlock(badPassword, badPath) match { + val unlockedE = BIP39LockedKeyManager.unlock(badPassword, None, badPath) + + unlockedE match { case Left(KeyManagerUnlockError.MnemonicNotFound) => succeed case result @ (Left(_) | Right(_)) => fail(s"Expected to fail test with ${KeyManagerUnlockError.MnemonicNotFound} got ${result}") diff --git a/key-manager/src/main/scala/org/bitcoins/keymanager/KeyManagerCreateApi.scala b/key-manager/src/main/scala/org/bitcoins/keymanager/KeyManagerCreateApi.scala index 44e084ac7639..b101a37e30f8 100644 --- a/key-manager/src/main/scala/org/bitcoins/keymanager/KeyManagerCreateApi.scala +++ b/key-manager/src/main/scala/org/bitcoins/keymanager/KeyManagerCreateApi.scala @@ -1,6 +1,7 @@ package org.bitcoins.keymanager import org.bitcoins.core.crypto.MnemonicCode +import org.bitcoins.keymanager.bip39.BIP39KeyManager import scodec.bits.BitVector /** @@ -16,21 +17,29 @@ import scodec.bits.BitVector * can write it down. They should also be prompted * to confirm at least parts of the code. */ -trait KeyManagerCreateApi[T <: KeyManager] { +trait BIP39KeyManagerCreateApi { /** * $initialize */ final def initialize( - kmParams: KeyManagerParams): Either[KeyManagerInitializeError, T] = - initializeWithEntropy(entropy = MnemonicCode.getEntropy256Bits, kmParams) + kmParams: KeyManagerParams, + bip39PasswordOpt: Option[String]): Either[ + KeyManagerInitializeError, + BIP39KeyManager] = + initializeWithEntropy(entropy = MnemonicCode.getEntropy256Bits, + bip39PasswordOpt = bip39PasswordOpt, + kmParams = kmParams) /** * $initializeWithEnt */ def initializeWithEntropy( entropy: BitVector, - kmParams: KeyManagerParams): Either[KeyManagerInitializeError, T] + bip39PasswordOpt: Option[String], + kmParams: KeyManagerParams): Either[ + KeyManagerInitializeError, + BIP39KeyManager] /** * Helper method to initialize a [[KeyManagerCreate$ KeyManager]] with a [[MnemonicCode MnemonicCode]] @@ -41,8 +50,13 @@ trait KeyManagerCreateApi[T <: KeyManager] { */ final def initializeWithMnemonic( mnemonicCode: MnemonicCode, - kmParams: KeyManagerParams): Either[KeyManagerInitializeError, T] = { + bip39PasswordOpt: Option[String], + kmParams: KeyManagerParams): Either[ + KeyManagerInitializeError, + BIP39KeyManager] = { val entropy = mnemonicCode.toEntropy - initializeWithEntropy(entropy = entropy, kmParams) + initializeWithEntropy(entropy = entropy, + bip39PasswordOpt = bip39PasswordOpt, + kmParams = kmParams) } } diff --git a/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala b/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala index 5791f607e9f5..cbe5b6440a62 100644 --- a/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala +++ b/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala @@ -22,11 +22,17 @@ import scala.util.{Failure, Success, Try} */ case class BIP39KeyManager( private val mnemonic: MnemonicCode, - kmParams: KeyManagerParams) + kmParams: KeyManagerParams, + private val bip39PasswordOpt: Option[String]) extends KeyManager { - private val seed = BIP39Seed.fromMnemonic(mnemonic = mnemonic, - password = BIP39Seed.EMPTY_PASSWORD) + private val seed = bip39PasswordOpt match { + case Some(pw) => + BIP39Seed.fromMnemonic(mnemonic = mnemonic, password = pw) + case None => + BIP39Seed.fromMnemonic(mnemonic = mnemonic, + password = BIP39Seed.EMPTY_PASSWORD) + } private val privVersion: ExtKeyPrivVersion = HDUtil.getXprivVersion(kmParams.purpose, kmParams.network) @@ -53,14 +59,13 @@ case class BIP39KeyManager( } } -object BIP39KeyManager - extends KeyManagerCreateApi[BIP39KeyManager] - with BitcoinSLogger { +object BIP39KeyManager extends BIP39KeyManagerCreateApi with BitcoinSLogger { val badPassphrase = AesPassword.fromString("changeMe").get /** Initializes the mnemonic seed and saves it to file */ override def initializeWithEntropy( entropy: BitVector, + bip39PasswordOpt: Option[String], kmParams: KeyManagerParams): Either[ KeyManagerInitializeError, BIP39KeyManager] = { @@ -99,10 +104,17 @@ object BIP39KeyManager logger.info(s"Saved encrypted wallet mnemonic to $mnemonicPath") } - } yield BIP39KeyManager(mnemonic = mnemonic, kmParams = kmParams) + } yield { + BIP39KeyManager(mnemonic = mnemonic, + kmParams = kmParams, + bip39PasswordOpt = bip39PasswordOpt) + } //verify we can unlock it for a sanity check - val unlocked = BIP39LockedKeyManager.unlock(badPassphrase, kmParams) + val unlocked = BIP39LockedKeyManager.unlock(passphrase = badPassphrase, + bip39PasswordOpt = + bip39PasswordOpt, + kmParams = kmParams) val biasedFinalE: CompatEither[KeyManagerInitializeError, BIP39KeyManager] = for { @@ -133,13 +145,17 @@ object BIP39KeyManager /** Reads the key manager from disk and decrypts it with the given password */ def fromParams( kmParams: KeyManagerParams, - password: AesPassword): Either[ReadMnemonicError, BIP39KeyManager] = { + password: AesPassword, + bip39PasswordOpt: Option[String]): Either[ + ReadMnemonicError, + BIP39KeyManager] = { val mnemonicCodeE = WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, password) mnemonicCodeE match { - case Right(mnemonic) => Right(new BIP39KeyManager(mnemonic, kmParams)) - case Left(v) => Left(v) + case Right(mnemonic) => + Right(new BIP39KeyManager(mnemonic, kmParams, bip39PasswordOpt)) + case Left(v) => Left(v) } } } diff --git a/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManager.scala b/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManager.scala index dd778ede2018..5ca7afa0f267 100644 --- a/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManager.scala +++ b/key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39LockedKeyManager.scala @@ -17,7 +17,10 @@ object BIP39LockedKeyManager extends BitcoinSLogger { * @param kmParams parameters needed to create the key manager * * */ - def unlock(passphrase: AesPassword, kmParams: KeyManagerParams): Either[ + def unlock( + passphrase: AesPassword, + bip39PasswordOpt: Option[String], + kmParams: KeyManagerParams): Either[ KeyManagerUnlockError, BIP39KeyManager] = { logger.debug(s"Trying to unlock wallet with seedPath=${kmParams.seedPath}") @@ -25,7 +28,8 @@ object BIP39LockedKeyManager extends BitcoinSLogger { WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, passphrase) resultE match { case Right(mnemonicCode) => - Right(new BIP39KeyManager(mnemonicCode, kmParams)) + Right(BIP39KeyManager(mnemonicCode, kmParams, bip39PasswordOpt)) + case Left(result) => result match { case DecryptionError => diff --git a/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerTestUtil.scala b/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerTestUtil.scala index f555fdc279f5..800e1b056c9d 100644 --- a/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerTestUtil.scala @@ -2,9 +2,13 @@ package org.bitcoins.keymanager import java.nio.file.Path +import org.bitcoins.core.config.Networks import org.bitcoins.core.crypto.AesPassword +import org.bitcoins.core.hd.HDPurposes import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.testkit.BitcoinSTestAppConfig +import org.bitcoins.testkit.core.gen.CryptoGenerators +import org.scalacheck.Gen object KeyManagerTestUtil { @@ -15,5 +19,24 @@ object KeyManagerTestUtil { .resolve(WalletStorage.ENCRYPTED_SEED_FILE_NAME) } + def createKeyManagerParams(): KeyManagerParams = { + val seedPath = KeyManagerTestUtil.tmpSeedPath + KeyManagerParams(seedPath = seedPath, + purpose = Gen.oneOf(HDPurposes.all).sample.get, + network = Gen.oneOf(Networks.knownNetworks).sample.get) + } + + def bip39PasswordOpt: Option[String] = { + if (scala.util.Random.nextBoolean()) { + Some(bip39Password) + } else { + None + } + } + + def bip39Password: String = { + CryptoGenerators.bip39Password.sample.get + } + val badPassphrase: AesPassword = BIP39KeyManager.badPassphrase } diff --git a/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerUnitTest.scala b/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerUnitTest.scala index a9056eef99d5..4186e546eb84 100644 --- a/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerUnitTest.scala +++ b/testkit/src/main/scala/org/bitcoins/keymanager/KeyManagerUnitTest.scala @@ -1,27 +1,19 @@ package org.bitcoins.keymanager -import org.bitcoins.core.config.Networks import org.bitcoins.core.crypto.MnemonicCode -import org.bitcoins.core.hd.HDPurposes import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.testkit.util.BitcoinSUnitTest -import org.scalacheck.Gen import scodec.bits.BitVector trait KeyManagerUnitTest extends BitcoinSUnitTest { - def createKeyManagerParams(): KeyManagerParams = { - val seedPath = KeyManagerTestUtil.tmpSeedPath - KeyManagerParams(seedPath = seedPath, - purpose = Gen.oneOf(HDPurposes.all).sample.get, - network = Gen.oneOf(Networks.knownNetworks).sample.get) - } - def withInitializedKeyManager( - kmParams: KeyManagerParams = createKeyManagerParams(), - entropy: BitVector = MnemonicCode.getEntropy256Bits): BIP39KeyManager = { + kmParams: KeyManagerParams = KeyManagerTestUtil.createKeyManagerParams(), + entropy: BitVector = MnemonicCode.getEntropy256Bits, + bip39PasswordOpt: Option[String] = KeyManagerTestUtil.bip39PasswordOpt): BIP39KeyManager = { val kmResult = BIP39KeyManager.initializeWithEntropy( entropy = entropy, + bip39PasswordOpt = bip39PasswordOpt, kmParams = kmParams ) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CryptoGenerators.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CryptoGenerators.scala index e4eee093e9bc..4b9fc4346d11 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CryptoGenerators.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CryptoGenerators.scala @@ -99,6 +99,13 @@ sealed abstract class CryptoGenerators { code <- mnemonicCode } yield BIP39Seed.fromMnemonic(code) + /** Generates a password that can be used with bip39 + * @see https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#From_mnemonic_to_seed + */ + def bip39Password: Gen[String] = { + Gen.asciiStr + } + /** * Generates a valid BIP39 seed from * an mnemonic with a random password @@ -106,7 +113,7 @@ sealed abstract class CryptoGenerators { def bip39SeedWithPassword: Gen[BIP39Seed] = for { code <- mnemonicCode - pass <- Gen.alphaStr + pass <- bip39Password } yield BIP39Seed.fromMnemonic(code, pass) def privateKey: Gen[ECPrivateKey] = Gen.delay(ECPrivateKey()) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/wallet/BitcoinSWalletTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/wallet/BitcoinSWalletTest.scala index f075945599ff..7006488be8dc 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/wallet/BitcoinSWalletTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/wallet/BitcoinSWalletTest.scala @@ -10,6 +10,7 @@ import org.bitcoins.core.gcs.BlockFilter import org.bitcoins.core.protocol.BlockStamp import org.bitcoins.core.util.FutureUtil import org.bitcoins.db.AppConfig +import org.bitcoins.keymanager.KeyManagerTestUtil import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.server.BitcoinSAppConfig @@ -115,9 +116,15 @@ trait BitcoinSWalletTest extends BitcoinSFixture with WalletLogger { val withNewConfiguredWallet: Config => OneArgAsyncTest => FutureOutcome = { walletConfig => val km = createNewKeyManager() + val bip39PasswordOpt = KeyManagerTestUtil.bip39PasswordOpt makeDependentFixture( - build = createNewWallet(km, Some(walletConfig), nodeApi, chainQueryApi), - destroy = destroyWallet) + build = createNewWallet(keyManager = km, + bip39PasswordOpt = bip39PasswordOpt, + extraConfig = Some(walletConfig), + nodeApi = nodeApi, + chainQueryApi = chainQueryApi), + destroy = destroyWallet + ) } /** Fixture for an initialized wallet which produce legacy addresses */ @@ -225,9 +232,12 @@ object BitcoinSWalletTest extends WalletLogger { wallet: UnlockedWalletApi, bitcoind: BitcoindRpcClient) - private def createNewKeyManager()( + private def createNewKeyManager( + bip39PasswordOpt: Option[String] = KeyManagerTestUtil.bip39PasswordOpt)( implicit config: BitcoinSAppConfig): BIP39KeyManager = { - val keyManagerE = BIP39KeyManager.initialize(config.walletConf.kmParams) + val keyManagerE = BIP39KeyManager.initialize( + kmParams = config.walletConf.kmParams, + bip39PasswordOpt = bip39PasswordOpt) keyManagerE match { case Right(keyManager) => keyManager case Left(err) => @@ -243,6 +253,7 @@ object BitcoinSWalletTest extends WalletLogger { */ private def createNewWallet( keyManager: BIP39KeyManager, + bip39PasswordOpt: Option[String], extraConfig: Option[Config], nodeApi: NodeApi, chainQueryApi: ChainQueryApi)( @@ -262,7 +273,7 @@ object BitcoinSWalletTest extends WalletLogger { walletConfig.initialize().flatMap { _ => val wallet = Wallet(keyManager, nodeApi, chainQueryApi)(walletConfig, ec) - Wallet.initialize(wallet) + Wallet.initialize(wallet, bip39PasswordOpt) } } @@ -272,9 +283,11 @@ object BitcoinSWalletTest extends WalletLogger { chainQueryApi: ChainQueryApi)( implicit config: BitcoinSAppConfig, ec: ExecutionContext): Future[UnlockedWalletApi] = { - val km = createNewKeyManager() + val bip39PasswordOpt = KeyManagerTestUtil.bip39PasswordOpt + val km = createNewKeyManager(bip39PasswordOpt = bip39PasswordOpt) createNewWallet( keyManager = km, + bip39PasswordOpt = bip39PasswordOpt, extraConfig = None, nodeApi = nodeApi, chainQueryApi = chainQueryApi)(config, ec)() // get the standard config diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/TrezorAddressTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/TrezorAddressTest.scala index 4575d00d2fd1..43a7cb0d78df 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/TrezorAddressTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/TrezorAddressTest.scala @@ -1,33 +1,17 @@ package org.bitcoins.wallet +import com.typesafe.config.{Config, ConfigFactory} +import org.bitcoins.rpc.serializers.JsonSerializers._ import org.bitcoins.core.crypto.{ExtPublicKey, MnemonicCode} - -import play.api.libs.json.JsValue -import play.api.libs.json.Json -import org.bitcoins.core.hd.HDCoinType -import org.bitcoins.core.hd.HDPurpose -import org.bitcoins.core.hd.HDPath +import org.bitcoins.core.hd._ import org.bitcoins.core.protocol.BitcoinAddress -import org.bitcoins.rpc.serializers.JsonSerializers._ -import play.api.libs.json.Reads -import play.api.libs.json.JsResult -import play.api.libs.json.JsError -import play.api.libs.json.JsSuccess -import org.bitcoins.core.hd.HDChainType -import org.bitcoins.core.hd.HDPurposes -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory - import org.bitcoins.core.util.FutureUtil import org.bitcoins.keymanager.KeyManagerParams import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.testkit.BitcoinSTestAppConfig import org.bitcoins.testkit.fixtures.EmptyFixture import org.bitcoins.testkit.wallet.BitcoinSWalletTest -import org.bitcoins.testkit.wallet.BitcoinSWalletTest.{ - MockChainQueryApi, - MockNodeApi -} +import org.bitcoins.testkit.wallet.BitcoinSWalletTest.{MockChainQueryApi, MockNodeApi} import org.bitcoins.wallet.config.WalletAppConfig import org.bitcoins.wallet.models.{AccountDb, AddressDb} import org.scalatest.compatible.Assertion @@ -150,17 +134,17 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture { ConfigFactory.parseString(confStr) } - private def getWallet(config: WalletAppConfig)( - implicit ec: ExecutionContext): Future[Wallet] = { - val kmE = - BIP39KeyManager.initializeWithEntropy(mnemonic.toEntropy, config.kmParams) + private def getWallet(config: WalletAppConfig)(implicit ec: ExecutionContext): Future[Wallet] = { + val bip39PasswordOpt = None + val kmE = BIP39KeyManager.initializeWithEntropy(entropy = mnemonic.toEntropy, + bip39PasswordOpt = bip39PasswordOpt, + kmParams = config.kmParams) kmE match { - case Left(err) => - Future.failed( - new RuntimeException(s"Failed to initialize km with err=${err}")) + case Left(err) => Future.failed(new RuntimeException(s"Failed to initialize km with err=${err}")) case Right(km) => val wallet = Wallet(km, MockNodeApi, MockChainQueryApi)(config, ec) - val walletF = Wallet.initialize(wallet)(config, ec) + val walletF = Wallet.initialize(wallet = wallet, + bip39PasswordOpt = bip39PasswordOpt)(config,ec) walletF } } diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala index be17eb2530cd..9d80f095b808 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala @@ -146,7 +146,8 @@ class WalletUnitTest extends BitcoinSWalletTest { it should "fail to unlock the wallet with a bad password" in { wallet: UnlockedWalletApi => val badpassphrase = AesPassword.fromNonEmptyString("bad") - val errorType = wallet.unlock(badpassphrase) match { + + val errorType = wallet.unlock(badpassphrase, None) match { case Right(_) => fail("Unlocked wallet with bad password!") case Left(err) => err } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index f0226ca38a2a..4cf5e725c094 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -181,7 +181,7 @@ object Wallet extends WalletLogger { } } - def initialize(wallet: Wallet)( + def initialize(wallet: Wallet, bip39PasswordOpt: Option[String])( implicit walletAppConfig: WalletAppConfig, ec: ExecutionContext): Future[Wallet] = { // We want to make sure all level 0 accounts are created, @@ -194,8 +194,11 @@ object Wallet extends WalletLogger { //we need to create key manager params for each purpose //and then initialize a key manager to derive the correct xpub val kmParams = wallet.keyManager.kmParams.copy(purpose = purpose) - val kmE = - BIP39KeyManager.fromParams(kmParams, BIP39KeyManager.badPassphrase) + val kmE = { + BIP39KeyManager.fromParams(kmParams = kmParams, + password = BIP39KeyManager.badPassphrase, + bip39PasswordOpt = bip39PasswordOpt) + } kmE match { case Right(km) => createRootAccount(wallet = wallet, keyManager = km) case Left(err) => diff --git a/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala b/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala index bc19878c9e71..1a1f7d731779 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala @@ -184,13 +184,15 @@ trait LockedWalletApi extends WalletApi { * Unlocks the wallet with the provided passphrase, * making it possible to send transactions. */ - def unlock(passphrase: AesPassword): Either[ + def unlock(passphrase: AesPassword, bip39PasswordOpt: Option[String]): Either[ KeyManagerUnlockError, UnlockedWalletApi] = { val kmParams = walletConfig.kmParams val unlockedKeyManagerE = - BIP39LockedKeyManager.unlock(passphrase, kmParams) + BIP39LockedKeyManager.unlock(passphrase = passphrase, + bip39PasswordOpt = bip39PasswordOpt, + kmParams = kmParams) unlockedKeyManagerE match { case Right(km) => val w = Wallet(keyManager = km,