From 88b0e87755673fda4d0b6f2485ee988ce95a1e77 Mon Sep 17 00:00:00 2001 From: Tom McCabe Date: Fri, 24 Jun 2016 14:07:15 -0500 Subject: [PATCH] add scala docs, breakdown isValid Base58 function --- .../scala/org/bitcoins/core/util/Base58.scala | 134 ++++++++++-------- .../core/crypto/ECPrivateKeyTest.scala | 2 +- .../bitcoins/core/util/CryptoTestUtil.scala | 2 +- .../core/util/TransactionTestUtil.scala | 6 +- 4 files changed, 78 insertions(+), 66 deletions(-) diff --git a/src/main/scala/org/bitcoins/core/util/Base58.scala b/src/main/scala/org/bitcoins/core/util/Base58.scala index 41fbec8232..6380418c52 100644 --- a/src/main/scala/org/bitcoins/core/util/Base58.scala +++ b/src/main/scala/org/bitcoins/core/util/Base58.scala @@ -2,7 +2,6 @@ package org.bitcoins.core.util import org.bitcoins.core.config.{MainNet, TestNet3} import org.bitcoins.core.crypto.{ECPrivateKey, Sha256Hash160Digest} -import org.bitcoins.core.protocol.Address import org.bitcoins.core.protocol.blockchain._ import scala.annotation.tailrec @@ -36,7 +35,6 @@ trait Base58 extends BitcoinSLogger { } } - /** * Takes in sequence of bytes and returns base58 bitcoin string * @@ -79,24 +77,10 @@ trait Base58 extends BitcoinSLogger { * @param isTestNet boolean * @return */ + //TODO: add logic to determine pubkey/script from first byte def encodePubKeyHashToBase58Address(hash: Sha256Hash160Digest, addressType : String, isTestNet : Boolean) : String = { -/* val versionByte : Byte = { - require(addressType != "pubkey" || addressType != "script", - throw new IllegalArgumentException("Address must be of type 'pubkey' or 'script'.")) - if (!isTestNet) { - if (addressType == "pubkey") MainNet.p2pkhNetworkByte - else MainNet.p2shNetworkByte - } - else if (isTestNet) { - if (addressType == "pubkey") TestNet3.p2pkhNetworkByte - else TestNet3.p2shNetworkByte - } - else throw new IllegalArgumentException("Something broke -- Check your parameters. There should be a " + - "Sha256RipeMD160 hash, addressType('pubkey' or 'script'), and testnet boolean (true or false).") - }*/ - def parseVersionByte(addressType : String, isTestnet : Boolean) : Byte = isTestnet match { case true => if (addressType == "pubkey") TestNet3.p2pkhNetworkByte else TestNet3.p2shNetworkByte case false => if (addressType == "pubkey") MainNet.p2pkhNetworkByte else MainNet.p2shNetworkByte @@ -148,51 +132,79 @@ trait Base58 extends BitcoinSLogger { if (trim.isEmpty) zeroes else zeroes ++ decoded.toByteArray.dropWhile(_ == 0) } - def isValid(base58 : String) : Boolean = { - if (base58.isEmpty) false - else { - val decoded = decode(base58) - val firstByte = decoded.head - val validAddressPreFixBytes: Seq[Byte] = - MainNetChainParams.base58Prefixes(PubKeyAddress) ++ MainNetChainParams.base58Prefixes(ScriptAddress) ++ - TestNetChainParams.base58Prefixes(PubKeyAddress) ++ TestNetChainParams.base58Prefixes(ScriptAddress) - val validSecretKeyPreFixBytes : Seq[Byte] = - MainNetChainParams.base58Prefixes(SecretKey) ++ TestNetChainParams.base58Prefixes(SecretKey) - val compressedPubKey = List('K', 'L', 'c').contains(base58.head) - def checkCompressedPubKeyValidity : Boolean = { - val compressedByte = decoded(decoded.length - 5) - compressedByte == 0x01.toByte - } - try { - if (base58.contains(List('0', 'O', 'l', 'I'))) false - else if (compressedPubKey) checkCompressedPubKeyValidity - else if (validAddressPreFixBytes.contains(firstByte)) base58.length >= 26 && base58.length <= 35 - else if (validSecretKeyPreFixBytes.contains(firstByte)) { - val byteSize = ECPrivateKey.fromBase58ToPrivateKey(base58).bytes.size - byteSize == 32 - } - else false - } - catch { - case error : IllegalArgumentException => false - } - } + /** + * Determines if a string is a valid Base58-encoded string. + * + * @param base58 + * @return + */ + def isValid(base58 : String) : Boolean = validityChecks(base58) match { + case Success(bool) => bool + case Failure(exception) => false } -/* def isValid(base58 : String) : Boolean = { - val firstByte : Seq[Byte]= if (base58.isEmpty) List() else Seq(decode(base58).head) - val length = base58.length - val validFirstByteInHex = List("00", "05", "80", "6f", "c4", "ef") - val invalidChars = List('0','O','l','I') - val firstByteInHex = BitcoinSUtil.encodeHex(firstByte) - if (!validFirstByteInHex.contains(firstByteInHex)) false - else if (length < 25 || length > 36) false - else if (base58.contains(invalidChars)) false - else true - }*/ + /** + * Checks a private key that begins with a symbol corresponding that private key to a compressed public key ('K', 'L', 'c'). + * In a Base58-encoded private key corresponding to a compressed public key, the 5th-to-last byte should be 0x01. + * + * @param base58 + * @return + */ + private def checkCompressedPubKeyValidity(base58 : String) : Boolean = { + val decoded = Base58.decode(base58) + val compressedByte = decoded(decoded.length - 5) + compressedByte == 0x01.toByte + } + + /** + * Checks if the string begins with an Address prefix byte/character. + * ('1', '3', 'm', 'n', '2') + * + * @param byte + * @return + */ + private def isValidAddressPreFixByte(byte : Byte) : Boolean = { + val validAddressPreFixBytes: Seq[Byte] = + MainNetChainParams.base58Prefixes(PubKeyAddress) ++ MainNetChainParams.base58Prefixes(ScriptAddress) ++ + TestNetChainParams.base58Prefixes(PubKeyAddress) ++ TestNetChainParams.base58Prefixes(ScriptAddress) + validAddressPreFixBytes.contains(byte) + } + + /** + * Checks if the string begins with a private key prefix byte/character. + * ('5', '9', 'c') + * + * @param byte + * @return + */ + private def isValidSecretKeyPreFixByte(byte : Byte) : Boolean = { + val validSecretKeyPreFixBytes : Seq[Byte] = + MainNetChainParams.base58Prefixes(SecretKey) ++ TestNetChainParams.base58Prefixes(SecretKey) + validSecretKeyPreFixBytes.contains(byte) + } + + /** + * Checks the validity of a Base58 encoded string. A Base58 encoded string must not contain ('0', 'O', 'l', 'I'). + * If the string is an address: it must have a valid address prefix byte and must be between 26-35 characters in length. + * If the string is a private key: it must have a valid private key prefix byte and must have a byte size of 32. + * If the string is a private key corresponding to a compressed public key, the 5th-to-last byte must be 0x01. + * + * @param base58 + * @return + */ + private def validityChecks(base58: String) : Try[Boolean] = Try { + val decoded = decode(base58) + val firstByte = decoded.head + val compressedPubKey = List('K', 'L', 'c').contains(base58.head) + if (base58.contains(List('0', 'O', 'l', 'I'))) false + else if (compressedPubKey) checkCompressedPubKeyValidity(base58) + else if (isValidAddressPreFixByte(firstByte)) base58.length >= 26 && base58.length <= 35 + else if (isValidSecretKeyPreFixByte(firstByte)) { + val byteSize = ECPrivateKey.fromWIFToPrivateKey(base58).bytes.size + byteSize == 32 + } + else false + } } -object Base58 extends Base58 - - - +object Base58 extends Base58 \ No newline at end of file diff --git a/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala b/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala index 09b45880de..27a74ac512 100644 --- a/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala +++ b/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala @@ -40,7 +40,7 @@ class ECPrivateKeyTest extends FlatSpec with MustMatchers { val privateKeyBase58 = CryptoTestUtil.privateKeyBase58 val bitcoinjDumpedPrivateKey = new org.bitcoinj.core.DumpedPrivateKey(BitcoinJTestUtil.params,privateKeyBase58) val bitcoinjPrivateKey = bitcoinjDumpedPrivateKey.getKey - val privateKey = ECPrivateKey.fromBase58ToPrivateKey(privateKeyBase58) + val privateKey = ECPrivateKey.fromWIFToPrivateKey(privateKeyBase58) privateKey.hex must be (bitcoinjPrivateKey.getPrivateKeyAsHex) diff --git a/src/test/scala/org/bitcoins/core/util/CryptoTestUtil.scala b/src/test/scala/org/bitcoins/core/util/CryptoTestUtil.scala index fdadfb0728..1c44f366da 100644 --- a/src/test/scala/org/bitcoins/core/util/CryptoTestUtil.scala +++ b/src/test/scala/org/bitcoins/core/util/CryptoTestUtil.scala @@ -13,7 +13,7 @@ trait CryptoTestUtil { def privateKeyHex = BitcoinSUtil.encodeHex(privateKeyBytes) def bitcoinjDumpedPrivateKey = new DumpedPrivateKey(BitcoinJTestUtil.params,privateKeyBase58) def bitcoinjPrivateKey = bitcoinjDumpedPrivateKey.getKey - def privateKey = ECPrivateKey.fromBase58ToPrivateKey(privateKeyBase58) + def privateKey = ECPrivateKey.fromWIFToPrivateKey(privateKeyBase58) } diff --git a/src/test/scala/org/bitcoins/core/util/TransactionTestUtil.scala b/src/test/scala/org/bitcoins/core/util/TransactionTestUtil.scala index fbdf22c8a1..e079b72eb3 100644 --- a/src/test/scala/org/bitcoins/core/util/TransactionTestUtil.scala +++ b/src/test/scala/org/bitcoins/core/util/TransactionTestUtil.scala @@ -102,9 +102,9 @@ trait TransactionTestUtil extends BitcoinSLogger { } def signedMultiSignatureTransaction : (Transaction, Int, ScriptPubKey, Seq[ECPublicKey]) = { - val key1 = ECPrivateKey.fromBase58ToPrivateKey("cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") - val key2 = ECPrivateKey.fromBase58ToPrivateKey("cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo") - def key3 = ECPrivateKey.fromBase58ToPrivateKey("cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg") + val key1 = ECPrivateKey.fromWIFToPrivateKey("cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") + val key2 = ECPrivateKey.fromWIFToPrivateKey("cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo") + def key3 = ECPrivateKey.fromWIFToPrivateKey("cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg") (signedMultiSignatureTx,0,multiSignatureScriptPubKey, Seq(key1.publicKey,key2.publicKey,key3.publicKey)) }