Skip to content

Commit eedcb8b

Browse files
committed
adding code base
1 parent 81f2c80 commit eedcb8b

7 files changed

Lines changed: 227 additions & 0 deletions

File tree

cashaddress/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pass

cashaddress/convert.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from crypto import *
2+
from base58 import b58decode_check, b58encode_check
3+
4+
5+
class InvalidAddress(Exception):
6+
pass
7+
8+
9+
class Address:
10+
VERSION_MAP = {
11+
'legacy': [
12+
('P2SH', 5),
13+
('P2PKH', 0)
14+
],
15+
'cash': [
16+
('P2SH', 8),
17+
('P2PKH', 0)
18+
]
19+
}
20+
DEFAULT_PREFIX = 'bitcoincash'
21+
22+
def __init__(self, version, payload, prefix=None):
23+
self.version = version
24+
self.payload = payload
25+
if prefix:
26+
self.prefix = prefix
27+
else:
28+
self.prefix = self.DEFAULT_PREFIX
29+
30+
def __str__(self):
31+
return 'version: {}\npayload: {}\nprefix: {}'.format(self.version, self.payload, self.prefix)
32+
33+
def legacy_address(self):
34+
version_int = Address._address_type('legacy', self.version)[1]
35+
return b58encode_check(Address.code_list_to_string([version_int] + self.payload))
36+
37+
def cash_address(self):
38+
version_int = Address._address_type('cash', self.version)[1]
39+
payload = [version_int] + self.payload
40+
payload = convertbits(payload, 8, 5)
41+
checksum = calculate_checksum(self.prefix, payload)
42+
return self.prefix + ':' + b32encode(payload + checksum)
43+
44+
@staticmethod
45+
def code_list_to_string(code_list):
46+
output = ''
47+
for code in code_list:
48+
output += chr(code)
49+
return output
50+
51+
@staticmethod
52+
def _address_type(address_type, version):
53+
for mapping in Address.VERSION_MAP[address_type]:
54+
if mapping[0] == version or mapping[1] == version:
55+
return mapping
56+
raise InvalidAddress('Could not determine address version')
57+
58+
@staticmethod
59+
def from_string(address_string):
60+
if ':' not in address_string:
61+
return Address._legacy_string(address_string)
62+
else:
63+
return Address._cash_string(address_string)
64+
65+
@staticmethod
66+
def _legacy_string(address_string):
67+
try:
68+
decoded = bytearray(b58decode_check(address_string))
69+
except ValueError:
70+
raise InvalidAddress('Could not decode legacy address')
71+
version = Address._address_type('legacy', decoded[0])[0]
72+
payload = list()
73+
for letter in str(decoded[1:]):
74+
payload.append(ord(letter))
75+
return Address(version, payload)
76+
77+
@staticmethod
78+
def _cash_string(address_string):
79+
if address_string.upper() != address_string and address_string.lower() != address_string:
80+
raise InvalidAddress('Cash address contains uppercase and lowercase characters')
81+
address_string = address_string.lower()
82+
if ':' not in address_string:
83+
address_string = Address.DEFAULT_PREFIX + ':' + address_string
84+
prefix, base32string = address_string.split(':')
85+
decoded = b32decode(base32string)
86+
if not verify_checksum(prefix, decoded):
87+
raise InvalidAddress('Bad cash address checksum')
88+
converted = convertbits(decoded, 5, 8)
89+
version = Address._address_type('cash', converted[0])[0]
90+
payload = converted[1:-6]
91+
return Address(version, payload, prefix)
92+
93+
94+
def to_cash_address(address):
95+
return Address.from_string(address).cash_address()
96+
97+
98+
def to_legacy_address(address):
99+
return Address.from_string(address).legacy_address()
100+
101+
102+
def is_valid(address):
103+
try:
104+
Address.from_string(address)
105+
return True
106+
except InvalidAddress:
107+
return False

cashaddress/crypto.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
2+
3+
4+
def polymod(values):
5+
chk = 1
6+
generator = [
7+
(0x01, 0x98f2bc8e61),
8+
(0x02, 0x79b76d99e2),
9+
(0x04, 0xf33e5fb3c4),
10+
(0x08, 0xae2eabe2a8),
11+
(0x10, 0x1e4f43e470)]
12+
for value in values:
13+
top = chk >> 35
14+
chk = ((chk & 0x07ffffffff) << 5) ^ value
15+
for i in generator:
16+
if top & i[0] != 0:
17+
chk ^= i[1]
18+
return chk ^ 1
19+
20+
21+
def prefix_expand(prefix):
22+
return [ord(x) & 0x1f for x in prefix] + [0]
23+
24+
25+
def calculate_checksum(prefix, payload):
26+
poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0])
27+
out = list()
28+
for i in range(8):
29+
out.append((poly >> 5 * (7 - i)) & 0x1f)
30+
return out
31+
32+
33+
def verify_checksum(prefix, payload):
34+
return polymod(prefix_expand(prefix) + payload) == 0
35+
36+
37+
def b32decode(inputs):
38+
out = list()
39+
for letter in inputs:
40+
out.append(CHARSET.find(letter))
41+
return out
42+
43+
44+
def b32encode(inputs):
45+
out = ''
46+
for char_code in inputs:
47+
out += CHARSET[char_code]
48+
return out
49+
50+
51+
def convertbits(data, frombits, tobits, pad=True):
52+
acc = 0
53+
bits = 0
54+
ret = []
55+
maxv = (1 << tobits) - 1
56+
max_acc = (1 << (frombits + tobits - 1)) - 1
57+
for value in data:
58+
if value < 0 or (value >> frombits):
59+
return None
60+
acc = ((acc << frombits) | value) & max_acc
61+
bits += frombits
62+
while bits >= tobits:
63+
bits -= tobits
64+
ret.append((acc >> bits) & maxv)
65+
if pad:
66+
if bits:
67+
ret.append((acc << (tobits - bits)) & maxv)
68+
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
69+
return None
70+
return ret

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
base58==0.2.5 --hash=sha256:b1c481f2b8cfbc31f77dafc0e9690d92f7c03dc01481a4512eb1914e91744856

setup.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from distutils.core import setup
2+
from setuptools import find_packages
3+
4+
setup(name='cashaddress',
5+
version='1.0.0',
6+
packages=find_packages(),
7+
install_requires=['base58==0.2.5'],
8+
description='Python tool for converty bitcoin cash legacy addresses',
9+
author='Oskar Hladky',
10+
author_email='oskyks1@gmail.com',
11+
url='https://github.com/oskyk/cashaddress',
12+
download_url='https://github.com/oskyk/cashaddress/archive/1.0.0.tar.gz',
13+
python_requires='>=2.7,<3',
14+
keywords=['bitcoincash', 'bch', 'address', 'cashaddress', 'legacy', 'convert']
15+
)

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pass

tests/test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from cashaddress import convert
2+
import unittest
3+
4+
5+
class TestConversion(unittest.TestCase):
6+
def test_to_legacy_p2sh(self):
7+
self.assertEqual(convert.to_legacy_address('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC'),
8+
'3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC')
9+
self.assertEqual(convert.to_legacy_address('bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq'),
10+
'3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC')
11+
12+
def test_to_legacy_p2pkh(self):
13+
self.assertEqual(convert.to_legacy_address('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4'),
14+
'155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4')
15+
self.assertEqual(convert.to_legacy_address('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'),
16+
'155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4')
17+
18+
def test_to_cash_p2sh(self):
19+
self.assertEqual(convert.to_cash_address('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC'),
20+
'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq')
21+
self.assertEqual(convert.to_cash_address('bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq'),
22+
'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq')
23+
24+
def test_to_cash_p2pkh(self):
25+
self.assertEqual(convert.to_cash_address('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4'),
26+
'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h')
27+
self.assertEqual(convert.to_cash_address('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h'),
28+
'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h')
29+
30+
31+
if __name__ == '__main__':
32+
unittest.main()

0 commit comments

Comments
 (0)