Skip to content

Commit

Permalink
adding code base
Browse files Browse the repository at this point in the history
  • Loading branch information
oskyk committed Jan 10, 2018
1 parent 81f2c80 commit eedcb8b
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 0 deletions.
1 change: 1 addition & 0 deletions cashaddress/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pass
107 changes: 107 additions & 0 deletions cashaddress/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from crypto import *
from base58 import b58decode_check, b58encode_check


class InvalidAddress(Exception):
pass


class Address:
VERSION_MAP = {
'legacy': [
('P2SH', 5),
('P2PKH', 0)
],
'cash': [
('P2SH', 8),
('P2PKH', 0)
]
}
DEFAULT_PREFIX = 'bitcoincash'

def __init__(self, version, payload, prefix=None):
self.version = version
self.payload = payload
if prefix:
self.prefix = prefix
else:
self.prefix = self.DEFAULT_PREFIX

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)

@staticmethod
def code_list_to_string(code_list):
output = ''
for code in code_list:
output += chr(code)
return output

@staticmethod
def _address_type(address_type, version):
for mapping in Address.VERSION_MAP[address_type]:
if mapping[0] == version or mapping[1] == version:
return mapping
raise InvalidAddress('Could not determine address version')

@staticmethod
def from_string(address_string):
if ':' not in address_string:
return Address._legacy_string(address_string)
else:
return Address._cash_string(address_string)

@staticmethod
def _legacy_string(address_string):
try:
decoded = bytearray(b58decode_check(address_string))
except ValueError:
raise InvalidAddress('Could not decode legacy address')
version = Address._address_type('legacy', decoded[0])[0]
payload = list()
for letter in str(decoded[1:]):
payload.append(ord(letter))
return Address(version, payload)

@staticmethod
def _cash_string(address_string):
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()
if ':' not in address_string:
address_string = Address.DEFAULT_PREFIX + ':' + address_string
prefix, base32string = address_string.split(':')
decoded = b32decode(base32string)
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]
payload = converted[1:-6]
return Address(version, payload, prefix)


def to_cash_address(address):
return Address.from_string(address).cash_address()


def to_legacy_address(address):
return Address.from_string(address).legacy_address()


def is_valid(address):
try:
Address.from_string(address)
return True
except InvalidAddress:
return False
70 changes: 70 additions & 0 deletions cashaddress/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'


def polymod(values):
chk = 1
generator = [
(0x01, 0x98f2bc8e61),
(0x02, 0x79b76d99e2),
(0x04, 0xf33e5fb3c4),
(0x08, 0xae2eabe2a8),
(0x10, 0x1e4f43e470)]
for value in values:
top = chk >> 35
chk = ((chk & 0x07ffffffff) << 5) ^ value
for i in generator:
if top & i[0] != 0:
chk ^= i[1]
return chk ^ 1


def prefix_expand(prefix):
return [ord(x) & 0x1f for x in prefix] + [0]


def calculate_checksum(prefix, payload):
poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0])
out = list()
for i in range(8):
out.append((poly >> 5 * (7 - i)) & 0x1f)
return out


def verify_checksum(prefix, payload):
return polymod(prefix_expand(prefix) + payload) == 0


def b32decode(inputs):
out = list()
for letter in inputs:
out.append(CHARSET.find(letter))
return out


def b32encode(inputs):
out = ''
for char_code in inputs:
out += CHARSET[char_code]
return out


def convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
base58==0.2.5 --hash=sha256:b1c481f2b8cfbc31f77dafc0e9690d92f7c03dc01481a4512eb1914e91744856
15 changes: 15 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from distutils.core import setup
from setuptools import find_packages

setup(name='cashaddress',
version='1.0.0',
packages=find_packages(),
install_requires=['base58==0.2.5'],
description='Python tool for converty bitcoin cash legacy addresses',
author='Oskar Hladky',
author_email='oskyks1@gmail.com',
url='https://github.com/oskyk/cashaddress',
download_url='https://github.com/oskyk/cashaddress/archive/1.0.0.tar.gz',
python_requires='>=2.7,<3',
keywords=['bitcoincash', 'bch', 'address', 'cashaddress', 'legacy', 'convert']
)
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pass
32 changes: 32 additions & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from cashaddress import convert
import unittest


class TestConversion(unittest.TestCase):
def test_to_legacy_p2sh(self):
self.assertEqual(convert.to_legacy_address('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC'),
'3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC')
self.assertEqual(convert.to_legacy_address('bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq'),
'3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC')

def test_to_legacy_p2pkh(self):
self.assertEqual(convert.to_legacy_address('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4'),
'155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4')
self.assertEqual(convert.to_legacy_address('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'),
'155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4')

def test_to_cash_p2sh(self):
self.assertEqual(convert.to_cash_address('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC'),
'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq')
self.assertEqual(convert.to_cash_address('bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq'),
'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq')

def test_to_cash_p2pkh(self):
self.assertEqual(convert.to_cash_address('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4'),
'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h')
self.assertEqual(convert.to_cash_address('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'),
'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h')


if __name__ == '__main__':
unittest.main()

0 comments on commit eedcb8b

Please sign in to comment.