-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
base58==0.2.5 --hash=sha256:b1c481f2b8cfbc31f77dafc0e9690d92f7c03dc01481a4512eb1914e91744856 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |