Adding support for bitcoin & asset addresses

This commit is contained in:
Chris Stewart 2015-12-02 10:14:25 -06:00
parent b784c8d643
commit 21b1873dfe
4 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,96 @@
package org.scalacoin.protocol
import org.bitcoinj.core.{VersionedChecksummedBytes, Base58, Utils}
case class AddressInfo(bitcoinAddress: BitcoinAddress, n_tx: Long, total_received: Long, total_sent: Long,
final_balance: Long)
sealed abstract class Address(val address : String)
sealed case class BitcoinAddress(bitcoinAddress: String) extends Address(bitcoinAddress) {
require(BitcoinAddress.validate(bitcoinAddress), "Bitcoin address was invalid " + bitcoinAddress)
}
sealed case class AssetAddress(assetAddress : String) extends Address(assetAddress) {
require(AssetAddress.validate(assetAddress), "The provided asset was was invalid: " + assetAddress)
}
object BitcoinAddress {
def validate(bitcoinAddress: String): Boolean = {
val illegalChars = List('O', 'I', 'l', '0')
bitcoinAddress.length >= 26 && bitcoinAddress.length <= 35 &&
(p2pkh(bitcoinAddress) || p2shAddress(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.bitcoinAddress
val base58decodeChecked : Array[Byte] = Base58.decodeChecked(underlying)
require (
base58decodeChecked.size == 21
)
AssetAddress(new VersionedChecksummedBytes(0x13, base58decodeChecked){}.toString())
}
/**
* Checks if a address is a valid p2sh address
* @param address
* @return
*/
def p2shAddress(address : String) : Boolean = {
address.charAt(0) == '3' || address.charAt(0) == '2'
}
/**
* Checks if a address is a valid p2sh address
* @param address
* @return
*/
def p2shAddress(address : BitcoinAddress) : Boolean = p2shAddress(address.bitcoinAddress)
/**
* Checks if an address is a valid p2pkh address
* @param address
* @return
*/
def p2pkh(address : String) : Boolean = {
val firstChar = address.charAt(0)
firstChar == '1' || firstChar == 'm' || firstChar == 'n'
}
/**
* Checks if an address is a valid p2pkh address
* @param address
* @return
*/
def p2pkh(address : BitcoinAddress) : Boolean = p2pkh(address.bitcoinAddress)
}
object AssetAddress {
def validate(assetAddress : String) = {
//asset addresses must have the one byte namespace equivalent to 19
//which ends up being 'a' in the ascii character set
val base58decodechecked : Array[Byte] = Base58.decodeChecked(assetAddress)
require(base58decodechecked != null)
base58decodechecked.size == 22 && base58decodechecked(0) == 0x13
}
/**
* Converts an asset address into a bitcoin address
* @param assetAddress
* @return
*/
def convertToBitcoinAddress(assetAddress : AssetAddress) = {
val underlying : String = assetAddress.assetAddress
val base58decodeChecked : Array[Byte] = Base58.decodeChecked(underlying)
require(base58decodeChecked.size == 22)
val slice = base58decodeChecked.slice(2, base58decodeChecked.length)
BitcoinAddress(new VersionedChecksummedBytes(base58decodeChecked(1), slice){}.toString())
}
}

View File

@ -0,0 +1,27 @@
package org.scalacoin.protocol
import org.scalacoin.util.TestUtil
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 3/23/15.
*/
class AddressTest extends FlatSpec with MustMatchers {
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

@ -0,0 +1,68 @@
package org.scalacoin.protocol
import org.scalatest.{FlatSpec, MustMatchers}
class BitcoinAddressTest extends FlatSpec with MustMatchers {
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" must "be a valid bitcoin address" in {
val address = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy"
BitcoinAddress(address).bitcoinAddress must be(address)
}
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" must "be a valid p2sh address and not a valid p2pkh" in {
val address = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy"
BitcoinAddress.p2shAddress(address) must be (true)
BitcoinAddress.p2pkh(address) must be (false)
}
"17WN1kFw8D6w1eHzqvkh49xwjE3iPN925b" must "be a valid p2pkh" in {
val address = "17WN1kFw8D6w1eHzqvkh49xwjE3iPN925b"
BitcoinAddress.p2pkh(address) must be (true)
BitcoinAddress.p2shAddress(address) must be (false)
}
"The empty string" must "not be a valid bitcoin address" in {
intercept[IllegalArgumentException] {
BitcoinAddress("")
}
}
"A string that is 25 characters long" must "not be a valid bitcoin address" in {
val address = "3J98t1WpEZ73CNmQviecrnyiW"
intercept[IllegalArgumentException] {
BitcoinAddress(address)
}
}
"A string that is 36 characters long" must "not be a valid bitcoin address" in {
val address = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLyyy"
intercept[IllegalArgumentException] {
BitcoinAddress(address)
}
}
"3J98t1WpEZ73CNmQviecrnyiWr (26 characters) " must "be a valid bitcoin address" in {
val address = "3J98t1WpEZ73CNmQviecrnyiWr"
BitcoinAddress(address).bitcoinAddress must be(address)
}
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy (35 characters)" must "be a valid bitcoin address" in {
val address = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy"
BitcoinAddress(address).bitcoinAddress must be(address)
}
"akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA" must "be a valid asset address" in {
val assetAddress = AssetAddress("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
assetAddress.assetAddress must be ("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
}
"An asset address with the first character replaced" must "not be a valid asset address" in {
//3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy
intercept[org.bitcoinj.core.AddressFormatException] {
val assetAddress = AssetAddress("aJ98t1WpEZ73CNmQviecrnyiWrnqRhWNLyy")
}
}
}

View File

@ -0,0 +1,15 @@
package org.scalacoin.util
import org.scalacoin.protocol.{AssetAddress, BitcoinAddress}
/**
* Created by chris on 12/2/15.
*/
object TestUtil {
val testBitcoinAddress = BitcoinAddress("n3p1ct69ao3qxWvEvzLhLtWG2zJGTjN3EV")
val testP2SHAddress = BitcoinAddress("2MzYbQdkSVp5wVyMRp6A5PHPuQNHpiaTbCj")
val bitcoinAddress = BitcoinAddress("1C4kYhyLftmkn48YarSoLupxHfYFo8kp64")
val multiSigAddress = BitcoinAddress("342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey")
val assetAddress = AssetAddress("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
}