mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 23:08:31 +01:00
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:
parent
2a2780bfd4
commit
ea6866c3ac
10 changed files with 55 additions and 123 deletions
|
@ -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))
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue