Create MaskedToString, implement it in ECPrivateKey, ExtPrivateKey, M… (#1011)

* Create MaskedToString, implement it in ECPrivateKey, ExtPrivateKey, MnemonicCode

* Add MaskedToString to AesPassword, AesKey, and BIP39Seed

* Add final to MaskedToString.toString() so it can't be overriden
This commit is contained in:
Chris Stewart 2020-01-07 12:31:13 -06:00 committed by GitHub
parent b5d21a5a54
commit 0421076b21
8 changed files with 109 additions and 28 deletions

View File

@ -89,7 +89,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val path = BIP32Path.empty
val masterPriv = ExtPrivateKey(LegacyMainNetPriv, Some(seedBytes), path)
masterPriv.toString must be(
masterPriv.toStringSensitive must be(
"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi")
//master public key
@ -100,7 +100,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
//derive child
val m0hPath = BIP32Path.fromString("m/0'")
val m0h = masterPriv.deriveChildPrivKey(m0hPath)
m0h.toString must be(
m0h.toStringSensitive must be(
"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7")
val m0hPub = m0h.extPublicKey
@ -109,7 +109,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m0h1Path = BIP32Path.fromString("m/0'/1")
val m0h1 = masterPriv.deriveChildPrivKey(m0h1Path)
m0h1.toString must be(
m0h1.toStringSensitive must be(
"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs")
val m0h1Pub = m0h1.extPublicKey
@ -118,7 +118,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m0h1P2hath = BIP32Path.fromString("m/0'/1/2'")
val m0h12h = masterPriv.deriveChildPrivKey(m0h1P2hath)
m0h12h.toString must be(
m0h12h.toStringSensitive must be(
"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM")
val m0h12hPub = m0h12h.extPublicKey
@ -127,7 +127,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m0h12h2Path = BIP32Path.fromString("m/0'/1/2'/2")
val m0h12h2 = masterPriv.deriveChildPrivKey(m0h12h2Path)
m0h12h2.toString must be(
m0h12h2.toStringSensitive must be(
"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334")
val m0h12h2Pub = m0h12h2.extPublicKey
@ -136,7 +136,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m0h12h21000000000Path = BIP32Path.fromString("m/0'/1/2'/2/1000000000")
val m0h12h21000000000 = masterPriv.deriveChildPrivKey(m0h12h21000000000Path)
m0h12h21000000000.toString must be(
m0h12h21000000000.toStringSensitive must be(
"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76")
val m0h12h21000000000Pub = m0h12h21000000000.extPublicKey
@ -150,7 +150,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val masterPriv =
ExtPrivateKey(LegacyMainNetPriv, Some(seedBytes), BIP32Path.empty)
masterPriv.toString must be(
masterPriv.toStringSensitive must be(
"xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U")
val masterPub = masterPriv.extPublicKey
@ -159,7 +159,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m0Path = BIP32Path.fromString("m/0")
val m0 = masterPriv.deriveChildPrivKey(m0Path)
m0.toString must be(
m0.toStringSensitive must be(
"xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt")
val m0Pub = m0.extPublicKey
@ -169,7 +169,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m02147483647hPath = BIP32Path.fromString("m/0/2147483647'")
val m02147483647h =
masterPriv.deriveChildPrivKey(m02147483647hPath)
m02147483647h.toString must be(
m02147483647h.toStringSensitive must be(
"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9")
val m02147483647hPub = m02147483647h.extPublicKey
@ -178,7 +178,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val m02147483647h1Path = BIP32Path.fromString("m/0/2147483647'/1")
val m02147483647h1 = masterPriv.deriveChildPrivKey(m02147483647h1Path)
m02147483647h1.toString must be(
m02147483647h1.toStringSensitive must be(
"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef")
val m02147483647h1Pub = m02147483647h1.extPublicKey
@ -189,7 +189,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
BIP32Path.fromString("m/0/2147483647'/1/2147483646'")
val m02147483647h12147483646h =
masterPriv.deriveChildPrivKey(m02147483647h12147483646hPath)
m02147483647h12147483646h.toString must be(
m02147483647h12147483646h.toStringSensitive must be(
"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc")
val m02147483647h12147483646hPub = m02147483647h12147483646h.extPublicKey
@ -200,7 +200,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
BIP32Path.fromString("m/0/2147483647'/1/2147483646'/2")
val m02147483647h12147483646h2 =
masterPriv.deriveChildPrivKey(m02147483647h12147483646h2Path)
m02147483647h12147483646h2.toString must be(
m02147483647h12147483646h2.toStringSensitive must be(
"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j")
val m02147483647h12147483646h2Pub = m02147483647h12147483646h2.extPublicKey
@ -214,7 +214,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
val masterPrivKey =
ExtPrivateKey(LegacyMainNetPriv, Some(seedBytes), BIP32Path.empty)
masterPrivKey.toString must be(
masterPrivKey.toStringSensitive must be(
"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6")
val masterPubKey = masterPrivKey.extPublicKey
@ -222,7 +222,7 @@ class ExtKeyTest extends BitcoinSUnitTest {
"xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13")
val m0h = masterPrivKey.deriveChildPrivKey(BIP32Path.fromString("m/0'"))
m0h.toString must be(
m0h.toStringSensitive must be(
"xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L")
val m0hPub = m0h.extPublicKey
@ -241,4 +241,13 @@ class ExtKeyTest extends BitcoinSUnitTest {
val path2 = masterPriv.extPublicKey.deriveChildPubKey(idx).get.key
path1 must be(path2)
}
it must "not serialize a ExtPrivateKey to string" in {
val seedBytes =
hex"4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"
val masterPriv = ExtPrivateKey(LegacyMainNetPriv,
Some(seedBytes), BIP32Path.empty)
masterPriv.toString must be (s"Masked(ExtPrivateKeyImpl)")
}
}

View File

@ -254,4 +254,28 @@ class MnemonicCodeTest extends BitcoinSUnitTest {
testVectors.map(testTrezorVector(_))
}
it must "not serialize a MnemonicCode toString" in {
val correctSeed = Vector("phone",
"dilemma",
"early",
"never",
"test",
"surge",
"ecology",
"rail",
"medal",
"benefit",
"mystery",
"toward",
"lounge",
"candy",
"syrup")
val mnemonicCode = MnemonicCode.fromWords(correctSeed)
mnemonicCode.toString must be ("Masked(MnemonicCodeImpl)")
mnemonicCode.toStringSensitive must be (correctSeed.mkString(","))
}
}

View File

@ -8,7 +8,7 @@ import scodec.bits.ByteVector
import scala.util.{Failure, Success, Try}
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
import org.bitcoins.core.util.{Factory, MaskedToString}
/**
* Represents a encrypted cipher text with it's accompanying
@ -95,7 +95,7 @@ object AesSalt extends Factory[AesSalt] {
// we enforce the non-empty password length in the companion object
// to be able to make this extend AnyVal, and not be boxed at runtime
final case class AesPassword private (private val value: String)
extends AnyVal {
extends MaskedToString {
/**
* Converts this password into an AES key
@ -127,6 +127,10 @@ final case class AesPassword private (private val value: String)
val key = AesKey.fromSecretKey(secretKey)
key
}
override def toStringSensitive: String = {
ByteVector.encodeUtf8(value).toString
}
}
object AesPassword {
@ -159,7 +163,7 @@ object AesPassword {
* and have certain length requirements.
*/
final case class AesKey private (bytes: ByteVector)
extends AnyVal
extends MaskedToString
with NetworkElement {
/**
@ -169,6 +173,9 @@ final case class AesKey private (bytes: ByteVector)
def toSecretKey: SecretKey =
new SecretKeySpec(bytes.toArray, "AES")
override def toStringSensitive: String = {
s"AesKey(${bytes.toHex})"
}
}
object AesKey {

View File

@ -1,10 +1,10 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
import org.bitcoins.core.util.{Factory, MaskedToString}
import scodec.bits.ByteVector
sealed abstract class BIP39Seed extends NetworkElement {
sealed abstract class BIP39Seed extends NetworkElement with MaskedToString {
require(
bytes.length <= MAX_SEED_LENGTH_BYTES && bytes.length >= MIN_SEED_LENGTH_BYTES,
s"Seed must be between $MIN_SEED_LENGTH_BYTES and $MAX_SEED_LENGTH_BYTES bytes, got ${bytes.length}"
@ -16,6 +16,10 @@ sealed abstract class BIP39Seed extends NetworkElement {
/** Generates an extended private key given a version */
def toExtPrivateKey(keyVersion: ExtKeyPrivVersion): ExtPrivateKey =
ExtPrivateKey.fromBIP39Seed(keyVersion, this)
override def toStringSensitive: String = {
s"BIP39Seed($hex)"
}
}
/**

View File

@ -32,7 +32,10 @@ sealed abstract class BaseECKey extends NetworkElement
/**
* Created by chris on 2/16/16.
*/
sealed abstract class ECPrivateKey extends BaseECKey with Sign {
sealed abstract class ECPrivateKey
extends BaseECKey
with Sign
with MaskedToString {
override def signFunction: ByteVector => Future[ECDigitalSignature] = {
bytes =>
@ -111,7 +114,7 @@ sealed abstract class ECPrivateKey extends BaseECKey with Sign {
Base58.encode(encodedPrivKey)
}
override def toString = s"ECPrivateKey($hex,$isCompressed)"
override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)"
}
object ECPrivateKey extends Factory[ECPrivateKey] {

View File

@ -107,11 +107,9 @@ sealed abstract class ExtKey extends NetworkElement {
}
override def toString: String = {
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
val encoded = Base58.encode(bytes ++ checksum)
require(Base58.decodeCheck(encoded).isSuccess)
encoded
ExtKey.toString(this)
}
}
object ExtKey extends Factory[ExtKey] {
@ -148,6 +146,14 @@ object ExtKey extends Factory[ExtKey] {
extKey
}
def toString(extKey: ExtKey): String = {
val bytes = extKey.bytes
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
val encoded = Base58.encode(bytes ++ checksum)
require(Base58.decodeCheck(encoded).isSuccess)
encoded
}
override def fromBytes(bytes: ByteVector): ExtKey = {
val privTry = Try(ExtPrivateKey(bytes))
if (privTry.isSuccess) privTry.get
@ -157,7 +163,10 @@ object ExtKey extends Factory[ExtKey] {
}
}
sealed abstract class ExtPrivateKey extends ExtKey with ExtSign {
sealed abstract class ExtPrivateKey
extends ExtKey
with ExtSign
with MaskedToString {
import ExtKeyVersion._
override protected type VersionType = ExtKeyPrivVersion
@ -224,6 +233,10 @@ sealed abstract class ExtPrivateKey extends ExtKey with ExtSign {
case (bytes, path) =>
deriveChildPrivKey(path).signFunction(bytes)
}
override def toStringSensitive: String = {
ExtKey.toString(this)
}
}
object ExtPrivateKey extends Factory[ExtPrivateKey] {

View File

@ -2,7 +2,7 @@ package org.bitcoins.core.crypto
import java.security.SecureRandom
import org.bitcoins.core.util.CryptoUtil
import org.bitcoins.core.util.{CryptoUtil, MaskedToString}
import scodec.bits.{BitVector, ByteVector}
import scala.annotation.tailrec
@ -16,7 +16,7 @@ import scala.io.Source
* can be the root of a [[https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki BIP32]]
* HD wallet.
*/
sealed abstract class MnemonicCode {
sealed abstract class MnemonicCode extends MaskedToString {
require(
MnemonicCode.VALID_LENGTHS.contains(words.length), {
val validLengths = MnemonicCode.VALID_LENGTHS.mkString(", ")
@ -106,6 +106,10 @@ sealed abstract class MnemonicCode {
bits.take(codeInfo.entropyAndChecksumBits)
}
override def toStringSensitive: String = {
words.mkString(",")
}
}
/**

View File

@ -0,0 +1,17 @@
package org.bitcoins.core.util
/** Meant to provide a simple trait that
* masks the default to string for sensitive classes */
trait MaskedToString {
final override def toString: String = {
s"Masked(${getClass.getSimpleName})"
}
/** Returns the real value of a sensitive string
* This should be considered unsafe in the sense
* that this information is sensitive and could cause
* loss of funds if used anywhere things are persisted like logs
*
* */
def toStringSensitive: String
}