mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 15:02:17 +01:00
merge master changes
This commit is contained in:
commit
f75c1f8cff
8 changed files with 69 additions and 143 deletions
|
@ -1 +1 @@
|
|||
import AssemblyKeys._
//test in assembly := {}
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
coverageExcludedPackages := ".*gen"
coverageMinimum := 90
coverageFailOnMinimum := true
|
||||
import AssemblyKeys._
//test in assembly := {}
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
coverageExcludedPackages := ".*gen"
coverageMinimum := 90
coverageFailOnMinimum := true
mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
{
case "logback.xml" => MergeStrategy.discard
case x => old(x)
}
}
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)
|
|
@ -2,20 +2,39 @@ 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 {
|
||||
sealed trait 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
|
||||
|
||||
sealed trait P2PKHAddress extends BitcoinAddress {
|
||||
/** The base58 string representation of this address */
|
||||
override def value : String = {
|
||||
val versionByte = networkParameters.p2pkhNetworkByte
|
||||
val bytes = Seq(versionByte) ++ hash.bytes
|
||||
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
|
||||
Base58.encode(bytes ++ checksum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object P2PKHAddress {
|
||||
private case class P2PKHAddressImpl(override val value: String) extends P2PKHAddress {
|
||||
private case class P2PKHAddressImpl(hash: Sha256Hash160Digest,
|
||||
networkParameters: NetworkParameters) extends P2PKHAddress {
|
||||
require(isP2PKHAddress(value), "Bitcoin address was invalid " + value)
|
||||
}
|
||||
|
||||
|
@ -26,15 +45,7 @@ object P2PKHAddress {
|
|||
* @param network the network on which this address is being generated for
|
||||
* @return
|
||||
*/
|
||||
def encodePubKeyHashToAddress(hash: Sha256Hash160Digest, network: NetworkParameters): 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))
|
||||
}
|
||||
|
||||
|
||||
def apply(value : String): P2PKHAddress = P2PKHAddressImpl(value)
|
||||
def encodePubKeyHashToAddress(hash: Sha256Hash160Digest, network: NetworkParameters): P2PKHAddress = P2PKHAddressImpl(hash,network)
|
||||
|
||||
def apply(hash: Sha256Hash160Digest, networkParameters: NetworkParameters): P2PKHAddress = encodePubKeyHashToAddress(hash,networkParameters)
|
||||
|
||||
|
@ -42,6 +53,7 @@ object P2PKHAddress {
|
|||
val hash = CryptoUtil.sha256Hash160(pubKey.bytes)
|
||||
P2PKHAddress(hash,networkParameters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an address is a valid p2pkh address
|
||||
*
|
||||
|
@ -69,13 +81,22 @@ object P2PKHAddress {
|
|||
|
||||
}
|
||||
|
||||
sealed trait P2SHAddress extends BitcoinAddress
|
||||
sealed trait P2SHAddress extends BitcoinAddress {
|
||||
/** The base58 string representation of this address */
|
||||
override def value : String = {
|
||||
val versionByte = networkParameters.p2shNetworkByte
|
||||
val bytes = Seq(versionByte) ++ hash.bytes
|
||||
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
|
||||
Base58.encode(bytes ++ checksum)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [[P2SHAddress]] companion object
|
||||
*/
|
||||
object P2SHAddress {
|
||||
private case class P2SHAddressImpl(override val value: String) extends P2SHAddress {
|
||||
private case class P2SHAddressImpl(hash: Sha256Hash160Digest,
|
||||
networkParameters: NetworkParameters) extends P2SHAddress {
|
||||
require(isP2SHAddress(value), "Bitcoin address was invalid " + value)
|
||||
}
|
||||
|
||||
|
@ -101,18 +122,13 @@ object P2SHAddress {
|
|||
def apply(scriptPubKey: ScriptPubKey,network: NetworkParameters): P2SHAddress = encodeScriptPubKeyToAddress(scriptPubKey,network)
|
||||
|
||||
|
||||
def apply(p2shScriptPubKey: P2SHScriptPubKey, network: NetworkParameters): P2SHAddress = {
|
||||
val versionByte = network.p2shNetworkByte
|
||||
val hash = p2shScriptPubKey.scriptHash
|
||||
val bytes = Seq(versionByte) ++ hash.bytes
|
||||
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
|
||||
P2SHAddressImpl(Base58.encode(bytes ++ checksum))
|
||||
}
|
||||
def apply(p2shScriptPubKey: P2SHScriptPubKey, network: NetworkParameters): P2SHAddress = P2SHAddress(p2shScriptPubKey.scriptHash,network)
|
||||
|
||||
|
||||
def apply(hash: Sha256Hash160Digest, network: NetworkParameters): P2SHAddress = P2SHAddressImpl(hash, network)
|
||||
|
||||
def apply(value: String): P2SHAddress = P2SHAddressImpl(value)
|
||||
/**
|
||||
* Checks if a address is a valid p2sh address
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
|
@ -138,84 +154,37 @@ object P2SHAddress {
|
|||
|
||||
}
|
||||
|
||||
|
||||
sealed trait AssetAddress extends Address
|
||||
|
||||
|
||||
object BitcoinAddress {
|
||||
/** Checks if the given base58 bitcoin address is a valid address */
|
||||
def validate(bitcoinAddress: String): Boolean = {
|
||||
val illegalChars = List('O', 'I', 'l', '0')
|
||||
bitcoinAddress.length >= 26 && bitcoinAddress.length <= 35 &&
|
||||
(P2PKHAddress.isP2PKHAddress(bitcoinAddress) || P2SHAddress.isP2SHAddress(bitcoinAddress)) &&
|
||||
bitcoinAddress.filter(c => illegalChars.contains(c)).size == 0
|
||||
val decodeChecked = Base58.decodeCheck(bitcoinAddress)
|
||||
decodeChecked.isSuccess
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/** Creates a [[BitcoinAddress]] from the given base58 string value */
|
||||
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)
|
||||
/** Helper function for helping matching an address to a network byte */
|
||||
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 +198,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")
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -75,14 +75,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