mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
Make aesPassword option for wallet config (#2217)
* Make aesPassword option for wallet config * Add to docs * Make AesPassword optional * Small touchups * Fix for oracle server * Fix docs * Increase code coverage
This commit is contained in:
parent
c8a0a0931a
commit
47a38f88db
28 changed files with 586 additions and 215 deletions
|
@ -1,8 +1,6 @@
|
|||
package org.bitcoins.oracle.server
|
||||
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.server.{BitcoinSRunner, Server}
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
@ -19,10 +17,9 @@ class OracleServerMain(override val args: Array[String])
|
|||
|
||||
// TODO need to prompt user for these
|
||||
val bip39PasswordOpt: Option[String] = None
|
||||
val aesPassword: AesPassword = BIP39KeyManager.badPassphrase
|
||||
for {
|
||||
_ <- conf.start()
|
||||
oracle <- conf.initialize(aesPassword, bip39PasswordOpt)
|
||||
oracle <- conf.initialize(bip39PasswordOpt)
|
||||
|
||||
routes = Seq(OracleRoutes(oracle))
|
||||
server = rpcPortOpt match {
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.bitcoins.core.wallet.keymanagement.{
|
|||
KeyManagerInitializeError,
|
||||
KeyManagerParams
|
||||
}
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import scodec.bits.BitVector
|
||||
|
||||
trait KeyManagerCreateApi
|
||||
|
@ -29,9 +30,11 @@ trait BIP39KeyManagerCreateApi[T <: BIP39KeyManagerApi]
|
|||
* $initialize
|
||||
*/
|
||||
final def initialize(
|
||||
aesPasswordOpt: Option[AesPassword],
|
||||
kmParams: KeyManagerParams,
|
||||
bip39PasswordOpt: Option[String]): Either[KeyManagerInitializeError, T] =
|
||||
initializeWithEntropy(entropy = MnemonicCode.getEntropy256Bits,
|
||||
initializeWithEntropy(aesPasswordOpt = aesPasswordOpt,
|
||||
entropy = MnemonicCode.getEntropy256Bits,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
kmParams = kmParams)
|
||||
|
||||
|
@ -39,23 +42,26 @@ trait BIP39KeyManagerCreateApi[T <: BIP39KeyManagerApi]
|
|||
* $initializeWithEnt
|
||||
*/
|
||||
def initializeWithEntropy(
|
||||
aesPasswordOpt: Option[AesPassword],
|
||||
entropy: BitVector,
|
||||
bip39PasswordOpt: Option[String],
|
||||
kmParams: KeyManagerParams): Either[KeyManagerInitializeError, T]
|
||||
|
||||
/**
|
||||
* Helper method to initialize a [[KeyManagerCreate$ KeyManager]] with a [[MnemonicCode MnemonicCode]]
|
||||
* Helper method to initialize a [[KeyManagerApi KeyManager]] with a [[MnemonicCode MnemonicCode]]
|
||||
*
|
||||
* @param mnemonicCode
|
||||
* @param kmParams
|
||||
* @return
|
||||
*/
|
||||
final def initializeWithMnemonic(
|
||||
aesPasswordOpt: Option[AesPassword],
|
||||
mnemonicCode: MnemonicCode,
|
||||
bip39PasswordOpt: Option[String],
|
||||
kmParams: KeyManagerParams): Either[KeyManagerInitializeError, T] = {
|
||||
val entropy = mnemonicCode.toEntropy
|
||||
initializeWithEntropy(entropy = entropy,
|
||||
initializeWithEntropy(aesPasswordOpt = aesPasswordOpt,
|
||||
entropy = entropy,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
kmParams = kmParams)
|
||||
}
|
||||
|
|
|
@ -58,4 +58,18 @@ object BIP39Seed extends Factory[BIP39Seed] {
|
|||
BIP39Seed.fromBytes(ByteVector(encodedBytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a [[https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki BIP32]]
|
||||
* seed from a mnemonic code. An optional password can be supplied.
|
||||
*/
|
||||
def fromMnemonic(
|
||||
mnemonic: MnemonicCode,
|
||||
passwordOpt: Option[String]): BIP39Seed = {
|
||||
passwordOpt match {
|
||||
case Some(pass) =>
|
||||
fromMnemonic(mnemonic, pass)
|
||||
case None =>
|
||||
fromMnemonic(mnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,19 +220,22 @@ object DLCOracle {
|
|||
|
||||
def apply(
|
||||
mnemonicCode: MnemonicCode,
|
||||
password: AesPassword,
|
||||
passwordOpt: Option[AesPassword],
|
||||
bip39PasswordOpt: Option[String] = None)(implicit
|
||||
conf: DLCOracleAppConfig): DLCOracle = {
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val encrypted = decryptedMnemonic.encrypt(password)
|
||||
val toWrite = passwordOpt match {
|
||||
case Some(password) => decryptedMnemonic.encrypt(password)
|
||||
case None => decryptedMnemonic
|
||||
}
|
||||
if (!conf.seedExists()) {
|
||||
WalletStorage.writeMnemonicToDisk(conf.seedPath, encrypted)
|
||||
WalletStorage.writeMnemonicToDisk(conf.seedPath, toWrite)
|
||||
}
|
||||
|
||||
val key =
|
||||
WalletStorage.getPrivateKeyFromDisk(conf.seedPath,
|
||||
SegWitMainNetPriv,
|
||||
password,
|
||||
passwordOpt,
|
||||
bip39PasswordOpt)
|
||||
DLCOracle(key)
|
||||
}
|
||||
|
|
|
@ -72,6 +72,11 @@ case class DLCOracleAppConfig(
|
|||
}
|
||||
}
|
||||
|
||||
lazy val aesPasswordOpt: Option[AesPassword] = {
|
||||
val passOpt = config.getStringOrNone("bitcoin-s.oracle.aesPassword")
|
||||
passOpt.flatMap(AesPassword.fromStringOpt)
|
||||
}
|
||||
|
||||
/** Checks if our oracle as a mnemonic seed associated with it */
|
||||
def seedExists(): Boolean = {
|
||||
WalletStorage.seedExists(seedPath)
|
||||
|
@ -90,21 +95,23 @@ case class DLCOracleAppConfig(
|
|||
start().map(_ => oracle)
|
||||
}
|
||||
|
||||
def initialize(
|
||||
password: AesPassword,
|
||||
bip39PasswordOpt: Option[String] = None): Future[DLCOracle] = {
|
||||
def initialize(bip39PasswordOpt: Option[String] = None): Future[DLCOracle] = {
|
||||
if (!seedExists()) {
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val encrypted = decryptedMnemonic.encrypt(password)
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, encrypted)
|
||||
val toWrite = aesPasswordOpt match {
|
||||
case Some(password) => decryptedMnemonic.encrypt(password)
|
||||
case None => decryptedMnemonic
|
||||
}
|
||||
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, toWrite)
|
||||
}
|
||||
|
||||
val key =
|
||||
WalletStorage.getPrivateKeyFromDisk(seedPath,
|
||||
SegWitMainNetPriv,
|
||||
password,
|
||||
aesPasswordOpt,
|
||||
bip39PasswordOpt)
|
||||
val oracle = DLCOracle(key)(this)
|
||||
initialize(oracle)
|
||||
|
|
|
@ -8,7 +8,7 @@ import akka.actor.ActorSystem
|
|||
import org.bitcoins.core.api._
|
||||
import org.bitcoins.core.api.chain.ChainQueryApi
|
||||
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.core.gcs.{FilterType, GolombFilter}
|
||||
import org.bitcoins.core.protocol.BlockStamp
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
|
@ -83,7 +83,8 @@ val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
|
|||
val nodeApi = BitcoinSWalletTest.MockNodeApi
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(kmParams = walletConf.kmParams,
|
||||
val keyManagerE = BIP39KeyManager.initialize(aesPasswordOpt = Some(AesPassword.fromString("password")),
|
||||
kmParams = walletConf.kmParams,
|
||||
bip39PasswordOpt = None)
|
||||
|
||||
val keyManager = keyManagerE match {
|
||||
|
|
|
@ -141,6 +141,9 @@ bitcoin-s {
|
|||
# You can optionally set a BIP 39 password
|
||||
# bip39password = "changeMe"
|
||||
|
||||
# Password that your seed is encrypted in
|
||||
aespassword = changeMe
|
||||
|
||||
defaultAccountType = legacy # legacy, segwit, nested-segwit
|
||||
|
||||
bloomFalsePositiveRate = 0.0001 # percentage
|
||||
|
|
|
@ -66,6 +66,8 @@ Now we can construct a native segwit key manager for the regtest network!
|
|||
```scala mdoc:invisible
|
||||
import java.time.Instant
|
||||
|
||||
import org.bitcoins.crypto._
|
||||
|
||||
import org.bitcoins.core.crypto._
|
||||
|
||||
import org.bitcoins.core.config._
|
||||
|
@ -96,7 +98,9 @@ val network = RegTest
|
|||
|
||||
val kmParams = KeyManagerParams(seedPath, purpose, network)
|
||||
|
||||
val km = BIP39KeyManager.initializeWithMnemonic(mnemonic, None, kmParams)
|
||||
val aesPasswordOpt = Some(AesPassword.fromString("password"))
|
||||
|
||||
val km = BIP39KeyManager.initializeWithMnemonic(aesPasswordOpt, mnemonic, None, kmParams)
|
||||
|
||||
val rootXPub = km.right.get.getRootXPub
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ title: Node API
|
|||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.core.api._
|
||||
import org.bitcoins.core.api.node._
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.wallet.fee._
|
||||
|
@ -60,9 +60,11 @@ implicit val walletConf: WalletAppConfig =
|
|||
// and a ChainApi
|
||||
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
|
||||
val chainApi = BitcoinSWalletTest.MockChainQueryApi
|
||||
val aesPasswordOpt = Some(AesPassword.fromString("password"))
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(kmParams = walletConf.kmParams,
|
||||
val keyManagerE = BIP39KeyManager.initialize(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams = walletConf.kmParams,
|
||||
bip39PasswordOpt = None)
|
||||
|
||||
val keyManager = keyManagerE match {
|
||||
|
|
|
@ -24,6 +24,7 @@ Here is an example of constructing a wallet and registering a callback, so you c
|
|||
```scala mdoc:invisible
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.core.api._
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.wallet.fee._
|
||||
import org.bitcoins.feeprovider._
|
||||
|
@ -48,9 +49,11 @@ implicit val walletConf: WalletAppConfig =
|
|||
// let's use a helper method to get a v19 bitcoind
|
||||
// and a ChainApi
|
||||
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
|
||||
val aesPasswordOpt = Some(AesPassword.fromString("password"))
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(kmParams = walletConf.kmParams,
|
||||
val keyManagerE = BIP39KeyManager.initialize(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams = walletConf.kmParams,
|
||||
bip39PasswordOpt = None)
|
||||
|
||||
val keyManager = keyManagerE match {
|
||||
|
|
|
@ -132,10 +132,11 @@ val syncF: Future[ChainApi] = configF.flatMap { _ =>
|
|||
}
|
||||
|
||||
//initialize our key manager, where we store our keys
|
||||
val aesPasswordOpt = Some(AesPassword.fromString("password"))
|
||||
//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 {
|
||||
val keyManager = BIP39KeyManager.initialize(aesPasswordOpt, walletConfig.kmParams, bip39PasswordOpt).getOrElse {
|
||||
throw new RuntimeException(s"Failed to initalize key manager")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
package org.bitcoins.keymanager
|
||||
|
||||
import java.nio.file.{Files, Path}
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
import org.bitcoins.core.crypto.BIP39Seed
|
||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||
import org.bitcoins.core.util.TimeUtil
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.keymanager.ReadMnemonicError.{
|
||||
DecryptionError,
|
||||
JsonParsingError
|
||||
}
|
||||
import org.bitcoins.keymanager.ReadMnemonicError._
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.testkit.Implicits._
|
||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.scalatest.{BeforeAndAfterEach, FutureOutcome}
|
||||
import ujson.Value.InvalidData
|
||||
|
||||
class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
||||
|
||||
|
@ -32,21 +27,24 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
|
||||
behavior of "WalletStorage"
|
||||
|
||||
val passphrase = AesPassword.fromNonEmptyString("this_is_secret")
|
||||
val badPassphrase = AesPassword.fromNonEmptyString("this_is_also_secret")
|
||||
val passphrase: Some[AesPassword] = Some(
|
||||
AesPassword.fromNonEmptyString("this_is_secret"))
|
||||
|
||||
val badPassphrase: Some[AesPassword] = Some(
|
||||
AesPassword.fromNonEmptyString("this_is_also_secret"))
|
||||
|
||||
def getAndWriteMnemonic(walletConf: WalletAppConfig): DecryptedMnemonic = {
|
||||
val mnemonicCode = CryptoGenerators.mnemonicCode.sampleSome
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val encrypted =
|
||||
EncryptedMnemonicHelper.encrypt(decryptedMnemonic, passphrase)
|
||||
EncryptedMnemonicHelper.encrypt(decryptedMnemonic, passphrase.get)
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
val _ =
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, encrypted)
|
||||
decryptedMnemonic
|
||||
}
|
||||
|
||||
it must "write and read a mnemonic to disk" in {
|
||||
it must "write and read an encrypted mnemonic to disk" in {
|
||||
walletConf: WalletAppConfig =>
|
||||
assert(!walletConf.seedExists())
|
||||
|
||||
|
@ -68,31 +66,77 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
}
|
||||
}
|
||||
|
||||
it must "read a mnemonic without a creation time" in { walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "iv":"d2aeeda5ab83d43bb0b8fe6416b12009",
|
||||
| "cipherText": "003ad9acd6c3559911d7e2446dc329c869266844fda949d69fce591205ab7a32ddb0aa614b1be5963ecc5b784bb0c1454d5d757b71584d5d990ecadc3d4414b87df50ffc46a54c912f258d5ab094bbeb49f92ef02ab60c92a52b3f205ce91943dc6c21b15bfbc635c17b049a8eec4b0a341c48ea163d5384ebbd69c79ff175823e8fbb0849e5a223e243c81c7f7c5bca62a11b7396",
|
||||
| "salt":"db3a6d3c88f430bf44f4a834d85255ad6b52c187c05e95fac3b427b094298028"
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
it must "write and read an unencrypted mnemonic to disk" in {
|
||||
walletConf: WalletAppConfig =>
|
||||
assert(!walletConf.seedExists())
|
||||
val mnemonicCode = CryptoGenerators.mnemonicCode.sampleSome
|
||||
val writtenMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, writtenMnemonic)
|
||||
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath,
|
||||
BIP39KeyManager.badPassphrase)
|
||||
|
||||
read match {
|
||||
case Right(readMnemonic) =>
|
||||
assert(
|
||||
readMnemonic.creationTime.getEpochSecond == WalletStorage.FIRST_BITCOIN_S_WALLET_TIME)
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
// should have been written by now
|
||||
assert(walletConf.seedExists())
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
read match {
|
||||
case Right(readMnemonic) =>
|
||||
assert(writtenMnemonic.mnemonicCode == readMnemonic.mnemonicCode)
|
||||
// Need to compare using getEpochSecond because when reading an epoch second
|
||||
// it will not include the milliseconds that writtenMnemonic will have
|
||||
assert(
|
||||
writtenMnemonic.creationTime.getEpochSecond == readMnemonic.creationTime.getEpochSecond)
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read a mnemonic with improperly formatted creation time" in {
|
||||
it must "read an encrypted mnemonic without a creation time" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "iv":"d2aeeda5ab83d43bb0b8fe6416b12009",
|
||||
| "cipherText": "003ad9acd6c3559911d7e2446dc329c869266844fda949d69fce591205ab7a32ddb0aa614b1be5963ecc5b784bb0c1454d5d757b71584d5d990ecadc3d4414b87df50ffc46a54c912f258d5ab094bbeb49f92ef02ab60c92a52b3f205ce91943dc6c21b15bfbc635c17b049a8eec4b0a341c48ea163d5384ebbd69c79ff175823e8fbb0849e5a223e243c81c7f7c5bca62a11b7396",
|
||||
| "salt":"db3a6d3c88f430bf44f4a834d85255ad6b52c187c05e95fac3b427b094298028"
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(
|
||||
seedPath,
|
||||
Some(BIP39KeyManager.badPassphrase))
|
||||
|
||||
read match {
|
||||
case Right(readMnemonic) =>
|
||||
assert(
|
||||
readMnemonic.creationTime.getEpochSecond == WalletStorage.FIRST_BITCOIN_S_WALLET_TIME)
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "read an unencrypted mnemonic without a creation time" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "mnemonicSeed":["stage","boring","net","gather","radar","radio","arrest","eye","ask","risk","girl","country"]
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read = WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
|
||||
read match {
|
||||
case Right(readMnemonic) =>
|
||||
assert(
|
||||
readMnemonic.creationTime.getEpochSecond == WalletStorage.FIRST_BITCOIN_S_WALLET_TIME)
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read an encrypted mnemonic with improperly formatted creation time" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
|
@ -106,47 +150,93 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
assertThrows[InvalidData] {
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath,
|
||||
BIP39KeyManager.badPassphrase)
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, passphrase)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read a mnemonic with bad password" in { walletConf =>
|
||||
val _ = getAndWriteMnemonic(walletConf)
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
val read = WalletStorage.decryptMnemonicFromDisk(seedPath, badPassphrase)
|
||||
|
||||
read match {
|
||||
case Right(_) =>
|
||||
fail("Wrote and read with different passwords")
|
||||
case Left(DecryptionError) => succeed
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read a mnemonic that has bad JSON in it" in { walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "iv":"ba7722683dad8067df8d069ee04530cc",
|
||||
| "cipherText":,
|
||||
| "salt":"2b7e7d718139518070a87fbbda03ea33cdcda83b555020e9344774e6e7d08af2"
|
||||
| }
|
||||
it must "fail to read an unencrypted mnemonic with improperly formatted creation time" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "mnemonicSeed":["stage","boring","net","gather","radar","radio","arrest","eye","ask","risk","girl","country"],
|
||||
| "creationTime":"not a number"
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, passphrase)
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString())
|
||||
}
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read a mnemonic that has missing a JSON field" in {
|
||||
it must "fail to read an encrypted mnemonic with bad aes password" in {
|
||||
walletConf =>
|
||||
val _ = getAndWriteMnemonic(walletConf)
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
val read = WalletStorage.decryptMnemonicFromDisk(seedPath, badPassphrase)
|
||||
|
||||
read match {
|
||||
case Right(_) =>
|
||||
fail("Wrote and read with different passwords")
|
||||
case Left(DecryptionError) => succeed
|
||||
case Left(err) => fail(err.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read an encrypted mnemonic that has bad JSON in it" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "iv":"ba7722683dad8067df8d069ee04530cc",
|
||||
| "cipherText":,
|
||||
| "salt":"2b7e7d718139518070a87fbbda03ea33cdcda83b555020e9344774e6e7d08af2"
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, passphrase)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read an unencrypted mnemonic that has bad JSON in it" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "mnemonicSeed":,
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read an encrypted mnemonic that has missing a JSON field" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
|
@ -158,12 +248,35 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
assertThrows[NoSuchElementException] {
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, passphrase)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read a mnemonic not in hex" in { walletConf =>
|
||||
it must "fail to read an unencrypted mnemonic that has missing a JSON field" in {
|
||||
walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
| "creationTime":1601917137
|
||||
| }
|
||||
""".stripMargin
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
Files.write(seedPath, badJson.getBytes())
|
||||
|
||||
val read = WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
|
||||
read match {
|
||||
case Left(JsonParsingError(_)) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to read an encrypted mnemonic not in hex" in { walletConf =>
|
||||
val badJson =
|
||||
"""
|
||||
| {
|
||||
|
@ -184,6 +297,19 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
}
|
||||
}
|
||||
|
||||
it must "fail to read an unencrypted seed that doesn't exist" in {
|
||||
walletConf =>
|
||||
require(!walletConf.seedExists())
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
val read =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, None)
|
||||
|
||||
read match {
|
||||
case Left(NotFoundError) => succeed
|
||||
case res @ (Left(_) | Right(_)) => fail(res.toString)
|
||||
}
|
||||
}
|
||||
|
||||
it must "throw an exception if we attempt to overwrite an existing seed" in {
|
||||
walletConf =>
|
||||
assert(!walletConf.seedExists())
|
||||
|
@ -199,7 +325,7 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
}
|
||||
}
|
||||
|
||||
it must "write and read an ExtPrivateKey from disk" in {
|
||||
it must "write and read an encrypted ExtPrivateKey from disk" in {
|
||||
walletConf: WalletAppConfig =>
|
||||
assert(!walletConf.seedExists())
|
||||
|
||||
|
@ -224,4 +350,43 @@ class WalletStorageTest extends BitcoinSWalletTest with BeforeAndAfterEach {
|
|||
|
||||
assert(read == expected)
|
||||
}
|
||||
|
||||
it must "write and read an unencrypted ExtPrivateKey from disk" in {
|
||||
walletConf: WalletAppConfig =>
|
||||
assert(!walletConf.seedExists())
|
||||
val mnemonicCode = CryptoGenerators.mnemonicCode.sampleSome
|
||||
val writtenMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, writtenMnemonic)
|
||||
|
||||
val password = getBIP39PasswordOpt().getOrElse(BIP39Seed.EMPTY_PASSWORD)
|
||||
val keyVersion = SegWitMainNetPriv
|
||||
|
||||
val expected = BIP39Seed
|
||||
.fromMnemonic(mnemonic = writtenMnemonic.mnemonicCode,
|
||||
password = password)
|
||||
.toExtPrivateKey(keyVersion)
|
||||
.toHardened
|
||||
|
||||
// should have been written by now
|
||||
assert(walletConf.seedExists())
|
||||
val read =
|
||||
WalletStorage.getPrivateKeyFromDisk(seedPath,
|
||||
keyVersion,
|
||||
None,
|
||||
Some(password))
|
||||
|
||||
assert(read == expected)
|
||||
}
|
||||
|
||||
it must "fail to read unencrypted ExtPrivateKey from disk that doesn't exist" in {
|
||||
walletConf: WalletAppConfig =>
|
||||
assert(!walletConf.seedExists())
|
||||
val seedPath = getSeedPath(walletConf)
|
||||
val keyVersion = SegWitMainNetPriv
|
||||
|
||||
assertThrows[RuntimeException](
|
||||
WalletStorage.getPrivateKeyFromDisk(seedPath, keyVersion, None, None))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,21 +46,23 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
it must "initialize the key manager" in {
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
val keyManager = withInitializedKeyManager(entropy = entropy)
|
||||
val aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt
|
||||
val keyManager =
|
||||
withInitializedKeyManager(aesPasswordOpt = aesPasswordOpt,
|
||||
entropy = entropy)
|
||||
val seedPath = keyManager.kmParams.seedPath
|
||||
//verify we wrote the seed
|
||||
assert(WalletStorage.seedExists(seedPath),
|
||||
"KeyManager did not write the seed to disk!")
|
||||
|
||||
val decryptedE =
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath,
|
||||
KeyManagerTestUtil.badPassphrase)
|
||||
WalletStorage.decryptMnemonicFromDisk(seedPath, aesPasswordOpt)
|
||||
|
||||
val mnemonic = decryptedE match {
|
||||
case Right(m) => m
|
||||
case Left(err) =>
|
||||
fail(
|
||||
s"Failed to read mnemonic that was written by key manager with err=${err}")
|
||||
s"Failed to read mnemonic that was written by key manager with err=$err")
|
||||
}
|
||||
|
||||
assert(mnemonic.mnemonicCode.toEntropy == entropy,
|
||||
|
@ -86,7 +88,8 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val directXpub = direct.getRootXPub
|
||||
|
||||
val api = BIP39KeyManager
|
||||
.initializeWithEntropy(entropy = mnemonic.toEntropy,
|
||||
.initializeWithEntropy(aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt,
|
||||
entropy = mnemonic.toEntropy,
|
||||
bip39PasswordOpt = None,
|
||||
kmParams = kmParams)
|
||||
.getOrElse(fail())
|
||||
|
@ -109,7 +112,10 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val directXpub = direct.getRootXPub
|
||||
|
||||
val api = BIP39KeyManager
|
||||
.initializeWithEntropy(mnemonic.toEntropy, Some(bip39Pw), kmParams)
|
||||
.initializeWithEntropy(aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt,
|
||||
mnemonic.toEntropy,
|
||||
Some(bip39Pw),
|
||||
kmParams)
|
||||
.getOrElse(fail())
|
||||
|
||||
val apiXpub = api.getRootXPub
|
||||
|
@ -135,7 +141,7 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val directXpub = direct.getRootXPub
|
||||
val api =
|
||||
BIP39KeyManager
|
||||
.fromParams(kmParams, password, Some(bip39Pw))
|
||||
.fromParams(kmParams, Some(password), Some(bip39Pw))
|
||||
.getOrElse(fail())
|
||||
|
||||
val apiXpub = api.getRootXPub
|
||||
|
@ -156,7 +162,11 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val directXpub = direct.getRootXPub
|
||||
|
||||
val api = BIP39KeyManager
|
||||
.initializeWithMnemonic(mnemonic, Some(bip39Pw), kmParams)
|
||||
.initializeWithMnemonic(aesPasswordOpt =
|
||||
KeyManagerTestUtil.aesPasswordOpt,
|
||||
mnemonic,
|
||||
Some(bip39Pw),
|
||||
kmParams)
|
||||
.getOrElse(fail())
|
||||
|
||||
val apiXpub = api.getRootXPub
|
||||
|
@ -190,8 +200,8 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
it must "return a mnemonic not found if we have not initialized the key manager" in {
|
||||
val kmParams = buildParams()
|
||||
val kmE = BIP39KeyManager.fromParams(kmParams = kmParams,
|
||||
password =
|
||||
BIP39KeyManager.badPassphrase,
|
||||
passwordOpt =
|
||||
Some(BIP39KeyManager.badPassphrase),
|
||||
bip39PasswordOpt = None)
|
||||
|
||||
assert(kmE == Left(ReadMnemonicError.NotFoundError))
|
||||
|
@ -209,6 +219,7 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val badEntropy = BitVector.empty
|
||||
|
||||
val init = BIP39KeyManager.initializeWithEntropy(
|
||||
aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt,
|
||||
entropy = badEntropy,
|
||||
bip39PasswordOpt = KeyManagerTestUtil.bip39PasswordOpt,
|
||||
kmParams = buildParams())
|
||||
|
@ -218,11 +229,13 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
it must "read an existing seed from disk if we call initialize and one already exists" in {
|
||||
val seedPath = KeyManagerTestUtil.tmpSeedPath
|
||||
val aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt
|
||||
val kmParams =
|
||||
keymanagement.KeyManagerParams(seedPath, HDPurposes.SegWit, RegTest)
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
val passwordOpt = Some(KeyManagerTestUtil.bip39Password)
|
||||
val keyManager = withInitializedKeyManager(kmParams = kmParams,
|
||||
val keyManager = withInitializedKeyManager(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams = kmParams,
|
||||
entropy = entropy,
|
||||
bip39PasswordOpt = passwordOpt)
|
||||
|
||||
|
@ -233,7 +246,9 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
//now let's try to initialize again, our xpub should be exactly the same
|
||||
val keyManager2E =
|
||||
BIP39KeyManager.initialize(kmParams, bip39PasswordOpt = passwordOpt)
|
||||
BIP39KeyManager.initialize(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams,
|
||||
bip39PasswordOpt = passwordOpt)
|
||||
keyManager2E match {
|
||||
case Left(_) =>
|
||||
fail(s"Must have been able to intiialize the key manager for test")
|
||||
|
@ -245,11 +260,13 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
it must "fail read an existing seed from disk if it is malformed" in {
|
||||
val seedPath = KeyManagerTestUtil.tmpSeedPath
|
||||
val aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt
|
||||
val kmParams =
|
||||
keymanagement.KeyManagerParams(seedPath, HDPurposes.SegWit, RegTest)
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
val passwordOpt = Some(KeyManagerTestUtil.bip39Password)
|
||||
val keyManager = withInitializedKeyManager(kmParams = kmParams,
|
||||
val keyManager = withInitializedKeyManager(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams = kmParams,
|
||||
entropy = entropy,
|
||||
bip39PasswordOpt = passwordOpt)
|
||||
|
||||
|
@ -261,7 +278,9 @@ class BIP39KeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
//now let's try to initialize again, it should fail with a JsonParsingError
|
||||
val keyManager2E =
|
||||
BIP39KeyManager.initialize(kmParams, bip39PasswordOpt = passwordOpt)
|
||||
BIP39KeyManager.initialize(aesPasswordOpt,
|
||||
kmParams,
|
||||
bip39PasswordOpt = passwordOpt)
|
||||
keyManager2E match {
|
||||
case Left(InitializeKeyManagerError.FailedToReadWrittenSeed(unlockErr)) =>
|
||||
unlockErr match {
|
||||
|
|
|
@ -13,10 +13,12 @@ class BIP39LockedKeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
it must "be able to read a locked mnemonic from disk" in {
|
||||
val bip39PwOpt = KeyManagerTestUtil.bip39PasswordOpt
|
||||
val km = withInitializedKeyManager(bip39PasswordOpt = bip39PwOpt)
|
||||
val aesPasswordOpt = KeyManagerTestUtil.aesPasswordOpt
|
||||
val km = withInitializedKeyManager(aesPasswordOpt = aesPasswordOpt,
|
||||
bip39PasswordOpt = bip39PwOpt)
|
||||
|
||||
val unlockedE =
|
||||
BIP39LockedKeyManager.unlock(KeyManagerTestUtil.badPassphrase,
|
||||
BIP39LockedKeyManager.unlock(aesPasswordOpt,
|
||||
bip39PasswordOpt = bip39PwOpt,
|
||||
km.kmParams)
|
||||
|
||||
|
@ -31,8 +33,8 @@ class BIP39LockedKeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
|
||||
it must "fail to read bad json in the seed file" in {
|
||||
val km = withInitializedKeyManager()
|
||||
val badPassword = AesPassword.fromString("other bad password")
|
||||
val unlockedE = BIP39LockedKeyManager.unlock(passphrase = badPassword,
|
||||
val badPassword = Some(AesPassword.fromString("other bad password"))
|
||||
val unlockedE = BIP39LockedKeyManager.unlock(passphraseOpt = badPassword,
|
||||
bip39PasswordOpt = None,
|
||||
kmParams = km.kmParams)
|
||||
|
||||
|
@ -49,7 +51,7 @@ class BIP39LockedKeyManagerApiTest extends KeyManagerApiUnitTest {
|
|||
val km = withInitializedKeyManager()
|
||||
|
||||
val badPath = km.kmParams.copy(seedPath = badSeedPath)
|
||||
val badPassword = AesPassword.fromString("other bad password")
|
||||
val badPassword = Some(AesPassword.fromString("other bad password"))
|
||||
val unlockedE = BIP39LockedKeyManager.unlock(badPassword, None, badPath)
|
||||
|
||||
unlockedE match {
|
||||
|
|
|
@ -9,9 +9,12 @@ import scodec.bits.ByteVector
|
|||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
case class DecryptedMnemonic(
|
||||
mnemonicCode: MnemonicCode,
|
||||
creationTime: Instant) {
|
||||
sealed trait MnemonicState {
|
||||
def creationTime: Instant
|
||||
}
|
||||
|
||||
case class DecryptedMnemonic(mnemonicCode: MnemonicCode, creationTime: Instant)
|
||||
extends MnemonicState {
|
||||
|
||||
def encrypt(password: AesPassword): EncryptedMnemonic =
|
||||
EncryptedMnemonicHelper.encrypt(this, password)
|
||||
|
@ -20,7 +23,8 @@ case class DecryptedMnemonic(
|
|||
case class EncryptedMnemonic(
|
||||
value: AesEncryptedData,
|
||||
salt: AesSalt,
|
||||
creationTime: Instant) {
|
||||
creationTime: Instant)
|
||||
extends MnemonicState {
|
||||
|
||||
def toMnemonic(password: AesPassword): Try[MnemonicCode] = {
|
||||
val key = password.toKey(salt)
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.bitcoins.core.crypto._
|
|||
import org.bitcoins.crypto.{AesEncryptedData, AesIV, AesPassword, AesSalt}
|
||||
import org.slf4j.LoggerFactory
|
||||
import scodec.bits.ByteVector
|
||||
import ujson.{Obj, Value}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
|
@ -35,6 +36,23 @@ object WalletStorage {
|
|||
val CIPHER_TEXT = "cipherText"
|
||||
val SALT = "salt"
|
||||
val CREATION_TIME = "creationTime"
|
||||
val MNEMONIC_SEED = "mnemonicSeed"
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the mnemonic to disk.
|
||||
* If we encounter a file in the place we're about
|
||||
* to write to, we move it to a backup location
|
||||
* with the current epoch timestamp as part of
|
||||
* the file name.
|
||||
*/
|
||||
def writeMnemonicToDisk(seedPath: Path, mnemonic: MnemonicState): Path = {
|
||||
mnemonic match {
|
||||
case decryptedMnemonic: DecryptedMnemonic =>
|
||||
writeMnemonicToDisk(seedPath, decryptedMnemonic)
|
||||
case encryptedMnemonic: EncryptedMnemonic =>
|
||||
writeMnemonicToDisk(seedPath, encryptedMnemonic)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,33 +75,64 @@ object WalletStorage {
|
|||
)
|
||||
}
|
||||
|
||||
writeMnemonicJsonToDisk(seedPath, jsObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the unencrypted mnemonic to disk.
|
||||
* If we encounter a file in the place we're about
|
||||
* to write to, we move it to a backup location
|
||||
* with the current epoch timestamp as part of
|
||||
* the file name.
|
||||
*/
|
||||
def writeMnemonicToDisk(seedPath: Path, mnemonic: DecryptedMnemonic): Path = {
|
||||
val mnemonicCode = mnemonic.mnemonicCode
|
||||
val jsObject = {
|
||||
import MnemonicJsonKeys._
|
||||
ujson.Obj(
|
||||
MNEMONIC_SEED -> mnemonicCode.toVector,
|
||||
CREATION_TIME -> ujson.Num(
|
||||
mnemonic.creationTime.getEpochSecond.toDouble)
|
||||
)
|
||||
}
|
||||
|
||||
writeMnemonicJsonToDisk(seedPath, jsObject)
|
||||
}
|
||||
|
||||
private def writeMnemonicJsonToDisk(seedPath: Path, jsObject: Obj): Path = {
|
||||
logger.info(s"Writing mnemonic to $seedPath")
|
||||
|
||||
val writtenJs = ujson.write(jsObject)
|
||||
|
||||
def writeJsToDisk() = {
|
||||
def writeJsToDisk(): Path = {
|
||||
val writtenPath = Files.write(seedPath, writtenJs.getBytes())
|
||||
logger.trace(s"Wrote encrypted mnemonic to $seedPath")
|
||||
|
||||
writtenPath
|
||||
}
|
||||
|
||||
//check to see if a mnemonic exists already...
|
||||
val foundMnemonicOpt: Option[EncryptedMnemonic] =
|
||||
readEncryptedMnemonicFromDisk(seedPath) match {
|
||||
case CompatLeft(_) =>
|
||||
None
|
||||
case CompatRight(mnemonic) => Some(mnemonic)
|
||||
}
|
||||
// Check to see if a mnemonic exists already
|
||||
val hasMnemonic: Boolean = Files.isRegularFile(seedPath)
|
||||
|
||||
foundMnemonicOpt match {
|
||||
case None =>
|
||||
logger.trace(s"$seedPath does not exist")
|
||||
writeJsToDisk()
|
||||
case Some(_) =>
|
||||
logger.info(s"$seedPath already exists")
|
||||
throw new RuntimeException(
|
||||
s"Attempting to overwrite an existing mnemonic seed, this is dangerous!")
|
||||
if (hasMnemonic) {
|
||||
logger.info(s"$seedPath already exists")
|
||||
throw new RuntimeException(
|
||||
s"Attempting to overwrite an existing mnemonic seed, this is dangerous! path: $seedPath")
|
||||
} else {
|
||||
logger.trace(s"$seedPath does not exist")
|
||||
writeJsToDisk()
|
||||
}
|
||||
}
|
||||
|
||||
private def parseCreationTime(json: Value): Long = {
|
||||
Try(json(MnemonicJsonKeys.CREATION_TIME).num.toLong) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(_: NoSuchElementException) =>
|
||||
// If no CREATION_TIME is set, we set date to start of bitcoin-s wallet project
|
||||
// default is Block 555,990 block time on 2018-12-28
|
||||
FIRST_BITCOIN_S_WALLET_TIME
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,23 +171,16 @@ object WalletStorage {
|
|||
ReadMnemonicError,
|
||||
(String, String, String, Long)] = jsonE.flatMap { json =>
|
||||
logger.trace(s"Read encrypted mnemonic JSON: $json")
|
||||
val creationTimeNum = Try(json(CREATION_TIME).num.toLong) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(err) if err.isInstanceOf[NoSuchElementException] =>
|
||||
// If no CREATION_TIME is set, we set date to start of bitcoin-s wallet project
|
||||
// default is Block 555,990 block time on 2018-12-28
|
||||
FIRST_BITCOIN_S_WALLET_TIME
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
Try {
|
||||
val creationTimeNum = parseCreationTime(json)
|
||||
val ivString = json(IV).str
|
||||
val cipherTextString = json(CIPHER_TEXT).str
|
||||
val rawSaltString = json(SALT).str
|
||||
(ivString, cipherTextString, rawSaltString, creationTimeNum)
|
||||
} match {
|
||||
case Success(value) => CompatRight(value)
|
||||
case Failure(exception) => throw exception
|
||||
case Success(value) => CompatRight(value)
|
||||
case Failure(exception) =>
|
||||
CompatLeft(JsonParsingError(exception.getMessage))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,28 +208,94 @@ object WalletStorage {
|
|||
encryptedEither
|
||||
}
|
||||
|
||||
/** Reads the raw unencrypted mnemonic from disk */
|
||||
private def readUnencryptedMnemonicFromDisk(
|
||||
seedPath: Path): CompatEither[ReadMnemonicError, DecryptedMnemonic] = {
|
||||
|
||||
val jsonE: CompatEither[ReadMnemonicError, ujson.Value] = {
|
||||
if (Files.isRegularFile(seedPath)) {
|
||||
val rawJson = Files.readAllLines(seedPath).asScala.mkString("\n")
|
||||
logger.debug(s"Read raw mnemonic from $seedPath")
|
||||
|
||||
Try(ujson.read(rawJson)) match {
|
||||
case Failure(ujson.ParseException(clue, _, _, _)) =>
|
||||
CompatLeft(ReadMnemonicError.JsonParsingError(clue))
|
||||
case Failure(exception) => throw exception
|
||||
case Success(value) =>
|
||||
logger.debug(s"Parsed $seedPath into valid json")
|
||||
CompatRight(value)
|
||||
}
|
||||
} else {
|
||||
logger.error(s"Mnemonic not found at $seedPath")
|
||||
CompatLeft(ReadMnemonicError.NotFoundError)
|
||||
}
|
||||
}
|
||||
|
||||
import MnemonicJsonKeys._
|
||||
import ReadMnemonicError._
|
||||
|
||||
val readJsonTupleEither: CompatEither[
|
||||
ReadMnemonicError,
|
||||
(Vector[String], Long)] = jsonE.flatMap { json =>
|
||||
logger.trace(s"Read mnemonic JSON: Masked(json)")
|
||||
Try {
|
||||
val creationTimeNum = parseCreationTime(json)
|
||||
val words = json(MNEMONIC_SEED).arr.toVector.map(_.str)
|
||||
(words, creationTimeNum)
|
||||
} match {
|
||||
case Success(value) => CompatRight(value)
|
||||
case Failure(exception) =>
|
||||
CompatLeft(JsonParsingError(exception.getMessage))
|
||||
}
|
||||
}
|
||||
|
||||
readJsonTupleEither.flatMap {
|
||||
case (words, rawCreationTime) =>
|
||||
val decryptedMnemonicT = for {
|
||||
mnemonicCodeT <- Try(MnemonicCode.fromWords(words))
|
||||
} yield {
|
||||
logger.debug(s"Parsed contents of $seedPath into a DecryptedMnemonic")
|
||||
DecryptedMnemonic(mnemonicCodeT,
|
||||
Instant.ofEpochSecond(rawCreationTime))
|
||||
}
|
||||
|
||||
val toRight: Try[CompatRight[ReadMnemonicError, DecryptedMnemonic]] =
|
||||
decryptedMnemonicT
|
||||
.map(CompatRight(_))
|
||||
|
||||
toRight.getOrElse(
|
||||
CompatLeft(JsonParsingError("JSON contents was correctly formatted")))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the wallet mnemonic from disk and tries to parse and
|
||||
* decrypt it
|
||||
*/
|
||||
def decryptMnemonicFromDisk(
|
||||
seedPath: Path,
|
||||
passphrase: AesPassword): Either[ReadMnemonicError, DecryptedMnemonic] = {
|
||||
|
||||
val encryptedEither = readEncryptedMnemonicFromDisk(seedPath)
|
||||
|
||||
passphraseOpt: Option[AesPassword]): Either[
|
||||
ReadMnemonicError,
|
||||
DecryptedMnemonic] = {
|
||||
val decryptedEither: CompatEither[ReadMnemonicError, DecryptedMnemonic] =
|
||||
encryptedEither.flatMap { encrypted =>
|
||||
encrypted.toMnemonic(passphrase) match {
|
||||
case Failure(exc) =>
|
||||
logger.error(s"Error when decrypting $encrypted: $exc")
|
||||
CompatLeft(ReadMnemonicError.DecryptionError)
|
||||
case Success(mnemonic) =>
|
||||
logger.debug(s"Decrypted $encrypted successfully")
|
||||
val decryptedMnemonic =
|
||||
DecryptedMnemonic(mnemonic, encrypted.creationTime)
|
||||
CompatRight(decryptedMnemonic)
|
||||
}
|
||||
passphraseOpt match {
|
||||
case Some(passphrase) =>
|
||||
val encryptedEither = readEncryptedMnemonicFromDisk(seedPath)
|
||||
|
||||
encryptedEither.flatMap { encrypted =>
|
||||
encrypted.toMnemonic(passphrase) match {
|
||||
case Failure(exc) =>
|
||||
logger.error(s"Error when decrypting $encrypted: $exc")
|
||||
CompatLeft(ReadMnemonicError.DecryptionError)
|
||||
case Success(mnemonic) =>
|
||||
logger.debug(s"Decrypted $encrypted successfully")
|
||||
val decryptedMnemonic =
|
||||
DecryptedMnemonic(mnemonic, encrypted.creationTime)
|
||||
CompatRight(decryptedMnemonic)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
readUnencryptedMnemonicFromDisk(seedPath)
|
||||
}
|
||||
|
||||
decryptedEither match {
|
||||
|
@ -199,19 +307,13 @@ object WalletStorage {
|
|||
def getPrivateKeyFromDisk(
|
||||
seedPath: Path,
|
||||
privKeyVersion: ExtKeyPrivVersion,
|
||||
passphrase: AesPassword,
|
||||
passphraseOpt: Option[AesPassword],
|
||||
bip39PasswordOpt: Option[String]): ExtPrivateKeyHardened = {
|
||||
val mnemonicCode = decryptMnemonicFromDisk(seedPath, passphrase) match {
|
||||
val mnemonicCode = decryptMnemonicFromDisk(seedPath, passphraseOpt) match {
|
||||
case Left(error) => sys.error(error.toString)
|
||||
case Right(mnemonic) => mnemonic.mnemonicCode
|
||||
}
|
||||
val seed = bip39PasswordOpt match {
|
||||
case Some(pw) =>
|
||||
BIP39Seed.fromMnemonic(mnemonic = mnemonicCode, password = pw)
|
||||
case None =>
|
||||
BIP39Seed.fromMnemonic(mnemonic = mnemonicCode,
|
||||
password = BIP39Seed.EMPTY_PASSWORD)
|
||||
}
|
||||
val seed = BIP39Seed.fromMnemonic(mnemonicCode, bip39PasswordOpt)
|
||||
|
||||
seed.toExtPrivateKey(privKeyVersion).toHardened
|
||||
}
|
||||
|
|
|
@ -40,13 +40,7 @@ case class BIP39KeyManager(
|
|||
extends BIP39KeyManagerApi
|
||||
with KeyManagerLogger {
|
||||
|
||||
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 seed = BIP39Seed.fromMnemonic(mnemonic, bip39PasswordOpt)
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
|
@ -87,17 +81,18 @@ case class BIP39KeyManager(
|
|||
object BIP39KeyManager
|
||||
extends BIP39KeyManagerCreateApi[BIP39KeyManager]
|
||||
with BitcoinSLogger {
|
||||
val badPassphrase = AesPassword.fromString("changeMe")
|
||||
val badPassphrase: AesPassword = AesPassword.fromString("changeMe")
|
||||
|
||||
/** Initializes the mnemonic seed and saves it to file */
|
||||
override def initializeWithEntropy(
|
||||
aesPasswordOpt: Option[AesPassword],
|
||||
entropy: BitVector,
|
||||
bip39PasswordOpt: Option[String],
|
||||
kmParams: KeyManagerParams): Either[
|
||||
KeyManagerInitializeError,
|
||||
BIP39KeyManager] = {
|
||||
val seedPath = kmParams.seedPath
|
||||
logger.info(s"Initializing wallet with seedPath=${seedPath}")
|
||||
logger.info(s"Initializing wallet with seedPath=$seedPath")
|
||||
|
||||
val time = TimeUtil.now
|
||||
|
||||
|
@ -118,24 +113,27 @@ object BIP39KeyManager
|
|||
CompatEither(Left(InitializeKeyManagerError.BadEntropy))
|
||||
}
|
||||
|
||||
val encryptedMnemonicE: CompatEither[
|
||||
val writableMnemonicE: CompatEither[
|
||||
KeyManagerInitializeError,
|
||||
EncryptedMnemonic] =
|
||||
MnemonicState] =
|
||||
mnemonicE.map { mnemonic =>
|
||||
EncryptedMnemonicHelper.encrypt(DecryptedMnemonic(mnemonic, time),
|
||||
badPassphrase)
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonic, time)
|
||||
aesPasswordOpt match {
|
||||
case Some(aesPassword) =>
|
||||
EncryptedMnemonicHelper.encrypt(decryptedMnemonic, aesPassword)
|
||||
case None =>
|
||||
decryptedMnemonic
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
mnemonic <- mnemonicE
|
||||
encrypted <- encryptedMnemonicE
|
||||
_ = {
|
||||
val mnemonicPath =
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, encrypted)
|
||||
logger.info(s"Saved encrypted wallet mnemonic to $mnemonicPath")
|
||||
}
|
||||
|
||||
writable <- writableMnemonicE
|
||||
} yield {
|
||||
val mnemonicPath =
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, writable)
|
||||
logger.info(s"Saved wallet mnemonic to $mnemonicPath")
|
||||
|
||||
BIP39KeyManager(mnemonic = mnemonic,
|
||||
kmParams = kmParams,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
|
@ -146,7 +144,7 @@ object BIP39KeyManager
|
|||
s"Seed file already exists, attempting to initialize form existing seed file=$seedPath.")
|
||||
|
||||
WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath,
|
||||
badPassphrase) match {
|
||||
aesPasswordOpt) match {
|
||||
case Right(mnemonic) =>
|
||||
CompatRight(
|
||||
BIP39KeyManager(mnemonic = mnemonic.mnemonicCode,
|
||||
|
@ -161,7 +159,7 @@ object BIP39KeyManager
|
|||
}
|
||||
|
||||
//verify we can unlock it for a sanity check
|
||||
val unlocked = BIP39LockedKeyManager.unlock(passphrase = badPassphrase,
|
||||
val unlocked = BIP39LockedKeyManager.unlock(passphraseOpt = aesPasswordOpt,
|
||||
bip39PasswordOpt =
|
||||
bip39PasswordOpt,
|
||||
kmParams = kmParams)
|
||||
|
@ -189,7 +187,7 @@ object BIP39KeyManager
|
|||
logger.info(s"Successfully initialized wallet")
|
||||
Right(initSuccess)
|
||||
case CompatLeft(err) =>
|
||||
logger.error(s"Failed to initialize key manager with err=${err}")
|
||||
logger.error(s"Failed to initialize key manager with err=$err")
|
||||
Left(err)
|
||||
}
|
||||
}
|
||||
|
@ -197,12 +195,12 @@ object BIP39KeyManager
|
|||
/** Reads the key manager from disk and decrypts it with the given password */
|
||||
def fromParams(
|
||||
kmParams: KeyManagerParams,
|
||||
password: AesPassword,
|
||||
passwordOpt: Option[AesPassword],
|
||||
bip39PasswordOpt: Option[String]): Either[
|
||||
ReadMnemonicError,
|
||||
BIP39KeyManager] = {
|
||||
val mnemonicCodeE =
|
||||
WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, password)
|
||||
WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, passwordOpt)
|
||||
|
||||
mnemonicCodeE match {
|
||||
case Right(mnemonic) =>
|
||||
|
|
|
@ -21,14 +21,14 @@ object BIP39LockedKeyManager extends BitcoinSLogger {
|
|||
* @param kmParams parameters needed to create the key manager
|
||||
*/
|
||||
def unlock(
|
||||
passphrase: AesPassword,
|
||||
passphraseOpt: Option[AesPassword],
|
||||
bip39PasswordOpt: Option[String],
|
||||
kmParams: KeyManagerParams): Either[
|
||||
KeyManagerUnlockError,
|
||||
BIP39KeyManager] = {
|
||||
logger.debug(s"Trying to unlock wallet with seedPath=${kmParams.seedPath}")
|
||||
val resultE =
|
||||
WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, passphrase)
|
||||
WalletStorage.decryptMnemonicFromDisk(kmParams.seedPath, passphraseOpt)
|
||||
resultE match {
|
||||
case Right(mnemonic) =>
|
||||
Right(
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.nio.file._
|
|||
import com.typesafe.config._
|
||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
||||
import org.bitcoins.server.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil
|
||||
import org.bitcoins.testkit.util.FileUtil
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
@ -88,9 +89,20 @@ object BitcoinSTestAppConfig {
|
|||
def getDLCOracleWithEmbeddedDbTestConfig(
|
||||
pgUrl: () => Option[String],
|
||||
config: Config*)(implicit ec: ExecutionContext): DLCOracleAppConfig = {
|
||||
val overrideConf = KeyManagerTestUtil.aesPasswordOpt match {
|
||||
case Some(value) =>
|
||||
ConfigFactory.parseString {
|
||||
s"""
|
||||
|bitcoin-s.oracle.aesPassword = $value
|
||||
""".stripMargin
|
||||
}
|
||||
case None =>
|
||||
ConfigFactory.empty()
|
||||
}
|
||||
|
||||
DLCOracleAppConfig(
|
||||
tmpDir(),
|
||||
configWithEmbeddedDb(project = None, pgUrl) +: config: _*)
|
||||
overrideConf +: configWithEmbeddedDb(project = None, pgUrl) +: config: _*)
|
||||
}
|
||||
|
||||
sealed trait ProjectType
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.testkit.fixtures
|
||||
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.dlc.oracle.DLCOracleAppConfig
|
||||
import org.bitcoins.dlc.oracle.storage._
|
||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil.bip39PasswordOpt
|
||||
|
@ -25,7 +24,7 @@ trait DLCOracleDAOFixture extends BitcoinSFixture with EmbeddedPg {
|
|||
makeFixture(
|
||||
build = () => {
|
||||
config
|
||||
.initialize(AesPassword.fromString("Ben was here"), bip39PasswordOpt)
|
||||
.initialize(bip39PasswordOpt)
|
||||
.map(oracle =>
|
||||
DLCOracleDAOs(oracle.rValueDAO,
|
||||
oracle.eventDAO,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.testkit.fixtures
|
||||
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.dlc.oracle.DLCOracle
|
||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil.bip39PasswordOpt
|
||||
import org.bitcoins.testkit.util.FileUtil
|
||||
|
@ -17,7 +16,7 @@ trait DLCOracleFixture extends BitcoinSFixture with EmbeddedPg {
|
|||
val builder: () => Future[DLCOracle] = () => {
|
||||
val conf =
|
||||
BitcoinSTestAppConfig.getDLCOracleWithEmbeddedDbTestConfig(pgUrl)
|
||||
conf.initialize(AesPassword.fromString("Ben was here"), bip39PasswordOpt)
|
||||
conf.initialize(bip39PasswordOpt)
|
||||
}
|
||||
|
||||
val destroy: DLCOracle => Future[Unit] = dlcOracle => {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.bitcoins.testkit.keymanager
|
|||
|
||||
import org.bitcoins.core.crypto.MnemonicCode
|
||||
import org.bitcoins.core.wallet.keymanagement.KeyManagerParams
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.BitVector
|
||||
|
@ -9,11 +10,13 @@ import scodec.bits.BitVector
|
|||
trait KeyManagerApiUnitTest extends BitcoinSUnitTest {
|
||||
|
||||
def withInitializedKeyManager(
|
||||
aesPasswordOpt: Option[AesPassword] = KeyManagerTestUtil.aesPasswordOpt,
|
||||
kmParams: KeyManagerParams = KeyManagerTestUtil.createKeyManagerParams(),
|
||||
entropy: BitVector = MnemonicCode.getEntropy256Bits,
|
||||
bip39PasswordOpt: Option[String] =
|
||||
KeyManagerTestUtil.bip39PasswordOpt): BIP39KeyManager = {
|
||||
val kmResult = BIP39KeyManager.initializeWithEntropy(
|
||||
aesPasswordOpt = aesPasswordOpt,
|
||||
entropy = entropy,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
kmParams = kmParams
|
||||
|
|
|
@ -6,8 +6,8 @@ import org.bitcoins.core.config.Networks
|
|||
import org.bitcoins.core.hd.HDPurposes
|
||||
import org.bitcoins.core.wallet.keymanagement.KeyManagerParams
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.keymanager.WalletStorage
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||
import org.scalacheck.Gen
|
||||
|
@ -49,5 +49,7 @@ object KeyManagerTestUtil {
|
|||
else attempt
|
||||
}
|
||||
|
||||
val badPassphrase: AesPassword = BIP39KeyManager.badPassphrase
|
||||
def aesPasswordOpt: Option[AesPassword] = CryptoGenerators.aesPassword.sample
|
||||
|
||||
val badPassphrase: Some[AesPassword] = Some(BIP39KeyManager.badPassphrase)
|
||||
}
|
||||
|
|
|
@ -364,7 +364,8 @@ object BitcoinSWalletTest extends WalletLogger {
|
|||
private def createNewKeyManager(
|
||||
bip39PasswordOpt: Option[String] = KeyManagerTestUtil.bip39PasswordOpt)(
|
||||
implicit config: WalletAppConfig): BIP39KeyManager = {
|
||||
val keyManagerE = BIP39KeyManager.initialize(kmParams = config.kmParams,
|
||||
val keyManagerE = BIP39KeyManager.initialize(config.aesPasswordOpt,
|
||||
kmParams = config.kmParams,
|
||||
bip39PasswordOpt =
|
||||
bip39PasswordOpt)
|
||||
keyManagerE match {
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.bitcoins.feeprovider.ConstantFeeRateProvider
|
|||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.testkit.fixtures.EmptyFixture
|
||||
import org.bitcoins.testkit.keymanager.KeyManagerTestUtil
|
||||
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
||||
import org.bitcoins.testkit.wallet.BitcoinSWalletTest.{
|
||||
MockChainQueryApi,
|
||||
|
@ -147,6 +148,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
|
|||
ec: ExecutionContext): Future[Wallet] = {
|
||||
val bip39PasswordOpt = None
|
||||
val kmE = BIP39KeyManager.initializeWithEntropy(
|
||||
aesPasswordOpt = config.aesPasswordOpt,
|
||||
entropy = mnemonic.toEntropy,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
kmParams = config.kmParams)
|
||||
|
|
|
@ -134,18 +134,28 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
} yield res
|
||||
}
|
||||
|
||||
it should "fail to unlock the wallet with a bad password" in {
|
||||
it should "fail to unlock the wallet with a bad aes password" in {
|
||||
wallet: Wallet =>
|
||||
val badpassphrase = AesPassword.fromNonEmptyString("bad")
|
||||
val badPassphrase = Some(AesPassword.fromNonEmptyString("bad"))
|
||||
|
||||
val errorType = wallet.unlock(badpassphrase, None) match {
|
||||
val errorType = wallet.unlock(badPassphrase, None) match {
|
||||
case Right(_) => fail("Unlocked wallet with bad password!")
|
||||
case Left(err) => err
|
||||
}
|
||||
errorType match {
|
||||
case KeyManagerUnlockError.MnemonicNotFound => fail(MnemonicNotFound)
|
||||
case KeyManagerUnlockError.BadPassword => succeed
|
||||
case KeyManagerUnlockError.JsonParsingError(message) => fail(message)
|
||||
case KeyManagerUnlockError.MnemonicNotFound => fail(MnemonicNotFound)
|
||||
case KeyManagerUnlockError.BadPassword =>
|
||||
// If wallet is unencrypted then we shouldn't get a bad password error
|
||||
wallet.walletConfig.aesPasswordOpt match {
|
||||
case Some(_) => succeed
|
||||
case None => fail()
|
||||
}
|
||||
case KeyManagerUnlockError.JsonParsingError(message) =>
|
||||
// If wallet is encrypted then we shouldn't get a json parsing error
|
||||
wallet.walletConfig.aesPasswordOpt match {
|
||||
case Some(_) => fail(message)
|
||||
case None => succeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -188,13 +188,15 @@ abstract class Wallet
|
|||
}
|
||||
}
|
||||
|
||||
def unlock(passphrase: AesPassword, bip39PasswordOpt: Option[String]): Either[
|
||||
def unlock(
|
||||
passphraseOpt: Option[AesPassword],
|
||||
bip39PasswordOpt: Option[String]): Either[
|
||||
KeyManagerUnlockError,
|
||||
Wallet] = {
|
||||
val kmParams = walletConfig.kmParams
|
||||
|
||||
val unlockedKeyManagerE =
|
||||
BIP39LockedKeyManager.unlock(passphrase = passphrase,
|
||||
BIP39LockedKeyManager.unlock(passphraseOpt = passphraseOpt,
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
kmParams = kmParams)
|
||||
unlockedKeyManagerE match {
|
||||
|
@ -732,10 +734,11 @@ object Wallet extends WalletLogger {
|
|||
def initialize(wallet: Wallet, bip39PasswordOpt: Option[String])(implicit
|
||||
walletAppConfig: WalletAppConfig,
|
||||
ec: ExecutionContext): Future[Wallet] = {
|
||||
val passwordOpt = walletAppConfig.aesPasswordOpt
|
||||
// We want to make sure all level 0 accounts are created,
|
||||
// so the user can change the default account kind later
|
||||
// and still have their wallet work
|
||||
def createAccountFutures =
|
||||
def createAccountFutures: Future[Vector[Future[AccountDb]]] =
|
||||
for {
|
||||
_ <- walletAppConfig.start()
|
||||
accounts = HDPurposes.singleSigPurposes.map { purpose =>
|
||||
|
@ -744,7 +747,7 @@ object Wallet extends WalletLogger {
|
|||
val kmParams = wallet.keyManager.kmParams.copy(purpose = purpose)
|
||||
val kmE = {
|
||||
BIP39KeyManager.fromParams(kmParams = kmParams,
|
||||
password = BIP39KeyManager.badPassphrase,
|
||||
passwordOpt = passwordOpt,
|
||||
bip39PasswordOpt = bip39PasswordOpt)
|
||||
}
|
||||
kmE match {
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.bitcoins.core.wallet.keymanagement.{
|
|||
KeyManagerInitializeError,
|
||||
KeyManagerParams
|
||||
}
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.db.DatabaseDriver.{PostgreSQL, SQLite}
|
||||
import org.bitcoins.db._
|
||||
import org.bitcoins.keymanager.WalletStorage
|
||||
|
@ -108,6 +109,11 @@ case class WalletAppConfig(
|
|||
config.getStringOrNone("bitcoin-s.wallet.bip39password")
|
||||
}
|
||||
|
||||
lazy val aesPasswordOpt: Option[AesPassword] = {
|
||||
val passOpt = config.getStringOrNone("bitcoin-s.wallet.aesPassword")
|
||||
passOpt.flatMap(AesPassword.fromStringOpt)
|
||||
}
|
||||
|
||||
override def start(): Future[Unit] = {
|
||||
for {
|
||||
_ <- super.start()
|
||||
|
@ -218,11 +224,14 @@ object WalletAppConfig
|
|||
walletConf: WalletAppConfig,
|
||||
ec: ExecutionContext): Future[Wallet] = {
|
||||
walletConf.hasWallet().flatMap { walletExists =>
|
||||
val aesPasswordOpt = walletConf.aesPasswordOpt
|
||||
val bip39PasswordOpt = walletConf.bip39PasswordOpt
|
||||
|
||||
if (walletExists) {
|
||||
logger.info(s"Using pre-existing wallet")
|
||||
// TODO change me when we implement proper password handling
|
||||
BIP39LockedKeyManager.unlock(BIP39KeyManager.badPassphrase,
|
||||
walletConf.bip39PasswordOpt,
|
||||
BIP39LockedKeyManager.unlock(aesPasswordOpt,
|
||||
bip39PasswordOpt,
|
||||
walletConf.kmParams) match {
|
||||
case Right(km) =>
|
||||
val wallet =
|
||||
|
@ -233,9 +242,9 @@ object WalletAppConfig
|
|||
}
|
||||
} else {
|
||||
logger.info(s"Initializing key manager")
|
||||
val bip39PasswordOpt = walletConf.bip39PasswordOpt
|
||||
val keyManagerE: Either[KeyManagerInitializeError, BIP39KeyManager] =
|
||||
BIP39KeyManager.initialize(kmParams = walletConf.kmParams,
|
||||
BIP39KeyManager.initialize(aesPasswordOpt = aesPasswordOpt,
|
||||
kmParams = walletConf.kmParams,
|
||||
bip39PasswordOpt = bip39PasswordOpt)
|
||||
|
||||
val keyManager = keyManagerE match {
|
||||
|
|
Loading…
Add table
Reference in a new issue