Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions cashaddress/convert.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from cashaddress.crypto import *
from cashaddress.base58 import b58decode_check, b58encode_check
import sys
from functools import partial


class InvalidAddress(Exception):
pass


class Address:
class Address(object):
VERSION_MAP = {
'legacy': [
('P2SH', 5, False),
Expand All @@ -22,8 +23,11 @@ class Address:
('P2PKH-TESTNET', 0, True)
]
}
VERSION_MAP['slp'] = VERSION_MAP['cash']

MAINNET_PREFIX = 'bitcoincash'
TESTNET_PREFIX = 'bchtest'
SLP_PREFIX = 'simpleledger'

def __init__(self, version, payload, prefix=None):
self.version = version
Expand All @@ -39,16 +43,27 @@ def __init__(self, version, payload, prefix=None):
def __str__(self):
return 'version: {}\npayload: {}\nprefix: {}'.format(self.version, self.payload, self.prefix)

def legacy_address(self):
version_int = Address._address_type('legacy', self.version)[1]
return b58encode_check(Address.code_list_to_string([version_int] + self.payload))

def cash_address(self):
version_int = Address._address_type('cash', self.version)[1]
payload = [version_int] + self.payload
payload = convertbits(payload, 8, 5)
checksum = calculate_checksum(self.prefix, payload)
return self.prefix + ':' + b32encode(payload + checksum)
def _wrapper(self, body):
return self.prefix + ':' + b32encode(body)


def __getattr__(self, attr):
if attr == 'legacy_address':
version_int = Address._address_type('legacy', self.version)[1]
return partial(b58encode_check, Address.code_list_to_string([version_int] + self.payload))
else:
addr_type = attr.split("_")[0]
if addr_type not in self.VERSION_MAP:
raise AttributeError()
version_int = Address._address_type('cash', self.version)[1]
payload = [version_int] + self.payload
payload = convertbits(payload, 8, 5)
if addr_type == 'slp':
self.prefix = self.SLP_PREFIX
checksum = calculate_checksum(self.prefix, payload)
return partial(self._wrapper, (payload + checksum))


@staticmethod
def code_list_to_string(code_list):
Expand Down Expand Up @@ -78,7 +93,13 @@ def from_string(address_string):
if ':' not in address_string:
return Address._legacy_string(address_string)
else:
return Address._cash_string(address_string)
if address_string.startswith(Address.MAINNET_PREFIX) or address_string.startswith(Address.TESTNET_PREFIX):
return Address._cash_string(address_string, 'cash')
elif address_string.startswith(Address.SLP_PREFIX):
return Address._cash_string(address_string, 'slp')
else:
raise InvalidAddress('Unexpected Prefix')


@staticmethod
def _legacy_string(address_string):
Expand All @@ -92,8 +113,9 @@ def _legacy_string(address_string):
payload.append(letter)
return Address(version, payload)


@staticmethod
def _cash_string(address_string):
def _cash_string(address_string, address_type='cash'):
if address_string.upper() != address_string and address_string.lower() != address_string:
raise InvalidAddress('Cash address contains uppercase and lowercase characters')
address_string = address_string.lower()
Expand All @@ -107,7 +129,7 @@ def _cash_string(address_string):
if not verify_checksum(prefix, decoded):
raise InvalidAddress('Bad cash address checksum')
converted = convertbits(decoded, 5, 8)
version = Address._address_type('cash', converted[0])[0]
version = Address._address_type(address_type, converted[0])[0]
if prefix == Address.TESTNET_PREFIX:
version += '-TESTNET'
payload = converted[1:-6]
Expand All @@ -122,6 +144,10 @@ def to_legacy_address(address):
return Address.from_string(address).legacy_address()


def to_slp_address(address):
return Address.from_string(address).slp_address()


def is_valid(address):
try:
Address.from_string(address)
Expand Down
5 changes: 5 additions & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def test_to_legacy_p2pkh(self):
self.assertEqual(convert.to_legacy_address('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'),
'155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4')

def test_to_slp_p2pkh(self):
self.assertEqual(convert.to_slp_address('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4'),
'simpleledger:qqkv9wr69ry2p9l53lxp635va4h86wv435f7l4jp5f')

def test_to_cash_p2sh(self):
self.assertEqual(convert.to_cash_address('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC'),
'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq')
Expand Down Expand Up @@ -55,6 +59,7 @@ def test_to_cash_p2pkh_testnet(self):
def test_is_valid(self):
self.assertTrue(convert.is_valid('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'))
self.assertTrue(convert.is_valid('2MwikwR6hoVijCmr1u8UgzFMHFP6rpQyRvP'))
self.assertTrue(convert.is_valid('simpleledger:qqkv9wr69ry2p9l53lxp635va4h86wv435f7l4jp5f'))
self.assertFalse(convert.is_valid('bitcoincash:aqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'))
self.assertFalse(convert.is_valid('bitcoincash:qqqqqqqq9ry2p9l53lxp635va4h86wv435995w8p2h'))
self.assertFalse(convert.is_valid('22222wR6hoVijCmr1u8UgzFMHFP6rpQyRvP'))
Expand Down