Refactoring Address type to have a networkParameters, hash and address value, removing AssetAddress - we can support these later if needed

This commit is contained in:
Chris Stewart 2016-08-31 10:31:09 -05:00
parent 2a2780bfd4
commit ea6866c3ac
10 changed files with 55 additions and 123 deletions

View file

@ -16,6 +16,7 @@ trait TransactionSignatureCreator {
*/
def createSig(txSignatureComponent: TransactionSignatureComponent, privateKey: ECPrivateKey, hashType: HashType): ECDigitalSignature = {
val hash = TransactionSignatureSerializer.hashForSignature(txSignatureComponent, hashType)
val signature = privateKey.sign(hash)
//append 1 byte hash type onto the end
val sig = ECDigitalSignature(signature.bytes ++ Seq(hashType.byte))

View file

@ -2,20 +2,29 @@ package org.bitcoins.core.protocol
import org.bitcoins.core.config._
import org.bitcoins.core.config.{MainNet, RegTest, TestNet3}
import org.bitcoins.core.crypto.{ECPublicKey, Sha256Hash160Digest}
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.protocol.script.{P2SHScriptPubKey, ScriptPubKey}
import org.bitcoins.core.util.{Base58, CryptoUtil, Factory}
import scala.util.{Failure, Success, Try}
sealed abstract class Address {
/** The network that this address is valid for */
def networkParameters: NetworkParameters
/** The base58 string representation of this address */
def value : String
/** Every address is derived from a [[Sha256Hash160Digest]] in a [[TransactionOutput]] */
def hash: Sha256Hash160Digest
}
sealed trait BitcoinAddress extends Address
sealed trait P2PKHAddress extends BitcoinAddress
object P2PKHAddress {
private case class P2PKHAddressImpl(override val value: String) extends P2PKHAddress {
private case class P2PKHAddressImpl(value: String, hash: Sha256Hash160Digest,
networkParameters: NetworkParameters) extends P2PKHAddress {
require(isP2PKHAddress(value), "Bitcoin address was invalid " + value)
}
@ -30,11 +39,13 @@ object P2PKHAddress {
val versionByte: Byte = network.p2pkhNetworkByte
val bytes = Seq(versionByte) ++ hash.bytes
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
P2PKHAddressImpl(Base58.encode(bytes ++ checksum))
P2PKHAddress(Base58.encode(bytes ++ checksum), hash, network)
}
def apply(value : String): P2PKHAddress = P2PKHAddressImpl(value)
def apply(value : String, hash: Sha256Hash160Digest, networkParameters: NetworkParameters): P2PKHAddress = {
P2PKHAddressImpl(value,hash,networkParameters)
}
def apply(hash: Sha256Hash160Digest, networkParameters: NetworkParameters): P2PKHAddress = encodePubKeyHashToAddress(hash,networkParameters)
@ -75,7 +86,8 @@ sealed trait P2SHAddress extends BitcoinAddress
* [[P2SHAddress]] companion object
*/
object P2SHAddress {
private case class P2SHAddressImpl(override val value: String) extends P2SHAddress {
private case class P2SHAddressImpl(value: String, hash: Sha256Hash160Digest,
networkParameters: NetworkParameters) extends P2SHAddress {
require(isP2SHAddress(value), "Bitcoin address was invalid " + value)
}
@ -106,10 +118,19 @@ object P2SHAddress {
val hash = p2shScriptPubKey.scriptHash
val bytes = Seq(versionByte) ++ hash.bytes
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
P2SHAddressImpl(Base58.encode(bytes ++ checksum))
P2SHAddress(Base58.encode(bytes ++ checksum),hash,network)
}
def apply(value: String): P2SHAddress = P2SHAddressImpl(value)
def apply(value: String, hash160Digest: Sha256Hash160Digest, networkParameters: NetworkParameters): P2SHAddress = {
P2SHAddressImpl(value, hash160Digest, networkParameters)
}
def apply(hash: Sha256Hash160Digest, network: NetworkParameters): P2SHAddress = {
val versionByte = network.p2shNetworkByte
val bytes = Seq(versionByte) ++ hash.bytes
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
P2SHAddress(Base58.encode(bytes ++ checksum), hash, network)
}
/**
* Checks if a address is a valid p2sh address
*
@ -138,10 +159,6 @@ object P2SHAddress {
}
sealed trait AssetAddress extends Address
object BitcoinAddress {
def validate(bitcoinAddress: String): Boolean = {
val illegalChars = List('O', 'I', 'l', '0')
@ -150,72 +167,28 @@ object BitcoinAddress {
bitcoinAddress.filter(c => illegalChars.contains(c)).size == 0
}
/**
* Converts a bitcoin address to an asset address
*
* @param address
* @return
*/
def convertToAssetAddress(address : BitcoinAddress) : AssetAddress = {
val underlying : String = address.value
val decodedBase58 : Seq[Byte] = Base58.decode(underlying)
require (
decodedBase58.size == 25
)
val decodedWithNameSpaceByte = Seq(0x13.toByte) ++ decodedBase58
val split = decodedWithNameSpaceByte.splitAt(decodedWithNameSpaceByte.length - 4)
val data = split._1
val newCheckSum = CryptoUtil.doubleSHA256(data).bytes.slice(0,4)
val constructedAssetAddress = data ++ newCheckSum
val encodedAssetAddress = Base58.encode(constructedAssetAddress)
AssetAddress(encodedAssetAddress)
}
def apply(value: String): BitcoinAddress = {
if (P2PKHAddress.isP2PKHAddress(value)) P2PKHAddress(value)
else if (P2SHAddress.isP2SHAddress(value)) P2SHAddress(value)
else throw new IllegalArgumentException("The address was not a p2pkh or p2sh address, got: " + value)
}
}
object AssetAddress {
private case class AssetAddressImpl(value : String) extends AssetAddress {
require(AssetAddress.validate(value), "The provided asset was invalid: " + value)
}
def validate(assetAddress : String) : Boolean = {
//asset addresses must have the one byte namespace equivalent to 19
//which ends up being 'a' in the ascii character set.
//bytes size becomes 22
val decodeCheckAssetAddress : Try[Seq[Byte]] = Base58.decodeCheck(assetAddress)
decodeCheckAssetAddress match {
case Success(bytes) => bytes.size == 22 && bytes.head == 0x13
case Failure(_) => false
val decodeChecked = Base58.decodeCheck(value)
decodeChecked match {
case Success(bytes) =>
val network = matchNetwork(bytes.head)
if (P2PKHAddress.isP2PKHAddress(value)) {
P2PKHAddress(Sha256Hash160Digest(bytes.tail),network)
}
else if (P2SHAddress.isP2SHAddress(value)) {
P2SHAddress(Sha256Hash160Digest(bytes.tail), network)
} else throw new IllegalArgumentException("The address was not a p2pkh or p2sh address, got: " + value)
case Failure(exception) =>
throw exception
}
}
/**
* Converts an asset address into a bitcoin address
*
* @param assetAddress
* @return
*/
def convertToBitcoinAddress(assetAddress : AssetAddress) : BitcoinAddress = {
val underlying : String = assetAddress.value
val decodedAsset = Base58.decode(underlying)
require {
decodedAsset.size == 26
}
val data = decodedAsset.slice(0, decodedAsset.length - 4)
val dataDroppedNameSpace = data.drop(1)
val checkSum = CryptoUtil.doubleSHA256(dataDroppedNameSpace).bytes.slice(0,4)
val value = Base58.encode(dataDroppedNameSpace ++ checkSum)
BitcoinAddress(value)
private def matchNetwork(byte: Byte): NetworkParameters = byte match {
case _ if Seq(MainNet.p2pkhNetworkByte,MainNet.p2shNetworkByte).contains(byte) => MainNet
case _ if Seq(TestNet3.p2pkhNetworkByte, TestNet3.p2shNetworkByte).contains(byte) => TestNet3
case _ if Seq(RegTest.p2pkhNetworkByte,RegTest.p2shNetworkByte).contains(byte) => RegTest
}
def apply(value : String): AssetAddress = AssetAddressImpl(value)
}
object Address extends Factory[Address] {
@ -229,12 +202,10 @@ object Address extends Factory[Address] {
* @return
*/
def factory(str : String) : Address = {
if (AssetAddress.validate(str)) AssetAddress(str)
else if (BitcoinAddress.validate(str)) BitcoinAddress(str)
if (BitcoinAddress.validate(str)) BitcoinAddress(str)
else throw new RuntimeException("The address that you passed in is invalid")
}
def fromBytes(bytes : Seq[Byte]) : Address = factory(Base58.encode(bytes))
override def fromHex(hex : String) : Address = throw new RuntimeException("We cannot create a bitcoin address from hex - bitcoin addresses are base 58 encoded")

View file

@ -33,7 +33,7 @@ sealed trait ScriptPubKey extends NetworkElement with BitcoinSLogger {
* https://bitcoin.org/en/developer-guide#pay-to-public-key-hash-p2pkh
* Format: OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
*/
trait P2PKHScriptPubKey extends ScriptPubKey
sealed trait P2PKHScriptPubKey extends ScriptPubKey
object P2PKHScriptPubKey extends Factory[P2PKHScriptPubKey] {
@ -72,7 +72,7 @@ object P2PKHScriptPubKey extends Factory[P2PKHScriptPubKey] {
* https://bitcoin.org/en/developer-guide#multisig
* Format: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
*/
trait MultiSignatureScriptPubKey extends ScriptPubKey {
sealed trait MultiSignatureScriptPubKey extends ScriptPubKey {
/**
* Returns the amount of required signatures for this multisignature script pubkey output

View file

@ -255,6 +255,7 @@ object P2SHScriptSignature extends Factory[P2SHScriptSignature] with BitcoinSLog
redeemScriptTry match {
case Success(redeemScript) =>
logger.debug("Possible redeemScript: " + redeemScript)
//logger.debug("Equals EmptyScriptPubKey: " + (EmptyScriptPubKey == redeemScript))
redeemScript match {
case x : P2PKHScriptPubKey => true
case x : MultiSignatureScriptPubKey => true

View file

@ -90,10 +90,10 @@ trait BitcoinSUtil {
paddedHex
}
/** Converts a sequence of bytes to a sequence of bit vectors - assumes the sequence of bytes are big endian */
/** Converts a sequence of bytes to a sequence of bit vectors */
def bytesToBitVectors(bytes: Seq[Byte]): Seq[Seq[Boolean]] = bytes.map(byteToBitVector)
/** Converts a byte to a bit vector representing that byte - the bit vector is big endian */
/** Converts a byte to a bit vector representing that byte */
def byteToBitVector(byte: Byte): Seq[Boolean] = {
(0 to 7).map(index => isBitSet(byte,7 - index))
}
@ -101,12 +101,12 @@ trait BitcoinSUtil {
/** Checks if the bit at the given index is set */
def isBitSet(byte: Byte, index: Int): Boolean = ((byte >> index) & 1) == 1
/** Converts a bit vector to a single byte -- assumes the bits are big endian */
/** Converts a bit vector to a single byte -- the resulting byte is big endian */
def bitVectorToByte(bits: Seq[Boolean]): Byte = {
require(bits.size <= 8, "Cannot convert a bit vector to a byte when the size of the bit vector is larger than 8, got: " + bits)
val b = bits.reverse
val result: Seq[Int] = b.zipWithIndex.map { case (b, index) =>
if (b) NumberUtil.pow2(index).toInt else 0
if (b) NumberUtil.pow2(index).toInt else 0
}
result.sum.toByte
}

View file

@ -16,11 +16,6 @@ class AddressFactoryTest extends FlatSpec with MustMatchers {
Address(Base58.decode(TestUtil.bitcoinAddress.value)) must be (TestUtil.bitcoinAddress)
}
it must "create an asset address from a base58 encoded string" in {
Address(Base58.decode(TestUtil.assetAddress.value)) must be (TestUtil.assetAddress)
}
it must "throw an exception if the given string" in {
intercept[RuntimeException] {
Address("01234567890abcdef")

View file

@ -7,21 +7,5 @@ import org.scalatest.{FlatSpec, MustMatchers}
* Created by chris on 3/23/15.
*/
class AddressTest extends FlatSpec with MustMatchers with BitcoinSLogger {
val assetAddress = TestUtil.assetAddress
"Addresses" must "be able to convert back and forth between a Bitcoin Address & an asset address" in {
val convertedOnce = BitcoinAddress.convertToAssetAddress(TestUtil.bitcoinAddress)
val actual : BitcoinAddress = AssetAddress.convertToBitcoinAddress(convertedOnce)
actual must be (TestUtil.bitcoinAddress)
val bitcoinAddress = AssetAddress.convertToBitcoinAddress(assetAddress)
val actualAssetAddress = BitcoinAddress.convertToAssetAddress(bitcoinAddress)
actualAssetAddress must be (assetAddress)
}
it must "allow type encapsulation for addresses" in {
val bitcoinAddress : Address = TestUtil.bitcoinAddress
val assetAddress : Address = TestUtil.assetAddress
assetAddress must be (TestUtil.assetAddress)
bitcoinAddress must be (TestUtil.bitcoinAddress)
}
}

View file

@ -45,17 +45,6 @@ class BitcoinAddressTest extends FlatSpec with MustMatchers {
}
}
"akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA" must "be a valid asset address" in {
val assetAddress = AssetAddress("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
assetAddress.value must be ("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
}
"An asset address with the first character replaced" must "not be a valid asset address" in {
//3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy
intercept[IllegalArgumentException] {
val assetAddress = AssetAddress("aJ98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy")
}
}
it must "encode a pubKeyHash to an address" in {
//from https://stackoverflow.com/questions/19233053/hashing-from-a-public-key-to-a-bitcoin-address-in-php
@ -69,6 +58,6 @@ class BitcoinAddressTest extends FlatSpec with MustMatchers {
val hex = "5141042f90074d7a5bf30c72cf3a8dfd1381bdbd30407010e878f3a11269d5f74a58788505cdca22ea6eab7cfb40dc0e07aba200424ab0d79122a653ad0c7ec9896bdf51ae"
val scriptPubKey = ScriptPubKey(hex)
val addr = P2SHAddress.encodeScriptPubKeyToAddress(scriptPubKey,MainNet)
addr must be (P2SHAddress("3P14159f73E4gFr7JterCCQh9QjiTjiZrG"))
addr must be (BitcoinAddress("3P14159f73E4gFr7JterCCQh9QjiTjiZrG"))
}
}

View file

@ -76,14 +76,6 @@ class Base58Test extends FlatSpec with MustMatchers with BitcoinSLogger {
Base58.encode(Base58.decode(address)) must be ("1C4kYhyLftmkn48YarSoLupxHfYFo8kp64")
}
it must "decode asset address into bytes then encode back to asset address" in {
//akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA
val asset = TestUtil.assetAddress.value
val bitcoinj = org.bitcoinj.core.Base58.encode(org.bitcoinj.core.Base58.decode(asset))
Base58.encode(Base58.decode(asset)) must be ("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
Base58.encode(Base58.decode(asset)) must be (bitcoinj)
}
it must "decode multisig address into bytes then encode back to multisig" in {
val multi = TestUtil.multiSigAddress.value
val bitcoinj = org.bitcoinj.core.Base58.encode(org.bitcoinj.core.Base58.decode(multi))

View file

@ -6,7 +6,7 @@ import org.bitcoins.core.serializers.transaction.{RawTransactionInputParser, Raw
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.{EmptyTransaction, Transaction}
import org.bitcoins.core.protocol.{AssetAddress, BitcoinAddress}
import org.bitcoins.core.protocol.{BitcoinAddress}
import org.bitcoins.core.script.{ExecutionInProgressScriptProgram, PreExecutionScriptProgram, ExecutedScriptProgram, ScriptProgram}
import org.bitcoins.core.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
import org.bitcoins.core.script.constant._
@ -22,7 +22,6 @@ object TestUtil {
def testP2SHAddress = BitcoinAddress("2MzYbQdkSVp5wVyMRp6A5PHPuQNHpiaTbCj")
def bitcoinAddress = BitcoinAddress("1C4kYhyLftmkn48YarSoLupxHfYFo8kp64")
def multiSigAddress = BitcoinAddress("342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey")
def assetAddress = AssetAddress("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
val p2pkhInputScript = "473044022016ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca030220119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac012102af7dad03e682fcd0427b5c24140c220ac9d8abe286c15f8cf5bf77eed19c3652"
def p2pkhScriptSig = ScriptSignature(p2pkhInputScript)