Update wallet project to work new AES changes

This commit is contained in:
Torkel Rogstad 2019-06-24 17:25:11 +02:00
parent d58f3dc715
commit f93b8cadbe
7 changed files with 58 additions and 69 deletions

View file

@ -18,8 +18,10 @@ import scala.io.Source
*/
sealed abstract class MnemonicCode {
require(
MnemonicCode.VALID_LENGTHS.contains(words.length),
s"Number of words must be one of the following: ${MnemonicCode.VALID_LENGTHS.mkString(", ")} "
MnemonicCode.VALID_LENGTHS.contains(words.length), {
val validLengths = MnemonicCode.VALID_LENGTHS.mkString(", ")
s"Number of words must be one of the following: $validLengths, got: ${words.length} "
}
)
require({

View file

@ -159,11 +159,6 @@ abstract class DbCommonsColumnMappers {
MappedColumnType
.base[ScriptType, String](_.toString, ScriptType.fromStringExn)
implicit val aesSaltMapper: BaseColumnType[AesSalt] =
MappedColumnType.base[AesSalt, String](
_.value.toHex,
hex => AesSalt(ByteVector.fromValidHex(hex)))
}
object DbCommonsColumnMappers extends DbCommonsColumnMappers

View file

@ -10,17 +10,14 @@ class EncryptedMnemonicTest extends BitcoinSUnitTest {
behavior of "EncryptedMnemonic"
it must "fail to decrypt with a bad password" in {
val password = AesPassword("good")
val badPassword = AesPassword("bad")
val password = AesPassword.fromNonEmptyString("good")
val badPassword = AesPassword.fromNonEmptyString("bad")
def getMnemonic(): MnemonicCode =
CryptoGenerators.mnemonicCode.sample.getOrElse(getMnemonic())
val mnemonic = getMnemonic()
val encrypted = EncryptedMnemonicHelper.encrypt(mnemonic, password) match {
case Success(value) => value
case Failure(exception) => fail(exception)
}
val encrypted = EncryptedMnemonicHelper.encrypt(mnemonic, password)
val decrypted = encrypted.toMnemonic(badPassword)
@ -31,10 +28,7 @@ class EncryptedMnemonicTest extends BitcoinSUnitTest {
it must "have encryption/decryption symmetry" in {
forAll(CryptoGenerators.mnemonicCode, CryptoGenerators.aesPassword) {
(code, password) =>
val encrypted = EncryptedMnemonicHelper.encrypt(code, password) match {
case Success(e) => e
case Failure(exception) => fail(exception)
}
val encrypted = EncryptedMnemonicHelper.encrypt(code, password)
val decrypted = encrypted.toMnemonic(password) match {
case Success(clear) => clear
case Failure(exc) => fail(exc)

View file

@ -150,7 +150,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
it should "fail to unlock the wallet with a bad password" in {
wallet: UnlockedWalletApi =>
val badpassphrase = AesPassword("bad")
val badpassphrase = AesPassword.fromNonEmptyString("bad")
val locked = wallet.lock()
wallet.unlock(badpassphrase) match {
case MnemonicNotFound => fail(MnemonicNotFound)

View file

@ -2,40 +2,50 @@ package org.bitcoins.wallet
import java.nio.charset.StandardCharsets
import org.bitcoins.core.crypto.{
AesCrypt,
AesEncryptedData,
AesPassword,
MnemonicCode
}
import org.bitcoins.core.crypto.{AesCrypt, AesEncryptedData, MnemonicCode}
import scodec.bits.ByteVector
import scala.util.Try
import org.bitcoins.core.crypto.AesSalt
import org.bitcoins.core.crypto.AesPassword
import org.bitcoins.core.util.BitcoinSLogger
import scala.util.Success
import scala.util.Failure
case class EncryptedMnemonic(value: AesEncryptedData) {
case class EncryptedMnemonic(value: AesEncryptedData, salt: AesSalt)
extends BitcoinSLogger {
import org.bitcoins.core.util.EitherUtil.EitherOps._
def toMnemonic(passphrase: AesPassword): Try[MnemonicCode] = {
AesCrypt.decrypt(value, passphrase).toTry.map { decrypted =>
val wordsStr = new String(decrypted.toArray, StandardCharsets.UTF_8)
val wordsVec = wordsStr.split(" ").toVector
MnemonicCode.fromWords(wordsVec)
def toMnemonic(password: AesPassword): Try[MnemonicCode] = {
val key = password.toKey(salt)
AesCrypt.decrypt(value, key).toTry.flatMap { decrypted =>
decrypted.decodeUtf8 match {
case Left(_) =>
// when failing to decode this to a UTF-8 string
// we assume it's because of a bad password
Failure(ReadMnemonicError.DecryptionError)
case Right(wordsStr) =>
val wordsVec = wordsStr.split(" ").toVector
Success(MnemonicCode.fromWords(wordsVec))
}
}
}
}
object EncryptedMnemonicHelper {
import org.bitcoins.core.util.EitherUtil.EitherOps._
def encrypt(
mnemonicCode: MnemonicCode,
passphrase: AesPassword): Try[EncryptedMnemonic] = {
password: AesPassword): EncryptedMnemonic = {
val wordsStr = mnemonicCode.words.mkString(" ")
val Right(clearText) = ByteVector.encodeUtf8(wordsStr)
AesCrypt
.encrypt(clearText, passphrase)
.toTry
.map(EncryptedMnemonic(_))
val (key, salt) = password.toKey
val encryted = AesCrypt
.encrypt(clearText, key)
EncryptedMnemonic(encryted, salt)
}
}

View file

@ -28,12 +28,7 @@ sealed abstract class Wallet
*/
override def lock(): LockedWalletApi = {
logger.debug(s"Locking wallet")
val encryptedT = EncryptedMnemonicHelper.encrypt(mnemonicCode, passphrase)
val encrypted = encryptedT match {
case Failure(exception) =>
throw new RuntimeException(s"Could not encrypt mnemonic: $exception")
case Success(value) => value
}
val encrypted = EncryptedMnemonicHelper.encrypt(mnemonicCode, passphrase)
WalletStorage.writeMnemonicToDisk(encrypted)
logger.debug("Locked wallet")
@ -120,7 +115,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
WalletImpl(mnemonicCode)
// todo figure out how to handle password part of wallet
val badPassphrase = AesPassword("changeMe")
val badPassphrase = AesPassword.fromNonEmptyString("changeMe")
// todo fix signature
override def initializeWithEntropy(entropy: BitVector)(
@ -143,22 +138,7 @@ object Wallet extends CreateWalletApi with BitcoinSLogger {
}
val encryptedMnemonicE: Either[InitializeWalletError, EncryptedMnemonic] =
mnemonicE.flatMap { mnemonic =>
val encryptedT = EncryptedMnemonicHelper
.encrypt(mnemonic, badPassphrase)
val encryptedE: Either[Throwable, EncryptedMnemonic] =
encryptedT match {
case Failure(exception) => Left(exception)
case Success(value) => Right(value)
}
encryptedE.left
.map { err =>
logger.error(s"Encryption error when encrypting mnemonic: $err")
InitializeWalletError.EncryptionError(err)
}
}
mnemonicE.map { EncryptedMnemonicHelper.encrypt(_, badPassphrase) }
val biasedFinalEither: Either[InitializeWalletError, Future[WalletImpl]] =
for {

View file

@ -14,6 +14,7 @@ import java.nio.file.Paths
import java.nio.file.Path
import scala.util.Try
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.core.crypto.AesIV
// what do we do if seed exists? error if they aren't equal?
object WalletStorage extends BitcoinSLogger {
@ -41,9 +42,9 @@ object WalletStorage extends BitcoinSLogger {
val jsObject = {
import MnemonicJsonKeys._
ujson.Obj(
IV -> encrypted.iv.toHex,
IV -> encrypted.iv.hex,
CIPHER_TEXT -> encrypted.cipherText.toHex,
SALT -> encrypted.salt.value.toHex
SALT -> mnemonic.salt.bytes.toHex
)
}
@ -135,6 +136,7 @@ object WalletStorage extends BitcoinSLogger {
val readJsonTupleEither: Either[
ReadMnemonicError,
(String, String, String)] = jsonE.flatMap { json =>
logger.trace(s"Read encrypted mnemonic JSON: $json")
Try {
val ivString = json(IV).str
val cipherTextString = json(CIPHER_TEXT).str
@ -153,13 +155,12 @@ object WalletStorage extends BitcoinSLogger {
readJsonTupleEither.flatMap {
case (rawIv, rawCipherText, rawSalt) =>
val encryptedOpt = for {
iv <- ByteVector.fromHex(rawIv)
iv <- ByteVector.fromHex(rawIv).map(AesIV.fromValidBytes(_))
cipherText <- ByteVector.fromHex(rawCipherText)
rawSalt <- ByteVector.fromHex(rawSalt)
salt = AesSalt(rawSalt)
salt <- ByteVector.fromHex(rawSalt).map(AesSalt(_))
} yield {
logger.debug(s"Parsed contents of $path into an EncryptedMnemonic")
EncryptedMnemonic(AesEncryptedData(cipherText, iv, salt))
EncryptedMnemonic(AesEncryptedData(cipherText, iv), salt)
}
encryptedOpt
.map(Right(_))
@ -208,7 +209,8 @@ sealed trait ReadMnemonicResult
case class ReadMnemonicSuccess(mnemonic: MnemonicCode)
extends ReadMnemonicResult
sealed trait ReadMnemonicError extends ReadMnemonicResult
sealed trait ReadMnemonicError extends ReadMnemonicResult { self: Error =>
}
object ReadMnemonicError {
@ -216,14 +218,20 @@ object ReadMnemonicError {
* Something went wrong while decrypting the mnemonic.
* Most likely the passphrase was bad
*/
case object DecryptionError extends ReadMnemonicError
case object DecryptionError
extends Error(s"Could not decrypt mnemonic!")
with ReadMnemonicError
/**
* Something went wrong while parsing the encrypted
* mnemonic into valid JSON
*/
case class JsonParsingError(message: String) extends ReadMnemonicError
case class JsonParsingError(message: String)
extends Error(s"Error when parsing JSON: $message")
with ReadMnemonicError
/** The encrypted mnemonic was not found on disk */
case object NotFoundError extends ReadMnemonicError
case object NotFoundError
extends Error("Could not find mnemonic!")
with ReadMnemonicError
}