Add SubAddress class
This commit is contained in:
parent
40fd464a12
commit
45516ec33f
|
@ -4,6 +4,9 @@ from sha3 import keccak_256
|
||||||
from . import base58
|
from . import base58
|
||||||
|
|
||||||
class Address(object):
|
class Address(object):
|
||||||
|
_valid_netbytes = (18, 53)
|
||||||
|
# NOTE: _valid_netbytes order is (real, testnet)
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, address):
|
||||||
address = str(address)
|
address = str(address)
|
||||||
if len(address) != 95:
|
if len(address) != 95:
|
||||||
|
@ -15,9 +18,13 @@ class Address(object):
|
||||||
checksum = self._decoded[-4:]
|
checksum = self._decoded[-4:]
|
||||||
if checksum != keccak_256(self._decoded[:-4]).digest()[:4]:
|
if checksum != keccak_256(self._decoded[:-4]).digest()[:4]:
|
||||||
raise ValueError("Invalid checksum")
|
raise ValueError("Invalid checksum")
|
||||||
|
if self._decoded[0] not in self._valid_netbytes:
|
||||||
|
raise ValueError("Invalid address netbyte {nb}. Allowed values are: {allowed}".format(
|
||||||
|
nb=hexlify(bytes(self._decoded[0])),
|
||||||
|
allowed=", ".join(map(lambda b: '%02x' % b, self._valid_netbytes))))
|
||||||
|
|
||||||
def is_testnet(self):
|
def is_testnet(self):
|
||||||
return self._decoded[0] in bytes([53, 54])
|
return self._decoded[0] == self._valid_netbytes[1]
|
||||||
|
|
||||||
def get_view_key(self):
|
def get_view_key(self):
|
||||||
return hexlify(self._decoded[33:65]).decode()
|
return hexlify(self._decoded[33:65]).decode()
|
||||||
|
@ -46,7 +53,16 @@ class Address(object):
|
||||||
return super()
|
return super()
|
||||||
|
|
||||||
|
|
||||||
|
class SubAddress(Address):
|
||||||
|
_valid_netbytes = (42, 63)
|
||||||
|
|
||||||
|
def with_payment_id(self):
|
||||||
|
raise TypeError("SubAddress cannot be merged with payment ID into IntegratedAddress")
|
||||||
|
|
||||||
|
|
||||||
class IntegratedAddress(Address):
|
class IntegratedAddress(Address):
|
||||||
|
_valid_netbytes = (19, 54)
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, address):
|
||||||
address = str(address)
|
address = str(address)
|
||||||
if len(address) != 106:
|
if len(address) != 106:
|
||||||
|
@ -66,7 +82,16 @@ class IntegratedAddress(Address):
|
||||||
def address(addr):
|
def address(addr):
|
||||||
addr = str(addr)
|
addr = str(addr)
|
||||||
if len(addr) == 95:
|
if len(addr) == 95:
|
||||||
return Address(addr)
|
netbyte = unhexlify(base58.decode(addr))[0]
|
||||||
|
if netbyte in Address._valid_netbytes:
|
||||||
|
return Address(addr)
|
||||||
|
elif netbyte in SubAddress._valid_netbytes:
|
||||||
|
return SubAddress(addr)
|
||||||
|
raise ValueError("Invalid address netbyte {nb}. Allowed values are: {allowed}".format(
|
||||||
|
nb=hexlify(self._decoded[0]),
|
||||||
|
allowed=", ".join(map(
|
||||||
|
lambda b: '%02x' % b,
|
||||||
|
sorted(Address._valid_netbytes + SubAddress._valid_netbytes)))))
|
||||||
elif len(addr) == 106:
|
elif len(addr) == 106:
|
||||||
return IntegratedAddress(addr)
|
return IntegratedAddress(addr)
|
||||||
raise ValueError("Address must be either 95 or 106 characters long")
|
raise ValueError("Address must be either 95 or 106 characters long")
|
||||||
|
|
|
@ -7,7 +7,7 @@ import requests
|
||||||
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..account import Account
|
from ..account import Account
|
||||||
from ..address import Address
|
from ..address import address
|
||||||
from ..numbers import from_atomic, to_atomic
|
from ..numbers import from_atomic, to_atomic
|
||||||
|
|
||||||
_log = logging.getLogger(__name__)
|
_log = logging.getLogger(__name__)
|
||||||
|
@ -79,7 +79,7 @@ class JSONRPC(object):
|
||||||
data = {
|
data = {
|
||||||
'account_index': account,
|
'account_index': account,
|
||||||
'destinations': list(map(
|
'destinations': list(map(
|
||||||
lambda dst: {'address': str(Address(dst[0])), 'amount': to_atomic(dst[1])},
|
lambda dst: {'address': str(address(dst[0])), 'amount': to_atomic(dst[1])},
|
||||||
destinations)),
|
destinations)),
|
||||||
'mixin': mixin,
|
'mixin': mixin,
|
||||||
'priority': priority,
|
'priority': priority,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from monero.address import Address, IntegratedAddress, address
|
from monero.address import Address, SubAddress, IntegratedAddress, address
|
||||||
|
|
||||||
class Tests(object):
|
class Tests(object):
|
||||||
def test_from_and_to_string(self):
|
def test_from_and_to_string(self):
|
||||||
|
@ -16,6 +16,9 @@ class Tests(object):
|
||||||
self.assertEqual(ia.get_view_key(), self.pvk)
|
self.assertEqual(ia.get_view_key(), self.pvk)
|
||||||
self.assertEqual(ia.get_base_address(), a)
|
self.assertEqual(ia.get_base_address(), a)
|
||||||
|
|
||||||
|
sa = SubAddress(self.subaddr)
|
||||||
|
self.assertEqual(str(sa), self.subaddr)
|
||||||
|
|
||||||
def test_payment_id(self):
|
def test_payment_id(self):
|
||||||
a = Address(self.addr)
|
a = Address(self.addr)
|
||||||
ia = a.with_payment_id(self.pid)
|
ia = a.with_payment_id(self.pid)
|
||||||
|
@ -43,6 +46,15 @@ class Tests(object):
|
||||||
self.assertEqual(ia2.is_testnet(), self.testnet)
|
self.assertEqual(ia2.is_testnet(), self.testnet)
|
||||||
self.assertEqual(ia2.get_base_address(), a)
|
self.assertEqual(ia2.get_base_address(), a)
|
||||||
|
|
||||||
|
sa = SubAddress(self.subaddr)
|
||||||
|
sa2 = address(self.subaddr)
|
||||||
|
self.assertIsInstance(sa2, SubAddress)
|
||||||
|
self.assertEqual(sa, sa2)
|
||||||
|
self.assertEqual(sa, self.subaddr)
|
||||||
|
self.assertEqual(self.subaddr, sa)
|
||||||
|
self.assertEqual(sa.is_testnet(), self.testnet)
|
||||||
|
self.assertEqual(sa2.is_testnet(), self.testnet)
|
||||||
|
|
||||||
def test_idempotence(self):
|
def test_idempotence(self):
|
||||||
a = Address(self.addr)
|
a = Address(self.addr)
|
||||||
a_idem = Address(a)
|
a_idem = Address(a)
|
||||||
|
@ -60,19 +72,41 @@ class Tests(object):
|
||||||
ia_idem = address(ia)
|
ia_idem = address(ia)
|
||||||
self.assertEqual(ia, ia_idem)
|
self.assertEqual(ia, ia_idem)
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
self.assertRaises(ValueError, Address, self.addr_invalid)
|
||||||
|
self.assertRaises(ValueError, Address, self.iaddr_invalid)
|
||||||
|
|
||||||
|
def test_type_mismatch(self):
|
||||||
|
self.assertRaises(ValueError, Address, self.iaddr)
|
||||||
|
self.assertRaises(ValueError, Address, self.subaddr)
|
||||||
|
self.assertRaises(ValueError, IntegratedAddress, self.addr)
|
||||||
|
self.assertRaises(ValueError, IntegratedAddress, self.subaddr)
|
||||||
|
self.assertRaises(ValueError, SubAddress, self.addr)
|
||||||
|
self.assertRaises(ValueError, SubAddress, self.iaddr)
|
||||||
|
|
||||||
|
def test_subaddress_cannot_into_integrated(self):
|
||||||
|
sa = SubAddress(self.subaddr)
|
||||||
|
self.assertRaises(TypeError, sa.with_payment_id, self.pid)
|
||||||
|
|
||||||
class AddressTestCase(unittest.TestCase, Tests):
|
class AddressTestCase(unittest.TestCase, Tests):
|
||||||
addr = '43aeKax1ts4BoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuh7PFUAmd'
|
addr = '43aeKax1ts4BoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuh7PFUAmd'
|
||||||
psk = '33a7ceb933b793408d49e82c0a34664a4be7117243cb77a64ef280b866d8aa6e'
|
psk = '33a7ceb933b793408d49e82c0a34664a4be7117243cb77a64ef280b866d8aa6e'
|
||||||
pvk = '96f70d63d9d3558b97a5dd200a170b4f45b3177a274aa90496ea683896ff6438'
|
pvk = '96f70d63d9d3558b97a5dd200a170b4f45b3177a274aa90496ea683896ff6438'
|
||||||
pid = '4a6f686e47616c74'
|
pid = '4a6f686e47616c74'
|
||||||
|
subaddr = '83bK2pMxCQXdRyd6W1haNWYRsF6Qb3iGa8gxKEynm9U7cYoXrMHFwRrFFuxRSgnLtGe7LM8SmrPY6L3TVBa3UV3YLuVJ7Rw'
|
||||||
iaddr = '4DHKLPmWW8aBoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuhAR6GpL18QNwE8h3TuF'
|
iaddr = '4DHKLPmWW8aBoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuhAR6GpL18QNwE8h3TuF'
|
||||||
testnet = False
|
testnet = False
|
||||||
|
addr_invalid = '43aeKax1ts4boEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuh7PFUAmd'
|
||||||
|
iaddr_invalid = '4DHKLpmWW8aBoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuhAR6GpL18QNwE8h3TuF'
|
||||||
|
|
||||||
|
|
||||||
class TestnetAddressTestCase(AddressTestCase, Tests):
|
class TestnetAddressTestCase(AddressTestCase, Tests):
|
||||||
addr = '9u9j6xG1GNu4ghrdUL35m5PQcJV69YF8731DSTDoh7pDgkBWz2LWNzncq7M5s1ARjPRhvGPX4dBUeC3xNj4wzfrjV6SY3e9'
|
addr = '9vgV48wWAPTWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YQN2Q9ag'
|
||||||
psk = '345b201b8d1ba216074e3c45ca606c85f68563f60d0b8c0bfab5123f80692aed'
|
psk = '5cbcfbcea7cc62b1aeb76758ad8df5f8cbe0c63d40c8cd9c49377bbc9c9b9520'
|
||||||
pvk = '9deb70cc7e1e23d635de2d5a3086a293b4580dc2b9133b4211bc09f22fadc4f9'
|
pvk = 'de048ca310ff7d6e3b6714bccdebd62c56d680a10272846c875241fa2c5fc1cf'
|
||||||
pid = '4a6f686e47616c74'
|
pid = '4a6f686e47616c74'
|
||||||
iaddr = 'A4rQ7m5VseR4ghrdUL35m5PQcJV69YF8731DSTDoh7pDgkBWz2LWNzncq7M5s1ARjPRhvGPX4dBUeC3xNj4wzfrjihS6W83Km1mE7W3kMa'
|
iaddr = 'A6PA4wkzmeyWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YbfyqvDecDn3E7cvp9b'
|
||||||
|
subaddr = 'BbBjyYoYNNwFfL8RRVRTMiZUofBLpjRxdNnd5E4LyGcAK5CEsnL3gmE5QkrDRta7RPficGHcFdR6rUwWcjnwZVvCE3tLxhJ'
|
||||||
testnet = True
|
testnet = True
|
||||||
|
addr_invalid = '9vgV48wWAPTWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUbbV6YQN2Q9ag'
|
||||||
|
iaddr_invalid = 'A6PA4wkzmeyWik5qSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YbfyqvDecDn3E7cvp9b'
|
||||||
|
|
Loading…
Reference in New Issue