mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
Update wallet project to work new AES changes
This commit is contained in:
parent
d58f3dc715
commit
f93b8cadbe
7 changed files with 58 additions and 69 deletions
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue