Reformat code with black

pycryptodomex
Michał Sałaban 3 years ago
parent be11c3591e
commit 102449f57f

@ -15,6 +15,7 @@ class Account(object):
:param index: the account's index within the wallet :param index: the account's index within the wallet
:param label: optional account label as `str` :param label: optional account label as `str`
""" """
index = None index = None
wallet = None wallet = None
label = None label = None
@ -23,8 +24,8 @@ class Account(object):
self.index = index self.index = index
self.label = label self.label = label
self._backend = backend self._backend = backend
self.incoming = PaymentManager(index, backend, 'in') self.incoming = PaymentManager(index, backend, "in")
self.outgoing = PaymentManager(index, backend, 'out') self.outgoing = PaymentManager(index, backend, "out")
def balances(self): def balances(self):
""" """
@ -69,9 +70,15 @@ class Account(object):
""" """
return self._backend.new_address(account=self.index, label=label) return self._backend.new_address(account=self.index, label=label)
def transfer(self, address, amount, def transfer(
priority=const.PRIO_NORMAL, payment_id=None, unlock_time=0, self,
relay=True): address,
amount,
priority=const.PRIO_NORMAL,
payment_id=None,
unlock_time=0,
relay=True,
):
""" """
Sends a transfer. Returns a list of resulting transactions. Sends a transfer. Returns a list of resulting transactions.
@ -95,11 +102,17 @@ class Account(object):
payment_id, payment_id,
unlock_time, unlock_time,
account=self.index, account=self.index,
relay=relay) relay=relay,
)
def transfer_multiple(self, destinations, def transfer_multiple(
priority=const.PRIO_NORMAL, payment_id=None, unlock_time=0, self,
relay=True): destinations,
priority=const.PRIO_NORMAL,
payment_id=None,
unlock_time=0,
relay=True,
):
""" """
Sends a batch of transfers. Returns a list of resulting transactions. Sends a batch of transfers. Returns a list of resulting transactions.
@ -124,10 +137,18 @@ class Account(object):
payment_id, payment_id,
unlock_time, unlock_time,
account=self.index, account=self.index,
relay=relay) relay=relay,
)
def sweep_all(self, address, priority=const.PRIO_NORMAL, payment_id=None, def sweep_all(
subaddr_indices=None, unlock_time=0, relay=True): self,
address,
priority=const.PRIO_NORMAL,
payment_id=None,
subaddr_indices=None,
unlock_time=0,
relay=True,
):
""" """
Sends all unlocked balance to an address. Returns a list of resulting transactions. Sends all unlocked balance to an address. Returns a list of resulting transactions.
@ -153,4 +174,5 @@ class Account(object):
subaddr_indices, subaddr_indices,
unlock_time, unlock_time,
account=self.index, account=self.index,
relay=relay) relay=relay,
)

@ -10,8 +10,13 @@ from . import const
from . import ed25519 from . import ed25519
from . import numbers from . import numbers
_ADDR_REGEX = re.compile(r'^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{95}$') _ADDR_REGEX = re.compile(
_IADDR_REGEX = re.compile(r'^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{106}$') r"^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{95}$"
)
_IADDR_REGEX = re.compile(
r"^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{106}$"
)
class BaseAddress(object): class BaseAddress(object):
label = None label = None
@ -19,8 +24,10 @@ class BaseAddress(object):
def __init__(self, addr, label=None): def __init__(self, addr, label=None):
addr = addr.decode() if isinstance(addr, bytes) else str(addr) addr = addr.decode() if isinstance(addr, bytes) else str(addr)
if not _ADDR_REGEX.match(addr): if not _ADDR_REGEX.match(addr):
raise ValueError("Address must be 95 characters long base58-encoded string, " raise ValueError(
"is {addr} ({len} chars length)".format(addr=addr, len=len(addr))) "Address must be 95 characters long base58-encoded string, "
"is {addr} ({len} chars length)".format(addr=addr, len=len(addr))
)
self._decode(addr) self._decode(addr)
self.label = label or self.label self.label = label or self.label
@ -48,9 +55,12 @@ class BaseAddress(object):
if checksum != keccak_256(self._decoded[:-4]).digest()[:4]: if checksum != keccak_256(self._decoded[:-4]).digest()[:4]:
raise ValueError("Invalid checksum in address {}".format(address)) raise ValueError("Invalid checksum in address {}".format(address))
if self._decoded[0] not in self._valid_netbytes: if self._decoded[0] not in self._valid_netbytes:
raise ValueError("Invalid address netbyte {nb}. Allowed values are: {allowed}".format( raise ValueError(
"Invalid address netbyte {nb}. Allowed values are: {allowed}".format(
nb=self._decoded[0], nb=self._decoded[0],
allowed=", ".join(map(lambda b: '%02x' % b, self._valid_netbytes)))) allowed=", ".join(map(lambda b: "%02x" % b, self._valid_netbytes)),
)
)
def __repr__(self): def __repr__(self):
return base58.encode(hexlify(self._decoded)) return base58.encode(hexlify(self._decoded))
@ -77,6 +87,7 @@ class Address(BaseAddress):
:param address: a Monero address as string-like object :param address: a Monero address as string-like object
:param label: a label for the address (defaults to `None`) :param label: a label for the address (defaults to `None`)
""" """
_valid_netbytes = const.MASTERADDR_NETBYTES _valid_netbytes = const.MASTERADDR_NETBYTES
def check_private_view_key(self, key): def check_private_view_key(self, key):
@ -104,9 +115,17 @@ class Address(BaseAddress):
""" """
payment_id = numbers.PaymentID(payment_id) payment_id = numbers.PaymentID(payment_id)
if not payment_id.is_short(): if not payment_id.is_short():
raise TypeError("Payment ID {0} has more than 64 bits and cannot be integrated".format(payment_id)) raise TypeError(
"Payment ID {0} has more than 64 bits and cannot be integrated".format(
payment_id
)
)
prefix = const.INTADDRR_NETBYTES[const.NETS.index(self.net)] prefix = const.INTADDRR_NETBYTES[const.NETS.index(self.net)]
data = bytearray([prefix]) + self._decoded[1:65] + struct.pack('>Q', int(payment_id)) data = (
bytearray([prefix])
+ self._decoded[1:65]
+ struct.pack(">Q", int(payment_id))
)
checksum = bytearray(keccak_256(data).digest()[:4]) checksum = bytearray(keccak_256(data).digest()[:4])
return IntegratedAddress(base58.encode(hexlify(data + checksum))) return IntegratedAddress(base58.encode(hexlify(data + checksum)))
@ -134,8 +153,10 @@ class IntegratedAddress(Address):
def __init__(self, address): def __init__(self, address):
address = address.decode() if isinstance(address, bytes) else str(address) address = address.decode() if isinstance(address, bytes) else str(address)
if not _IADDR_REGEX.match(address): if not _IADDR_REGEX.match(address):
raise ValueError("Integrated address must be 106 characters long base58-encoded string, " raise ValueError(
"is {addr} ({len} chars length)".format(addr=address, len=len(address))) "Integrated address must be 106 characters long base58-encoded string, "
"is {addr} ({len} chars length)".format(addr=address, len=len(address))
)
self._decode(address) self._decode(address)
def payment_id(self): def payment_id(self):
@ -170,12 +191,20 @@ def address(addr, label=None):
return Address(addr, label=label) return Address(addr, label=label)
elif netbyte in SubAddress._valid_netbytes: elif netbyte in SubAddress._valid_netbytes:
return SubAddress(addr, label=label) return SubAddress(addr, label=label)
raise ValueError("Invalid address netbyte {nb:x}. Allowed values are: {allowed}".format( raise ValueError(
"Invalid address netbyte {nb:x}. Allowed values are: {allowed}".format(
nb=netbyte, nb=netbyte,
allowed=", ".join(map( allowed=", ".join(
lambda b: '%02x' % b, map(
sorted(Address._valid_netbytes + SubAddress._valid_netbytes))))) lambda b: "%02x" % b,
sorted(Address._valid_netbytes + SubAddress._valid_netbytes),
)
),
)
)
elif _IADDR_REGEX.match(addr): elif _IADDR_REGEX.match(addr):
return IntegratedAddress(addr) return IntegratedAddress(addr)
raise ValueError("Address must be either 95 or 106 characters long base58-encoded string, " raise ValueError(
"is {addr} ({len} chars length)".format(addr=addr, len=len(addr))) "Address must be either 95 or 106 characters long base58-encoded string, "
"is {addr} ({len} chars length)".format(addr=addr, len=len(addr))
)

File diff suppressed because it is too large Load Diff

@ -1,5 +1,6 @@
from ... import exceptions from ... import exceptions
class RPCError(exceptions.BackendException): class RPCError(exceptions.BackendException):
pass pass
@ -11,5 +12,6 @@ class Unauthorized(RPCError):
class MethodNotFound(RPCError): class MethodNotFound(RPCError):
pass pass
class RestrictedRPC(RPCError): class RestrictedRPC(RPCError):
pass pass

@ -16,6 +16,7 @@ from .exceptions import RPCError, Unauthorized, MethodNotFound
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
class JSONRPCWallet(object): class JSONRPCWallet(object):
""" """
JSON RPC backend for Monero wallet (``monero-wallet-rpc``) JSON RPC backend for Monero wallet (``monero-wallet-rpc``)
@ -30,122 +31,147 @@ class JSONRPCWallet(object):
:param verify_ssl_certs: verify ssl certs for request :param verify_ssl_certs: verify ssl certs for request
:param proxy_url: a proxy to use :param proxy_url: a proxy to use
""" """
_master_address = None _master_address = None
def __init__(self, protocol='http', host='127.0.0.1', port=18088, path='/json_rpc', def __init__(
user='', password='', timeout=30, verify_ssl_certs=True, proxy_url=None): self,
self.url = '{protocol}://{host}:{port}/json_rpc'.format( protocol="http",
protocol=protocol, host="127.0.0.1",
host=host, port=18088,
port=port) path="/json_rpc",
user="",
password="",
timeout=30,
verify_ssl_certs=True,
proxy_url=None,
):
self.url = "{protocol}://{host}:{port}/json_rpc".format(
protocol=protocol, host=host, port=port
)
_log.debug("JSONRPC wallet backend URL: {url}".format(url=self.url)) _log.debug("JSONRPC wallet backend URL: {url}".format(url=self.url))
self.user = user self.user = user
self.password = password self.password = password
self.timeout = timeout self.timeout = timeout
self.verify_ssl_certs = verify_ssl_certs self.verify_ssl_certs = verify_ssl_certs
self.proxies = {protocol: proxy_url} self.proxies = {protocol: proxy_url}
_log.debug("JSONRPC wallet backend auth: '{user}'/'{stars}'".format( _log.debug(
user=user, stars=('*' * len(password)) if password else '')) "JSONRPC wallet backend auth: '{user}'/'{stars}'".format(
user=user, stars=("*" * len(password)) if password else ""
)
)
def height(self): def height(self):
return self.raw_request('getheight')['height'] return self.raw_request("getheight")["height"]
def spend_key(self): def spend_key(self):
return self.raw_request('query_key', {'key_type': 'spend_key'})['key'] return self.raw_request("query_key", {"key_type": "spend_key"})["key"]
def view_key(self): def view_key(self):
return self.raw_request('query_key', {'key_type': 'view_key'})['key'] return self.raw_request("query_key", {"key_type": "view_key"})["key"]
def seed(self): def seed(self):
return Seed(self.raw_request('query_key', {'key_type': 'mnemonic'})['key']) return Seed(self.raw_request("query_key", {"key_type": "mnemonic"})["key"])
def accounts(self): def accounts(self):
accounts = [] accounts = []
_accounts = self.raw_request('get_accounts') _accounts = self.raw_request("get_accounts")
idx = 0 idx = 0
self._master_address = Address(_accounts['subaddress_accounts'][0]['base_address']) self._master_address = Address(
for _acc in _accounts['subaddress_accounts']: _accounts["subaddress_accounts"][0]["base_address"]
assert idx == _acc['account_index'] )
accounts.append(Account(self, _acc['account_index'], label=_acc.get('label'))) for _acc in _accounts["subaddress_accounts"]:
assert idx == _acc["account_index"]
accounts.append(
Account(self, _acc["account_index"], label=_acc.get("label"))
)
idx += 1 idx += 1
return accounts return accounts
def new_account(self, label=None): def new_account(self, label=None):
_account = self.raw_request('create_account', {'label': label}) _account = self.raw_request("create_account", {"label": label})
# NOTE: the following should re-read label by _account.get('label') but the RPC # NOTE: the following should re-read label by _account.get('label') but the RPC
# doesn't return that detail here # doesn't return that detail here
return Account(self, _account['account_index'], label=label), SubAddress(_account['address']) return Account(self, _account["account_index"], label=label), SubAddress(
_account["address"]
)
def addresses(self, account=0, addr_indices=None): def addresses(self, account=0, addr_indices=None):
qdata = {'account_index': account} qdata = {"account_index": account}
if addr_indices: if addr_indices:
qdata['address_index'] = addr_indices qdata["address_index"] = addr_indices
_addresses = self.raw_request('getaddress', qdata) _addresses = self.raw_request("getaddress", qdata)
addresses = [None] * (max(map(operator.itemgetter('address_index'), _addresses['addresses'])) + 1) addresses = [None] * (
for _addr in _addresses['addresses']: max(map(operator.itemgetter("address_index"), _addresses["addresses"])) + 1
addresses[_addr['address_index']] = address( )
_addr['address'], for _addr in _addresses["addresses"]:
label=_addr.get('label', None)) addresses[_addr["address_index"]] = address(
_addr["address"], label=_addr.get("label", None)
)
return addresses return addresses
def new_address(self, account=0, label=None): def new_address(self, account=0, label=None):
_address = self.raw_request( _address = self.raw_request(
'create_address', {'account_index': account, 'label': label}) "create_address", {"account_index": account, "label": label}
return SubAddress(_address['address']), _address['address_index'] )
return SubAddress(_address["address"]), _address["address_index"]
def balances(self, account=0): def balances(self, account=0):
_balance = self.raw_request('getbalance', {'account_index': account}) _balance = self.raw_request("getbalance", {"account_index": account})
return (from_atomic(_balance['balance']), from_atomic(_balance['unlocked_balance'])) return (
from_atomic(_balance["balance"]),
from_atomic(_balance["unlocked_balance"]),
)
def transfers_in(self, account, pmtfilter): def transfers_in(self, account, pmtfilter):
params = {'account_index': account, 'pending': False} params = {"account_index": account, "pending": False}
method = 'get_transfers' method = "get_transfers"
if pmtfilter.tx_ids: if pmtfilter.tx_ids:
method = 'get_transfer_by_txid' method = "get_transfer_by_txid"
if pmtfilter.unconfirmed: if pmtfilter.unconfirmed:
params['in'] = pmtfilter.confirmed params["in"] = pmtfilter.confirmed
params['out'] = False params["out"] = False
params['pool'] = True params["pool"] = True
else: else:
if pmtfilter.payment_ids: if pmtfilter.payment_ids:
method = 'get_bulk_payments' method = "get_bulk_payments"
params['payment_ids'] = list(map(str, pmtfilter.payment_ids)) params["payment_ids"] = list(map(str, pmtfilter.payment_ids))
else: else:
params['in'] = pmtfilter.confirmed params["in"] = pmtfilter.confirmed
params['out'] = False params["out"] = False
params['pool'] = False params["pool"] = False
if method == 'get_transfers': if method == "get_transfers":
if pmtfilter.min_height: if pmtfilter.min_height:
# NOTE: the API uses (min, max] range which is confusing # NOTE: the API uses (min, max] range which is confusing
params['min_height'] = pmtfilter.min_height - 1 params["min_height"] = pmtfilter.min_height - 1
params['filter_by_height'] = True params["filter_by_height"] = True
if pmtfilter.max_height: if pmtfilter.max_height:
params['max_height'] = pmtfilter.max_height params["max_height"] = pmtfilter.max_height
params['filter_by_height'] = True params["filter_by_height"] = True
_pmts = self.raw_request(method, params) _pmts = self.raw_request(method, params)
pmts = _pmts.get('in', []) pmts = _pmts.get("in", [])
elif method == 'get_transfer_by_txid': elif method == "get_transfer_by_txid":
pmts = [] pmts = []
for txid in pmtfilter.tx_ids: for txid in pmtfilter.tx_ids:
params['txid'] = txid params["txid"] = txid
try: try:
_pmts = self.raw_request(method, params, squelch_error_logging=True) _pmts = self.raw_request(method, params, squelch_error_logging=True)
except exceptions.TransactionNotFound: except exceptions.TransactionNotFound:
continue continue
pmts.extend(_pmts['transfers']) pmts.extend(_pmts["transfers"])
# Issue #71: incoming payments to self will have excess 'destinations' key. Remove. # Issue #71: incoming payments to self will have excess 'destinations' key. Remove.
for pmt in pmts: for pmt in pmts:
try: try:
del pmt['destinations'] del pmt["destinations"]
except KeyError: except KeyError:
pass pass
else: else:
# NOTE: the API uses (min, max] range which is confusing # NOTE: the API uses (min, max] range which is confusing
params['min_block_height'] = (pmtfilter.min_height or 1) - 1 params["min_block_height"] = (pmtfilter.min_height or 1) - 1
_pmts = self.raw_request(method, params) _pmts = self.raw_request(method, params)
pmts = _pmts.get('payments', []) pmts = _pmts.get("payments", [])
if pmtfilter.unconfirmed: if pmtfilter.unconfirmed:
pmts.extend(_pmts.get('pool', [])) pmts.extend(_pmts.get("pool", []))
return list(pmtfilter.filter(map(self._inpayment, pmts))) return list(pmtfilter.filter(map(self._inpayment, pmts)))
def transfers_out(self, account, pmtfilter): def transfers_out(self, account, pmtfilter):
@ -154,45 +180,51 @@ class JSONRPCWallet(object):
for txid in pmtfilter.tx_ids: for txid in pmtfilter.tx_ids:
try: try:
_pmts = self.raw_request( _pmts = self.raw_request(
'get_transfer_by_txid', "get_transfer_by_txid",
{'account_index': account, 'txid': txid}, {"account_index": account, "txid": txid},
squelch_error_logging=True) squelch_error_logging=True,
)
except exceptions.TransactionNotFound: except exceptions.TransactionNotFound:
continue continue
pmts.extend(_pmts['transfers']) pmts.extend(_pmts["transfers"])
else: else:
_pmts = self.raw_request('get_transfers', { _pmts = self.raw_request(
'account_index': account, "get_transfers",
'in': False, {
'out': pmtfilter.confirmed, "account_index": account,
'pool': False, "in": False,
'pending': pmtfilter.unconfirmed}) "out": pmtfilter.confirmed,
pmts = _pmts.get('out', []) "pool": False,
"pending": pmtfilter.unconfirmed,
},
)
pmts = _pmts.get("out", [])
if pmtfilter.unconfirmed: if pmtfilter.unconfirmed:
pmts.extend(_pmts.get('pending', [])) pmts.extend(_pmts.get("pending", []))
return list(pmtfilter.filter(map(self._outpayment, pmts))) return list(pmtfilter.filter(map(self._outpayment, pmts)))
def _paymentdict(self, data): def _paymentdict(self, data):
pid = data.get('payment_id', None) pid = data.get("payment_id", None)
laddr = data.get('address', None) laddr = data.get("address", None)
if laddr: if laddr:
laddr = address(laddr) laddr = address(laddr)
result = { result = {
'payment_id': None if pid is None else PaymentID(pid), "payment_id": None if pid is None else PaymentID(pid),
'amount': from_atomic(data['amount']), "amount": from_atomic(data["amount"]),
'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None, "timestamp": datetime.fromtimestamp(data["timestamp"])
'note': data.get('note', None), if "timestamp" in data
'transaction': self._tx(data), else None,
'local_address': laddr, "note": data.get("note", None),
"transaction": self._tx(data),
"local_address": laddr,
} }
if 'destinations' in data: if "destinations" in data:
result['destinations'] = [ result["destinations"] = [
(address(x['address']), from_atomic(x['amount'])) (address(x["address"]), from_atomic(x["amount"]))
for x in data.get('destinations') for x in data.get("destinations")
] ]
return result return result
def _inpayment(self, data): def _inpayment(self, data):
return IncomingPayment(**self._paymentdict(data)) return IncomingPayment(**self._paymentdict(data))
@ -200,123 +232,198 @@ class JSONRPCWallet(object):
return OutgoingPayment(**self._paymentdict(data)) return OutgoingPayment(**self._paymentdict(data))
def _tx(self, data): def _tx(self, data):
return Transaction(**{ return Transaction(
'hash': data.get('txid', data.get('tx_hash')), **{
'fee': from_atomic(data['fee']) if 'fee' in data else None, "hash": data.get("txid", data.get("tx_hash")),
'key': data.get('key'), "fee": from_atomic(data["fee"]) if "fee" in data else None,
'height': data.get('height', data.get('block_height')) or None, "key": data.get("key"),
'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None, "height": data.get("height", data.get("block_height")) or None,
'blob': binascii.unhexlify(data.get('blob', '')), "timestamp": datetime.fromtimestamp(data["timestamp"])
'confirmations': data.get('confirmations', None) if "timestamp" in data
}) else None,
"blob": binascii.unhexlify(data.get("blob", "")),
"confirmations": data.get("confirmations", None),
}
)
def export_outputs(self): def export_outputs(self):
return self.raw_request('export_outputs')['outputs_data_hex'] return self.raw_request("export_outputs")["outputs_data_hex"]
def import_outputs(self, outputs_hex): def import_outputs(self, outputs_hex):
return self.raw_request( return self.raw_request("import_outputs", {"outputs_data_hex": outputs_hex})[
'import_outputs', "num_imported"
{'outputs_data_hex': outputs_hex})['num_imported'] ]
def export_key_images(self): def export_key_images(self):
return self.raw_request('export_key_images')['signed_key_images'] return self.raw_request("export_key_images")["signed_key_images"]
def import_key_images(self, key_images): def import_key_images(self, key_images):
_data = self.raw_request( _data = self.raw_request("import_key_images", {"signed_key_images": key_images})
'import_key_images', return (
{'signed_key_images': key_images}) _data["height"],
return (_data['height'], from_atomic(_data['spent']), from_atomic(_data['unspent'])) from_atomic(_data["spent"]),
from_atomic(_data["unspent"]),
def transfer(self, destinations, priority, )
payment_id=None, unlock_time=0, account=0,
relay=True): def transfer(
self,
destinations,
priority,
payment_id=None,
unlock_time=0,
account=0,
relay=True,
):
data = { data = {
'account_index': account, "account_index": account,
'destinations': list(map( "destinations": list(
lambda dst: {'address': str(address(dst[0])), 'amount': to_atomic(dst[1])}, map(
destinations)), lambda dst: {
'priority': priority, "address": str(address(dst[0])),
'unlock_time': 0, "amount": to_atomic(dst[1]),
'get_tx_keys': True, },
'get_tx_hex': True, destinations,
'new_algorithm': True, )
'do_not_relay': not relay, ),
"priority": priority,
"unlock_time": 0,
"get_tx_keys": True,
"get_tx_hex": True,
"new_algorithm": True,
"do_not_relay": not relay,
} }
if payment_id is not None: if payment_id is not None:
data['payment_id'] = str(PaymentID(payment_id)) data["payment_id"] = str(PaymentID(payment_id))
_transfers = self.raw_request('transfer_split', data) _transfers = self.raw_request("transfer_split", data)
_pertx = [dict(_tx) for _tx in map( _pertx = [
lambda vs: zip(('txid', 'amount', 'fee', 'key', 'blob', 'payment_id'), vs), dict(_tx)
zip(*[_transfers[k] for k in ( for _tx in map(
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))] lambda vs: zip(
("txid", "amount", "fee", "key", "blob", "payment_id"), vs
),
zip(
*[
_transfers[k]
for k in (
"tx_hash_list",
"amount_list",
"fee_list",
"tx_key_list",
"tx_blob_list",
)
]
),
)
]
for d in _pertx: for d in _pertx:
d['payment_id'] = payment_id d["payment_id"] = payment_id
return [self._tx(data) for data in _pertx] return [self._tx(data) for data in _pertx]
def sweep_all(self, destination, priority, payment_id=None, subaddr_indices=None, def sweep_all(
unlock_time=0, account=0, relay=True): self,
destination,
priority,
payment_id=None,
subaddr_indices=None,
unlock_time=0,
account=0,
relay=True,
):
if not subaddr_indices: if not subaddr_indices:
# retrieve indices of all subaddresses with positive unlocked balance # retrieve indices of all subaddresses with positive unlocked balance
bals = self.raw_request('get_balance', {'account_index': account}) bals = self.raw_request("get_balance", {"account_index": account})
subaddr_indices = [] subaddr_indices = []
for subaddr in bals['per_subaddress']: for subaddr in bals["per_subaddress"]:
if subaddr.get('unlocked_balance', 0): if subaddr.get("unlocked_balance", 0):
subaddr_indices.append(subaddr['address_index']) subaddr_indices.append(subaddr["address_index"])
data = { data = {
'account_index': account, "account_index": account,
'address': str(address(destination)), "address": str(address(destination)),
'subaddr_indices': list(subaddr_indices), "subaddr_indices": list(subaddr_indices),
'priority': priority, "priority": priority,
'unlock_time': 0, "unlock_time": 0,
'get_tx_keys': True, "get_tx_keys": True,
'get_tx_hex': True, "get_tx_hex": True,
'do_not_relay': not relay, "do_not_relay": not relay,
} }
if payment_id is not None: if payment_id is not None:
data['payment_id'] = str(PaymentID(payment_id)) data["payment_id"] = str(PaymentID(payment_id))
_transfers = self.raw_request('sweep_all', data) _transfers = self.raw_request("sweep_all", data)
_pertx = [dict(_tx) for _tx in map( _pertx = [
lambda vs: zip(('txid', 'amount', 'fee', 'key', 'blob', 'payment_id'), vs), dict(_tx)
zip(*[_transfers[k] for k in ( for _tx in map(
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))] lambda vs: zip(
("txid", "amount", "fee", "key", "blob", "payment_id"), vs
),
zip(
*[
_transfers[k]
for k in (
"tx_hash_list",
"amount_list",
"fee_list",
"tx_key_list",
"tx_blob_list",
)
]
),
)
]
for d in _pertx: for d in _pertx:
d['payment_id'] = payment_id d["payment_id"] = payment_id
return list(zip( return list(
zip(
[self._tx(data) for data in _pertx], [self._tx(data) for data in _pertx],
map(from_atomic, _transfers['amount_list']))) map(from_atomic, _transfers["amount_list"]),
)
)
def raw_request(self, method, params=None, squelch_error_logging=False): def raw_request(self, method, params=None, squelch_error_logging=False):
hdr = {'Content-Type': 'application/json'} hdr = {"Content-Type": "application/json"}
data = {'jsonrpc': '2.0', 'id': 0, 'method': method, 'params': params or {}} data = {"jsonrpc": "2.0", "id": 0, "method": method, "params": params or {}}
_log.debug(u"Method: {method}\nParams:\n{params}".format( _log.debug(
method=method, "Method: {method}\nParams:\n{params}".format(
params=json.dumps(params, indent=2, sort_keys=True))) method=method, params=json.dumps(params, indent=2, sort_keys=True)
)
)
auth = requests.auth.HTTPDigestAuth(self.user, self.password) auth = requests.auth.HTTPDigestAuth(self.user, self.password)
rsp = requests.post( rsp = requests.post(
self.url, headers=hdr, data=json.dumps(data), auth=auth, self.url,
timeout=self.timeout, verify=self.verify_ssl_certs, proxies=self.proxies) headers=hdr,
data=json.dumps(data),
auth=auth,
timeout=self.timeout,
verify=self.verify_ssl_certs,
proxies=self.proxies,
)
if rsp.status_code == 401: if rsp.status_code == 401:
raise Unauthorized("401 Unauthorized. Invalid RPC user name or password.") raise Unauthorized("401 Unauthorized. Invalid RPC user name or password.")
elif rsp.status_code != 200: elif rsp.status_code != 200:
raise RPCError("Invalid HTTP status {code} for method {method}.".format( raise RPCError(
code=rsp.status_code, "Invalid HTTP status {code} for method {method}.".format(
method=method)) code=rsp.status_code, method=method
)
)
result = rsp.json() result = rsp.json()
_ppresult = json.dumps(result, indent=2, sort_keys=True) _ppresult = json.dumps(result, indent=2, sort_keys=True)
_log.debug(u"Result:\n{result}".format(result=_ppresult)) _log.debug("Result:\n{result}".format(result=_ppresult))
if 'error' in result: if "error" in result:
err = result['error'] err = result["error"]
if not squelch_error_logging: if not squelch_error_logging:
_log.error(u"JSON RPC error:\n{result}".format(result=_ppresult)) _log.error("JSON RPC error:\n{result}".format(result=_ppresult))
if err['code'] in _err2exc: if err["code"] in _err2exc:
raise _err2exc[err['code']](err['message']) raise _err2exc[err["code"]](err["message"])
else: else:
raise RPCError( raise RPCError(
"Method '{method}' failed with RPC Error of unknown code {code}, " "Method '{method}' failed with RPC Error of unknown code {code}, "
"message: {message}".format(method=method, data=data, result=result, **err)) "message: {message}".format(
return result['result'] method=method, data=data, result=result, **err
)
)
return result["result"]
_err2exc = { _err2exc = {
-2: exceptions.WrongAddress, -2: exceptions.WrongAddress,

@ -13,6 +13,7 @@ class OfflineWallet(object):
""" """
Offline backend for Monero wallet. Provides support for address generation. Offline backend for Monero wallet. Provides support for address generation.
""" """
_address = None _address = None
_svk = None _svk = None
_ssk = EMPTY_KEY _ssk = EMPTY_KEY

@ -9,9 +9,11 @@
# + optimized # + optimized
# + proper exceptions instead of returning errors as results # + proper exceptions instead of returning errors as results
__alphabet = [ord(s) for s in '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'] __alphabet = [
ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
]
__b58base = 58 __b58base = 58
__UINT64MAX = 2**64 __UINT64MAX = 2 ** 64
__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11] __encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]
__fullBlockSize = 8 __fullBlockSize = 8
__fullEncodedBlockSize = 11 __fullEncodedBlockSize = 11
@ -20,11 +22,11 @@ __fullEncodedBlockSize = 11
def _hexToBin(hex_): def _hexToBin(hex_):
if len(hex_) % 2 != 0: if len(hex_) % 2 != 0:
raise ValueError("Hex string has invalid length: %d" % len(hex_)) raise ValueError("Hex string has invalid length: %d" % len(hex_))
return [int(hex_[i:i + 2], 16) for i in range(0, len(hex_), 2)] return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)]
def _binToHex(bin_): def _binToHex(bin_):
return "".join('%02x' % int(b) for b in bin_) return "".join("%02x" % int(b) for b in bin_)
def _uint8be_to_64(data): def _uint8be_to_64(data):
@ -42,8 +44,8 @@ def _uint64_to_8be(num, size):
raise ValueError("Invalid input length: %d" % size) raise ValueError("Invalid input length: %d" % size)
res = [0] * size res = [0] * size
twopow8 = 2**8 twopow8 = 2 ** 8
for i in range(size-1,-1,-1): for i in range(size - 1, -1, -1):
res[i] = num % twopow8 res[i] = num % twopow8
num = num // twopow8 num = num // twopow8
@ -62,14 +64,14 @@ def encode_block(data, buf, index):
while num > 0: while num > 0:
remainder = num % __b58base remainder = num % __b58base
num = num // __b58base num = num // __b58base
buf[index+i] = __alphabet[remainder] buf[index + i] = __alphabet[remainder]
i -= 1 i -= 1
return buf return buf
def encode(hex): def encode(hex):
'''Encode hexadecimal string as base58 (ex: encoding a Monero address).''' """Encode hexadecimal string as base58 (ex: encoding a Monero address)."""
data = _hexToBin(hex) data = _hexToBin(hex)
l_data = len(data) l_data = len(data)
@ -78,17 +80,31 @@ def encode(hex):
full_block_count = l_data // __fullBlockSize full_block_count = l_data // __fullBlockSize
last_block_size = l_data % __fullBlockSize last_block_size = l_data % __fullBlockSize
res_size = full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size] res_size = (
full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size]
)
res = bytearray([__alphabet[0]] * res_size) res = bytearray([__alphabet[0]] * res_size)
for i in range(full_block_count): for i in range(full_block_count):
res = encode_block(data[(i*__fullBlockSize):(i*__fullBlockSize+__fullBlockSize)], res, i * __fullEncodedBlockSize) res = encode_block(
data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)],
res,
i * __fullEncodedBlockSize,
)
if last_block_size > 0: if last_block_size > 0:
res = encode_block(data[(full_block_count*__fullBlockSize):(full_block_count*__fullBlockSize+last_block_size)], res, full_block_count * __fullEncodedBlockSize) res = encode_block(
data[
(full_block_count * __fullBlockSize) : (
full_block_count * __fullBlockSize + last_block_size
)
],
res,
full_block_count * __fullEncodedBlockSize,
)
return bytes(res).decode('ascii') return bytes(res).decode("ascii")
def decode_block(data, buf, index): def decode_block(data, buf, index):
@ -103,30 +119,32 @@ def decode_block(data, buf, index):
res_num = 0 res_num = 0
order = 1 order = 1
for i in range(l_data-1, -1, -1): for i in range(l_data - 1, -1, -1):
digit = __alphabet.index(data[i]) digit = __alphabet.index(data[i])
if digit < 0: if digit < 0:
raise ValueError("Invalid symbol: %s" % data[i]) raise ValueError("Invalid symbol: %s" % data[i])
product = order * digit + res_num product = order * digit + res_num
if product > __UINT64MAX: if product > __UINT64MAX:
raise ValueError("Overflow: %d * %d + %d = %d" % (order, digit, res_num, product)) raise ValueError(
"Overflow: %d * %d + %d = %d" % (order, digit, res_num, product)
)
res_num = product res_num = product
order = order * __b58base order = order * __b58base
if res_size < __fullBlockSize and 2**(8 * res_size) <= res_num: if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num:
raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size)) raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size))
tmp_buf = _uint64_to_8be(res_num, res_size) tmp_buf = _uint64_to_8be(res_num, res_size)
buf[index:index + len(tmp_buf)] = tmp_buf buf[index : index + len(tmp_buf)] = tmp_buf
return buf return buf
def decode(enc): def decode(enc):
'''Decode a base58 string (ex: a Monero address) into hexidecimal form.''' """Decode a base58 string (ex: a Monero address) into hexidecimal form."""
enc = bytearray(enc, encoding='ascii') enc = bytearray(enc, encoding="ascii")
l_enc = len(enc) l_enc = len(enc)
if l_enc == 0: if l_enc == 0:
@ -143,9 +161,25 @@ def decode(enc):
data = bytearray(data_size) data = bytearray(data_size)
for i in range(full_block_count): for i in range(full_block_count):
data = decode_block(enc[(i*__fullEncodedBlockSize):(i*__fullEncodedBlockSize+__fullEncodedBlockSize)], data, i * __fullBlockSize) data = decode_block(
enc[
(i * __fullEncodedBlockSize) : (
i * __fullEncodedBlockSize + __fullEncodedBlockSize
)
],
data,
i * __fullBlockSize,
)
if last_block_size > 0: if last_block_size > 0:
data = decode_block(enc[(full_block_count*__fullEncodedBlockSize):(full_block_count*__fullEncodedBlockSize+last_block_size)], data, full_block_count * __fullBlockSize) data = decode_block(
enc[
(full_block_count * __fullEncodedBlockSize) : (
full_block_count * __fullEncodedBlockSize + last_block_size
)
],
data,
full_block_count * __fullBlockSize,
)
return _binToHex(data) return _binToHex(data)

@ -10,6 +10,7 @@ class Block(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
hash = None hash = None
height = None height = None
timestamp = None timestamp = None
@ -24,11 +25,20 @@ class Block(object):
transactions = None transactions = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
for k in ('hash', 'height', 'timestamp', 'version', 'difficulty', 'nonce', 'prev_hash', 'reward'): for k in (
"hash",
"height",
"timestamp",
"version",
"difficulty",
"nonce",
"prev_hash",
"reward",
):
setattr(self, k, kwargs.get(k, getattr(self, k))) setattr(self, k, kwargs.get(k, getattr(self, k)))
self.orphan = kwargs.get('orphan', self.orphan) self.orphan = kwargs.get("orphan", self.orphan)
self.transactions = kwargs.get('transactions', self.transactions or []) self.transactions = kwargs.get("transactions", self.transactions or [])
self.blob = kwargs.get('blob', self.blob) self.blob = kwargs.get("blob", self.blob)
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, Block): if isinstance(other, Block):
@ -45,5 +55,6 @@ class Block(object):
else: else:
raise ValueError( raise ValueError(
"Only Transaction or hash strings may be used to test existence in Blocks, " "Only Transaction or hash strings may be used to test existence in Blocks, "
"got '{:s}'".format(tx)) "got '{:s}'".format(tx)
return txid in map(operator.attrgetter('hash'), self.transactions) )
return txid in map(operator.attrgetter("hash"), self.transactions)

@ -2,6 +2,7 @@ import six
from .backends.jsonrpc import JSONRPCDaemon from .backends.jsonrpc import JSONRPCDaemon
class Daemon(object): class Daemon(object):
"""Monero daemon. """Monero daemon.
@ -11,9 +12,10 @@ class Daemon(object):
:param \\**kwargs: arguments to initialize a :class:`JSONRPCDaemon <monero.backends.jsonrpc.JSONRPCDaemon>` :param \\**kwargs: arguments to initialize a :class:`JSONRPCDaemon <monero.backends.jsonrpc.JSONRPCDaemon>`
instance if no backend is given instance if no backend is given
""" """
def __init__(self, backend=None, **kwargs): def __init__(self, backend=None, **kwargs):
if backend and len(kwargs): if backend and len(kwargs):
raise ValueError('backend already given, other arguments are extraneous') raise ValueError("backend already given, other arguments are extraneous")
self._backend = backend if backend else JSONRPCDaemon(**kwargs) self._backend = backend if backend else JSONRPCDaemon(**kwargs)
@ -35,7 +37,7 @@ class Daemon(object):
:rtype: int :rtype: int
""" """
return self._backend.info()['height'] return self._backend.info()["height"]
def send_transaction(self, tx, relay=True): def send_transaction(self, tx, relay=True):
""" """
@ -55,7 +57,6 @@ class Daemon(object):
""" """
return self._backend.mempool() return self._backend.mempool()
def headers(self, start_height, end_height=None): def headers(self, start_height, end_height=None):
""" """
Returns block headers for given height range. Returns block headers for given height range.

@ -86,16 +86,19 @@ def xrecover(y):
x = (x * I) % q x = (x * I) % q
if x % 2 != 0: if x % 2 != 0:
x = q-x x = q - x
return x return x
def compress(P): def compress(P):
zinv = inv(P[2]) zinv = inv(P[2])
return (P[0] * zinv % q, P[1] * zinv % q) return (P[0] * zinv % q, P[1] * zinv % q)
def decompress(P): def decompress(P):
return (P[0], P[1], 1, P[0]*P[1] % q) return (P[0], P[1], 1, P[0] * P[1] % q)
By = 4 * inv(5) By = 4 * inv(5)
Bx = xrecover(By) Bx = xrecover(By)
@ -109,18 +112,18 @@ def edwards_add(P, Q):
(x1, y1, z1, t1) = P (x1, y1, z1, t1) = P
(x2, y2, z2, t2) = Q (x2, y2, z2, t2) = Q
a = (y1-x1)*(y2-x2) % q a = (y1 - x1) * (y2 - x2) % q
b = (y1+x1)*(y2+x2) % q b = (y1 + x1) * (y2 + x2) % q
c = t1*2*d*t2 % q c = t1 * 2 * d * t2 % q
dd = z1*2*z2 % q dd = z1 * 2 * z2 % q
e = b - a e = b - a
f = dd - c f = dd - c
g = dd + c g = dd + c
h = b + a h = b + a
x3 = e*f x3 = e * f
y3 = g*h y3 = g * h
t3 = e*h t3 = e * h
z3 = f*g z3 = f * g
return (x3 % q, y3 % q, z3 % q, t3 % q) return (x3 % q, y3 % q, z3 % q, t3 % q)
@ -130,18 +133,18 @@ def edwards_double(P):
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P (x1, y1, z1, t1) = P
a = x1*x1 % q a = x1 * x1 % q
b = y1*y1 % q b = y1 * y1 % q
c = 2*z1*z1 % q c = 2 * z1 * z1 % q
# dd = -a # dd = -a
e = ((x1+y1)*(x1+y1) - a - b) % q e = ((x1 + y1) * (x1 + y1) - a - b) % q
g = -a + b # dd + b g = -a + b # dd + b
f = g - c f = g - c
h = -a - b # dd - b h = -a - b # dd - b
x3 = e*f x3 = e * f
y3 = g*h y3 = g * h
t3 = e*h t3 = e * h
z3 = f*g z3 = f * g
return (x3 % q, y3 % q, z3 % q, t3 % q) return (x3 % q, y3 % q, z3 % q, t3 % q)
@ -165,6 +168,8 @@ def make_Bpow():
for i in range(253): for i in range(253):
Bpow.append(P) Bpow.append(P)
P = edwards_double(P) P = edwards_double(P)
make_Bpow() make_Bpow()
@ -185,10 +190,12 @@ def scalarmult_B(e):
def encodeint(y): def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)] bits = [(y >> i) & 1 for i in range(b)]
return b''.join([ return b"".join(
[
six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b//8) for i in range(b // 8)
]) ]
)
def encodepoint(P): def encodepoint(P):
@ -197,10 +204,12 @@ def encodepoint(P):
x = (x * zi) % q x = (x * zi) % q
y = (y * zi) % q y = (y * zi) % q
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return b''.join([ return b"".join(
[
six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b // 8) for i in range(b // 8)
]) ]
)
def bit(h, i): def bit(h, i):
@ -209,9 +218,11 @@ def bit(h, i):
def isoncurve(P): def isoncurve(P):
(x, y, z, t) = P (x, y, z, t) = P
return (z % q != 0 and return (
x*y % q == z*t % q and z % q != 0
(y*y - x*x - z*z - d*t*t) % q == 0) and x * y % q == z * t % q
and (y * y - x * x - z * z - d * t * t) % q == 0
)
def decodeint(s): def decodeint(s):
@ -221,9 +232,9 @@ def decodeint(s):
def decodepoint(s): def decodepoint(s):
y = sum(2 ** i * bit(s, i) for i in range(0, b - 1)) y = sum(2 ** i * bit(s, i) for i in range(0, b - 1))
x = xrecover(y) x = xrecover(y)
if x & 1 != bit(s, b-1): if x & 1 != bit(s, b - 1):
x = q - x x = q - x
P = (x, y, 1, (x*y) % q) P = (x, y, 1, (x * y) % q)
if not isoncurve(P): if not isoncurve(P):
raise ValueError("decoding point that is not on curve") raise ValueError("decoding point that is not on curve")
return P return P
@ -234,5 +245,6 @@ def public_from_secret(k):
aB = scalarmult_B(keyInt) aB = scalarmult_B(keyInt)
return encodepoint(aB) return encodepoint(aB)
def public_from_secret_hex(hk): def public_from_secret_hex(hk):
return binascii.hexlify(public_from_secret(binascii.unhexlify(hk))).decode() return binascii.hexlify(public_from_secret(binascii.unhexlify(hk))).decode()

@ -1,67 +1,88 @@
class MoneroException(Exception): class MoneroException(Exception):
pass pass
class BackendException(MoneroException): class BackendException(MoneroException):
pass pass
class NoDaemonConnection(BackendException): class NoDaemonConnection(BackendException):
pass pass
class DaemonIsBusy(BackendException): class DaemonIsBusy(BackendException):
pass pass
class AccountException(MoneroException): class AccountException(MoneroException):
pass pass
class WrongAddress(AccountException): class WrongAddress(AccountException):
pass pass
class WrongPaymentId(AccountException): class WrongPaymentId(AccountException):
pass pass
class NotEnoughMoney(AccountException): class NotEnoughMoney(AccountException):
pass pass
class NotEnoughUnlockedMoney(NotEnoughMoney): class NotEnoughUnlockedMoney(NotEnoughMoney):
pass pass
class AmountIsZero(AccountException): class AmountIsZero(AccountException):
pass pass
class TransactionNotPossible(AccountException): class TransactionNotPossible(AccountException):
pass pass
class TransactionBroadcastError(BackendException): class TransactionBroadcastError(BackendException):
def __init__(self, message, details=None): def __init__(self, message, details=None):
self.details = details self.details = details
super(TransactionBroadcastError, self).__init__(message) super(TransactionBroadcastError, self).__init__(message)
class TransactionNotFound(AccountException): class TransactionNotFound(AccountException):
pass pass
class SignatureCheckFailed(MoneroException): class SignatureCheckFailed(MoneroException):
pass pass
class WalletIsNotDeterministic(MoneroException): class WalletIsNotDeterministic(MoneroException):
pass pass
class GenericTransferError(AccountException): class GenericTransferError(AccountException):
pass pass
class AccountIndexOutOfBound(AccountException): class AccountIndexOutOfBound(AccountException):
pass pass
class AddressIndexOutOfBound(AccountException): class AddressIndexOutOfBound(AccountException):
pass pass
class WalletIsWatchOnly(MoneroException): class WalletIsWatchOnly(MoneroException):
pass pass
class TransactionIncomplete(MoneroException): class TransactionIncomplete(MoneroException):
pass pass
class TransactionWithoutBlob(TransactionIncomplete): class TransactionWithoutBlob(TransactionIncomplete):
pass pass
class TransactionWithoutJSON(TransactionIncomplete): class TransactionWithoutJSON(TransactionIncomplete):
pass pass

@ -1,21 +1,25 @@
from decimal import Decimal from decimal import Decimal
import six import six
PICONERO = Decimal('0.000000000001') PICONERO = Decimal("0.000000000001")
EMPTY_KEY = '0' * 64 EMPTY_KEY = "0" * 64
def to_atomic(amount): def to_atomic(amount):
"""Convert Monero decimal to atomic integer of piconero.""" """Convert Monero decimal to atomic integer of piconero."""
if not isinstance(amount, (Decimal, float) + six.integer_types): if not isinstance(amount, (Decimal, float) + six.integer_types):
raise ValueError("Amount '{}' doesn't have numeric type. Only Decimal, int, long and " raise ValueError(
"float (not recommended) are accepted as amounts.") "Amount '{}' doesn't have numeric type. Only Decimal, int, long and "
return int(amount * 10**12) "float (not recommended) are accepted as amounts."
)
return int(amount * 10 ** 12)
def from_atomic(amount): def from_atomic(amount):
"""Convert atomic integer of piconero to Monero decimal.""" """Convert atomic integer of piconero to Monero decimal."""
return (Decimal(amount) * PICONERO).quantize(PICONERO) return (Decimal(amount) * PICONERO).quantize(PICONERO)
def as_monero(amount): def as_monero(amount):
"""Return the amount rounded to maximal Monero precision.""" """Return the amount rounded to maximal Monero precision."""
return Decimal(amount).quantize(PICONERO) return Decimal(amount).quantize(PICONERO)
@ -31,18 +35,25 @@ class PaymentID(object):
:param payment_id: the payment ID as integer or hexadecimal string :param payment_id: the payment ID as integer or hexadecimal string
""" """
_payment_id = None _payment_id = None
def __init__(self, payment_id): def __init__(self, payment_id):
if isinstance(payment_id, PaymentID): if isinstance(payment_id, PaymentID):
payment_id = int(payment_id) payment_id = int(payment_id)
if isinstance(payment_id, six.text_type) or isinstance(payment_id, six.string_types): if isinstance(payment_id, six.text_type) or isinstance(
payment_id, six.string_types
):
payment_id = int(payment_id, 16) payment_id = int(payment_id, 16)
elif not isinstance(payment_id, six.integer_types): elif not isinstance(payment_id, six.integer_types):
raise TypeError("payment_id must be either int or hexadecimal str or bytes, " raise TypeError(
"is {0}".format(type(payment_id))) "payment_id must be either int or hexadecimal str or bytes, "
"is {0}".format(type(payment_id))
)
if payment_id.bit_length() > 256: if payment_id.bit_length() > 256:
raise ValueError("payment_id {0} is more than 256 bits long".format(payment_id)) raise ValueError(
"payment_id {0} is more than 256 bits long".format(payment_id)
)
self._payment_id = payment_id self._payment_id = payment_id
def is_short(self): def is_short(self):

@ -42,6 +42,7 @@ import warnings
from . import base58, const, ed25519, wordlists from . import base58, const, ed25519, wordlists
from .address import address from .address import address
class Seed(object): class Seed(object):
"""Creates a seed object either from local system randomness or an imported phrase. """Creates a seed object either from local system randomness or an imported phrase.
@ -55,7 +56,7 @@ class Seed(object):
:rtype: :class:`Seed <monero.seed.Seed>` :rtype: :class:`Seed <monero.seed.Seed>`
""" """
self.phrase = "" #13 or 25 word mnemonic word string self.phrase = "" # 13 or 25 word mnemonic word string
self.hex = "" # hexadecimal self.hex = "" # hexadecimal
self.word_list = wordlists.get_wordlist(wordlist) self.word_list = wordlists.get_wordlist(wordlist)
@ -82,11 +83,15 @@ class Seed(object):
elif len(seed_split) == 1: elif len(seed_split) == 1:
# single string, probably hex, but confirm # single string, probably hex, but confirm
if not len(phrase_or_hex) % 8 == 0: if not len(phrase_or_hex) % 8 == 0:
raise ValueError("Not valid hexadecimal: {hex}".format(hex=phrase_or_hex)) raise ValueError(
"Not valid hexadecimal: {hex}".format(hex=phrase_or_hex)
)
self.hex = phrase_or_hex self.hex = phrase_or_hex
self._encode_seed() self._encode_seed()
else: else:
raise ValueError("Not valid mnemonic phrase or hex: {arg}".format(arg=phrase_or_hex)) raise ValueError(
"Not valid mnemonic phrase or hex: {arg}".format(arg=phrase_or_hex)
)
else: else:
self.hex = generate_hex() self.hex = generate_hex()
self._encode_seed() self._encode_seed()
@ -96,13 +101,11 @@ class Seed(object):
return len(self.hex) == 32 return len(self.hex) == 32
def _encode_seed(self): def _encode_seed(self):
"""Convert hexadecimal string to mnemonic word representation with checksum. """Convert hexadecimal string to mnemonic word representation with checksum."""
"""
self.phrase = self.word_list.encode(self.hex) self.phrase = self.word_list.encode(self.hex)
def _decode_seed(self): def _decode_seed(self):
"""Calculate hexadecimal representation of the phrase. """Calculate hexadecimal representation of the phrase."""
"""
self.hex = self.word_list.decode(self.phrase) self.hex = self.word_list.decode(self.phrase)
def _validate_checksum(self): def _validate_checksum(self):
@ -133,7 +136,11 @@ class Seed(object):
return self.sc_reduce(a) return self.sc_reduce(a)
def secret_view_key(self): def secret_view_key(self):
b = self._hex_seed_keccak() if self.is_mymonero() else unhexlify(self.secret_spend_key()) b = (
self._hex_seed_keccak()
if self.is_mymonero()
else unhexlify(self.secret_spend_key())
)
h = keccak_256() h = keccak_256()
h.update(b) h.update(b)
return self.sc_reduce(h.digest()) return self.sc_reduce(h.digest())
@ -158,12 +165,17 @@ class Seed(object):
:rtype: :class:`Address <monero.address.Address>` :rtype: :class:`Address <monero.address.Address>`
""" """
# backward compatibility # backward compatibility
_net = net[:-3] if net.endswith('net') else net _net = net[:-3] if net.endswith("net") else net
if net not in const.NETS: if net not in const.NETS:
raise ValueError( raise ValueError(
"Invalid net argument '{:s}'. Must be one of monero.const.NET_*".format(net)) "Invalid net argument '{:s}'. Must be one of monero.const.NET_*".format(
net
)
)
netbyte = (18, 53, 24)[const.NETS.index(net)] netbyte = (18, 53, 24)[const.NETS.index(net)]
data = "{:x}{:s}{:s}".format(netbyte, self.public_spend_key(), self.public_view_key()) data = "{:x}{:s}{:s}".format(
netbyte, self.public_spend_key(), self.public_view_key()
)
h = keccak_256() h = keccak_256()
h.update(unhexlify(data)) h.update(unhexlify(data))
checksum = h.hexdigest() checksum = h.hexdigest()

@ -13,6 +13,7 @@ from .. import ed25519
from .. import exceptions from .. import exceptions
from .extra import ExtraParser from .extra import ExtraParser
class Payment(object): class Payment(object):
""" """
A payment base class, representing payment not associated with any A payment base class, representing payment not associated with any
@ -21,28 +22,35 @@ class Payment(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
payment_id = None payment_id = None
amount = None amount = None
timestamp = None timestamp = None
transaction = None transaction = None
local_address = None local_address = None
note = '' note = ""
_reprstr = "{} @ {} {:.12f} id={}" _reprstr = "{} @ {} {:.12f} id={}"
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.amount = kwargs.pop('amount', self.amount) self.amount = kwargs.pop("amount", self.amount)
self.timestamp = kwargs.pop('timestamp', self.timestamp) self.timestamp = kwargs.pop("timestamp", self.timestamp)
self.payment_id = kwargs.pop('payment_id', self.payment_id) self.payment_id = kwargs.pop("payment_id", self.payment_id)
self.transaction = kwargs.pop('transaction', self.transaction) self.transaction = kwargs.pop("transaction", self.transaction)
self.local_address = kwargs.pop('local_address', self.local_address) self.local_address = kwargs.pop("local_address", self.local_address)
self.note = kwargs.pop('note', self.note) self.note = kwargs.pop("note", self.note)
if len(kwargs): if len(kwargs):
raise ValueError("Excessive arguments for {}: {}".format(type(self), kwargs)) raise ValueError(
"Excessive arguments for {}: {}".format(type(self), kwargs)
)
def __repr__(self): def __repr__(self):
return self._reprstr.format( return self._reprstr.format(
self.transaction.hash, self.transaction.height or 'pool', self.amount, self.payment_id) self.transaction.hash,
self.transaction.height or "pool",
self.amount,
self.payment_id,
)
class IncomingPayment(Payment): class IncomingPayment(Payment):
@ -50,6 +58,7 @@ class IncomingPayment(Payment):
An incoming payment (one that increases the balance of an An incoming payment (one that increases the balance of an
:class:`Account <monero.account.Account>`) :class:`Account <monero.account.Account>`)
""" """
_reprstr = "in: {} @ {} {:.12f} id={}" _reprstr = "in: {} @ {} {:.12f} id={}"
@ -58,10 +67,11 @@ class OutgoingPayment(Payment):
An outgoing payment (one that decreases the balance of an An outgoing payment (one that decreases the balance of an
:class:`Account <monero.account.Account>`) :class:`Account <monero.account.Account>`)
""" """
destinations = None destinations = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.destinations = kwargs.pop('destinations', []) self.destinations = kwargs.pop("destinations", [])
super(OutgoingPayment, self).__init__(**kwargs) super(OutgoingPayment, self).__init__(**kwargs)
_reprstr = "out: {} @ {} {:.12f} id={}" _reprstr = "out: {} @ {} {:.12f} id={}"
@ -75,6 +85,7 @@ class Transaction(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
hash = None hash = None
fee = None fee = None
height = None height = None
@ -90,29 +101,31 @@ class Transaction(object):
@property @property
def is_coinbase(self): def is_coinbase(self):
if self.json: if self.json:
return "gen" in self.json['vin'][0] return "gen" in self.json["vin"][0]
raise exceptions.TransactionWithoutJSON( raise exceptions.TransactionWithoutJSON(
"Tx {:s} has no .json attribute and it cannot be determined " "Tx {:s} has no .json attribute and it cannot be determined "
"if it's coinbase tx.".format(self.hash)) "if it's coinbase tx.".format(self.hash)
)
@property @property
def size(self): def size(self):
if not self.blob: if not self.blob:
raise exceptions.TransactionWithoutBlob( raise exceptions.TransactionWithoutBlob(
"Transaction has no blob, hence the size cannot be determined. " "Transaction has no blob, hence the size cannot be determined. "
"Perhaps the backend prunes transaction data?") "Perhaps the backend prunes transaction data?"
)
return len(self.blob) return len(self.blob)
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.hash = kwargs.get('hash', self.hash) self.hash = kwargs.get("hash", self.hash)
self.fee = kwargs.get('fee', self.fee) self.fee = kwargs.get("fee", self.fee)
self.height = kwargs.get('height', self.height) self.height = kwargs.get("height", self.height)
self.timestamp = kwargs.get('timestamp', self.timestamp) self.timestamp = kwargs.get("timestamp", self.timestamp)
self.key = kwargs.get('key', self.key) self.key = kwargs.get("key", self.key)
self.blob = kwargs.get('blob', self.blob) self.blob = kwargs.get("blob", self.blob)
self.confirmations = kwargs.get('confirmations', self.confirmations) self.confirmations = kwargs.get("confirmations", self.confirmations)
self.output_indices = kwargs.get('output_indices', self.output_indices) self.output_indices = kwargs.get("output_indices", self.output_indices)
self.json = kwargs.get('json', self.json) self.json = kwargs.get("json", self.json)
self.pubkeys = self.pubkeys or [] self.pubkeys = self.pubkeys or []
if self.json: if self.json:
if "rct_signatures" in self.json: if "rct_signatures" in self.json:
@ -126,6 +139,7 @@ class Transaction(object):
for outputs directed to the wallet, provided that matching subaddresses have been for outputs directed to the wallet, provided that matching subaddresses have been
already generated. already generated.
""" """
def _scan_pubkeys(svk, psk, stealth_address, amount, encamount): def _scan_pubkeys(svk, psk, stealth_address, amount, encamount):
for keyidx, tx_key in enumerate(self.pubkeys): for keyidx, tx_key in enumerate(self.pubkeys):
hsdata = b"".join( hsdata = b"".join(
@ -147,7 +161,8 @@ class Transaction(object):
k = ed25519.encodepoint( k = ed25519.encodepoint(
ed25519.edwards_add( ed25519.edwards_add(
ed25519.scalarmult_B(Hsint), ed25519.decodepoint(psk), ed25519.scalarmult_B(Hsint),
ed25519.decodepoint(psk),
) )
) )
if k != stealth_address: if k != stealth_address:
@ -158,22 +173,26 @@ class Transaction(object):
amount=amount, amount=amount,
timestamp=self.timestamp, timestamp=self.timestamp,
transaction=self, transaction=self,
local_address=addr) local_address=addr,
)
amount_hs = sha3.keccak_256(b"amount" + Hs).digest() amount_hs = sha3.keccak_256(b"amount" + Hs).digest()
xormask = amount_hs[:len(encamount)] xormask = amount_hs[: len(encamount)]
dec_amount = bytearray(a ^ b for a, b in zip(*map(bytearray, (encamount, xormask)))) dec_amount = bytearray(
a ^ b for a, b in zip(*map(bytearray, (encamount, xormask)))
)
int_amount = struct.unpack("<Q", dec_amount)[0] int_amount = struct.unpack("<Q", dec_amount)[0]
amount = from_atomic(int_amount) amount = from_atomic(int_amount)
return Payment( return Payment(
amount=amount, amount=amount,
timestamp=self.timestamp, timestamp=self.timestamp,
transaction=self, transaction=self,
local_address=addr) local_address=addr,
)
if not self.json: if not self.json:
raise exceptions.TransactionWithoutJSON( raise exceptions.TransactionWithoutJSON(
'Tx {:s} has no .json attribute'.format(self.hash)) "Tx {:s} has no .json attribute".format(self.hash)
)
if wallet: if wallet:
ep = ExtraParser(self.json["extra"]) ep = ExtraParser(self.json["extra"])
@ -182,29 +201,41 @@ class Transaction(object):
svk = binascii.unhexlify(wallet.view_key()) svk = binascii.unhexlify(wallet.view_key())
# fetch before loop to save on calls; cast to list to preserve over multiple iterations # fetch before loop to save on calls; cast to list to preserve over multiple iterations
addresses = list( addresses = list(
itertools.chain(*map(operator.methodcaller("addresses"), wallet.accounts))) itertools.chain(
*map(operator.methodcaller("addresses"), wallet.accounts)
)
)
outs = [] outs = []
for idx, vout in enumerate(self.json['vout']): for idx, vout in enumerate(self.json["vout"]):
stealth_address = binascii.unhexlify(vout['target']['key']) stealth_address = binascii.unhexlify(vout["target"]["key"])
encamount = None encamount = None
if self.version == 2 and not self.is_coinbase: if self.version == 2 and not self.is_coinbase:
encamount = binascii.unhexlify( encamount = binascii.unhexlify(
self.json["rct_signatures"]["ecdhInfo"][idx]["amount"] self.json["rct_signatures"]["ecdhInfo"][idx]["amount"]
) )
payment = None payment = None
amount = from_atomic(vout['amount']) if self.version == 1 or self.is_coinbase else None amount = (
from_atomic(vout["amount"])
if self.version == 1 or self.is_coinbase
else None
)
if wallet: if wallet:
for addridx, addr in enumerate(addresses): for addridx, addr in enumerate(addresses):
psk = binascii.unhexlify(addr.spend_key()) psk = binascii.unhexlify(addr.spend_key())
payment = _scan_pubkeys(svk, psk, stealth_address, amount, encamount) payment = _scan_pubkeys(
svk, psk, stealth_address, amount, encamount
)
if payment: if payment:
break break
outs.append(Output( outs.append(
stealth_address=vout['target']['key'], Output(
stealth_address=vout["target"]["key"],
amount=payment.amount if payment else amount, amount=payment.amount if payment else amount,
index=self.output_indices[idx] if self.output_indices else None, index=self.output_indices[idx] if self.output_indices else None,
transaction=self, transaction=self,
payment=payment)) payment=payment,
)
)
return outs return outs
def __repr__(self): def __repr__(self):
@ -220,6 +251,7 @@ class Output(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
stealth_address = None stealth_address = None
amount = None amount = None
index = None index = None
@ -227,11 +259,11 @@ class Output(object):
payment = None payment = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.stealth_address = kwargs.get('stealth_address', self.stealth_address) self.stealth_address = kwargs.get("stealth_address", self.stealth_address)
self.amount = kwargs.get('amount', self.amount) self.amount = kwargs.get("amount", self.amount)
self.index = kwargs.get('index', self.index) self.index = kwargs.get("index", self.index)
self.transaction = kwargs.get('transaction', self.transaction) self.transaction = kwargs.get("transaction", self.transaction)
self.payment = kwargs.get('payment', self.payment) self.payment = kwargs.get("payment", self.payment)
def __repr__(self): def __repr__(self):
# Try to represent output as (index, amount) pair if applicable because there is no RPC # Try to represent output as (index, amount) pair if applicable because there is no RPC
@ -242,7 +274,8 @@ class Output(object):
res = "(index={},amount={})".format(self.index, self.amount) res = "(index={},amount={})".format(self.index, self.amount)
if self.payment: if self.payment:
return "{:s}, {:.12f} to [{:s}]".format( return "{:s}, {:.12f} to [{:s}]".format(
res, self.payment.amount, str(self.payment.local_address)[:6]) res, self.payment.amount, str(self.payment.local_address)[:6]
)
return res return res
def __eq__(self, other): def __eq__(self, other):
@ -252,10 +285,12 @@ class Output(object):
elif None not in (self.index, other.index, self.amount, other.amount): elif None not in (self.index, other.index, self.amount, other.amount):
return self.index == other.index and self.amount == other.amount return self.index == other.index and self.amount == other.amount
else: else:
raise TypeError('Given one-time outputs (%r,%r) are not comparable'.format(self, other)) raise TypeError(
"Given one-time outputs (%r,%r) are not comparable".format(self, other)
)
def __ne__(self, other): def __ne__(self, other):
return not(self == other) return not (self == other)
class PaymentManager(object): class PaymentManager(object):
@ -266,6 +301,7 @@ class PaymentManager(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
account_idx = 0 account_idx = 0
backend = None backend = None
@ -275,14 +311,20 @@ class PaymentManager(object):
self.direction = direction self.direction = direction
def __call__(self, **filterparams): def __call__(self, **filterparams):
fetch = self.backend.transfers_in if self.direction == 'in' else self.backend.transfers_out fetch = (
self.backend.transfers_in
if self.direction == "in"
else self.backend.transfers_out
)
return fetch(self.account_idx, PaymentFilter(**filterparams)) return fetch(self.account_idx, PaymentFilter(**filterparams))
def _validate_tx_id(txid): def _validate_tx_id(txid):
if not bool(re.compile('^[0-9a-f]{64}$').match(txid)): if not bool(re.compile("^[0-9a-f]{64}$").match(txid)):
raise ValueError("Transaction ID must be a 64-character hexadecimal string, not " raise ValueError(
"'{}'".format(txid)) "Transaction ID must be a 64-character hexadecimal string, not "
"'{}'".format(txid)
)
return txid return txid
@ -292,8 +334,10 @@ class _ByHeight(object):
**WARNING:** Integer sorting is reversed here. **WARNING:** Integer sorting is reversed here.
""" """
def __init__(self, pmt): def __init__(self, pmt):
self.pmt = pmt self.pmt = pmt
def _cmp(self, other): def _cmp(self, other):
sh = self.pmt.transaction.height sh = self.pmt.transaction.height
oh = other.pmt.transaction.height oh = other.pmt.transaction.height
@ -304,16 +348,22 @@ class _ByHeight(object):
if oh is None: if oh is None:
return -1 return -1
return (sh > oh) - (sh < oh) return (sh > oh) - (sh < oh)
def __lt__(self, other): def __lt__(self, other):
return self._cmp(other) > 0 return self._cmp(other) > 0
def __le__(self, other): def __le__(self, other):
return self._cmp(other) >= 0 return self._cmp(other) >= 0
def __eq__(self, other): def __eq__(self, other):
return self._cmp(other) == 0 return self._cmp(other) == 0
def __ge__(self, other): def __ge__(self, other):
return self._cmp(other) <= 0 return self._cmp(other) <= 0
def __gt__(self, other): def __gt__(self, other):
return self._cmp(other) < 0 return self._cmp(other) < 0
def __ne__(self, other): def __ne__(self, other):
return self._cmp(other) != 0 return self._cmp(other) != 0
@ -325,27 +375,35 @@ class PaymentFilter(object):
This class is not intended to be turned into objects by the user, This class is not intended to be turned into objects by the user,
it is used by backends. it is used by backends.
""" """
def __init__(self, **filterparams): def __init__(self, **filterparams):
self.min_height = filterparams.pop('min_height', None) self.min_height = filterparams.pop("min_height", None)
self.max_height = filterparams.pop('max_height', None) self.max_height = filterparams.pop("max_height", None)
self.unconfirmed = filterparams.pop('unconfirmed', False) self.unconfirmed = filterparams.pop("unconfirmed", False)
self.confirmed = filterparams.pop('confirmed', True) self.confirmed = filterparams.pop("confirmed", True)
_local_address = filterparams.pop('local_address', None) _local_address = filterparams.pop("local_address", None)
_tx_id = filterparams.pop('tx_id', None) _tx_id = filterparams.pop("tx_id", None)
_payment_id = filterparams.pop('payment_id', None) _payment_id = filterparams.pop("payment_id", None)
if len(filterparams) > 0: if len(filterparams) > 0:
raise ValueError("Excessive arguments for payment query: {}".format(filterparams)) raise ValueError(
if self.unconfirmed and (self.min_height is not None or self.max_height is not None): "Excessive arguments for payment query: {}".format(filterparams)
warnings.warn("Height filtering (min_height/max_height) has been requested while " )
if self.unconfirmed and (
self.min_height is not None or self.max_height is not None
):
warnings.warn(
"Height filtering (min_height/max_height) has been requested while "
"also asking for unconfirmed transactions. These are mutually exclusive. " "also asking for unconfirmed transactions. These are mutually exclusive. "
"As mempool transactions have no height at all, they will be excluded " "As mempool transactions have no height at all, they will be excluded "
"from the result.", "from the result.",
RuntimeWarning) RuntimeWarning,
)
if _local_address is None: if _local_address is None:
self.local_addresses = [] self.local_addresses = []
else: else:
if isinstance(_local_address, six.string_types) \ if isinstance(_local_address, six.string_types) or isinstance(
or isinstance(_local_address, six.text_type): _local_address, six.text_type
):
local_addresses = [_local_address] local_addresses = [_local_address]
else: else:
try: try:
@ -357,7 +415,9 @@ class PaymentFilter(object):
if _tx_id is None: if _tx_id is None:
self.tx_ids = [] self.tx_ids = []
else: else:
if isinstance(_tx_id, six.string_types) or isinstance(_tx_id, six.text_type): if isinstance(_tx_id, six.string_types) or isinstance(
_tx_id, six.text_type
):
tx_ids = [_tx_id] tx_ids = [_tx_id]
else: else:
try: try:
@ -369,7 +429,9 @@ class PaymentFilter(object):
if _payment_id is None: if _payment_id is None:
self.payment_ids = [] self.payment_ids = []
else: else:
if isinstance(_payment_id, six.string_types) or isinstance(_payment_id, six.text_type): if isinstance(_payment_id, six.string_types) or isinstance(
_payment_id, six.text_type
):
payment_ids = [_payment_id] payment_ids = [_payment_id]
else: else:
try: try:
@ -403,6 +465,4 @@ class PaymentFilter(object):
return True return True
def filter(self, payments): def filter(self, payments):
return sorted( return sorted(filter(self.check, payments), key=_ByHeight)
filter(self.check, payments),
key=_ByHeight)

@ -10,6 +10,7 @@ from . import ed25519
from . import numbers from . import numbers
from .transaction import Payment, PaymentManager from .transaction import Payment, PaymentManager
class Wallet(object): class Wallet(object):
""" """
Monero wallet. Monero wallet.
@ -29,15 +30,16 @@ class Wallet(object):
:param \\**kwargs: arguments to initialize a :class:`JSONRPCWallet <monero.backends.jsonrpc.JSONRPCWallet>` :param \\**kwargs: arguments to initialize a :class:`JSONRPCWallet <monero.backends.jsonrpc.JSONRPCWallet>`
instance if no backend is given instance if no backend is given
""" """
accounts = None accounts = None
def __init__(self, backend=None, **kwargs): def __init__(self, backend=None, **kwargs):
if backend and len(kwargs): if backend and len(kwargs):
raise ValueError('backend already given, other arguments are extraneous') raise ValueError("backend already given, other arguments are extraneous")
self._backend = backend if backend else JSONRPCWallet(**kwargs) self._backend = backend if backend else JSONRPCWallet(**kwargs)
self.incoming = PaymentManager(0, self._backend, 'in') self.incoming = PaymentManager(0, self._backend, "in")
self.outgoing = PaymentManager(0, self._backend, 'out') self.outgoing = PaymentManager(0, self._backend, "out")
self.refresh() self.refresh()
def refresh(self): def refresh(self):
@ -209,34 +211,47 @@ class Wallet(object):
:rtype: :class:`BaseAddress <monero.address.BaseAddress>` :rtype: :class:`BaseAddress <monero.address.BaseAddress>`
""" """
# ensure indexes are within uint32 # ensure indexes are within uint32
if major < 0 or major >= 2**32: if major < 0 or major >= 2 ** 32:
raise ValueError('major index {} is outside uint32 range'.format(major)) raise ValueError("major index {} is outside uint32 range".format(major))
if minor < 0 or minor >= 2**32: if minor < 0 or minor >= 2 ** 32:
raise ValueError('minor index {} is outside uint32 range'.format(minor)) raise ValueError("minor index {} is outside uint32 range".format(minor))
master_address = self.address() master_address = self.address()
if major == minor == 0: if major == minor == 0:
return master_address return master_address
master_svk = unhexlify(self.view_key()) master_svk = unhexlify(self.view_key())
master_psk = unhexlify(self.address().spend_key()) master_psk = unhexlify(self.address().spend_key())
# m = Hs("SubAddr\0" || master_svk || major || minor) # m = Hs("SubAddr\0" || master_svk || major || minor)
hsdata = b''.join([ hsdata = b"".join(
b'SubAddr\0', master_svk, [
struct.pack('<I', major), struct.pack('<I', minor)]) b"SubAddr\0",
master_svk,
struct.pack("<I", major),
struct.pack("<I", minor),
]
)
m = keccak_256(hsdata).digest() m = keccak_256(hsdata).digest()
# D = master_psk + m * B # D = master_psk + m * B
D = ed25519.edwards_add( D = ed25519.edwards_add(
ed25519.decodepoint(master_psk), ed25519.decodepoint(master_psk), ed25519.scalarmult_B(ed25519.decodeint(m))
ed25519.scalarmult_B(ed25519.decodeint(m))) )
# C = master_svk * D # C = master_svk * D
C = ed25519.scalarmult(D, ed25519.decodeint(master_svk)) C = ed25519.scalarmult(D, ed25519.decodeint(master_svk))
netbyte = bytearray([const.SUBADDR_NETBYTES[const.NETS.index(master_address.net)]]) netbyte = bytearray(
[const.SUBADDR_NETBYTES[const.NETS.index(master_address.net)]]
)
data = netbyte + ed25519.encodepoint(D) + ed25519.encodepoint(C) data = netbyte + ed25519.encodepoint(D) + ed25519.encodepoint(C)
checksum = keccak_256(data).digest()[:4] checksum = keccak_256(data).digest()[:4]
return address.SubAddress(base58.encode(hexlify(data + checksum))) return address.SubAddress(base58.encode(hexlify(data + checksum)))
def transfer(self, address, amount, def transfer(
priority=const.PRIO_NORMAL, payment_id=None, unlock_time=0, self,
relay=True): address,
amount,
priority=const.PRIO_NORMAL,
payment_id=None,
unlock_time=0,
relay=True,
):
""" """
Sends a transfer from the default account. Returns a list of resulting transactions. Sends a transfer from the default account. Returns a list of resulting transactions.
@ -260,11 +275,17 @@ class Wallet(object):
priority=priority, priority=priority,
payment_id=payment_id, payment_id=payment_id,
unlock_time=unlock_time, unlock_time=unlock_time,
relay=relay) relay=relay,
)
def transfer_multiple(self, destinations, def transfer_multiple(
priority=const.PRIO_NORMAL, payment_id=None, unlock_time=0, self,
relay=True): destinations,
priority=const.PRIO_NORMAL,
payment_id=None,
unlock_time=0,
relay=True,
):
""" """
Sends a batch of transfers from the default account. Returns a list of resulting Sends a batch of transfers from the default account. Returns a list of resulting
transactions and amounts. transactions and amounts.
@ -288,10 +309,18 @@ class Wallet(object):
priority=priority, priority=priority,
payment_id=payment_id, payment_id=payment_id,
unlock_time=unlock_time, unlock_time=unlock_time,
relay=relay) relay=relay,
)
def sweep_all(self, address, priority=const.PRIO_NORMAL, payment_id=None, def sweep_all(
subaddr_indices=None, unlock_time=0, relay=True): self,
address,
priority=const.PRIO_NORMAL,
payment_id=None,
subaddr_indices=None,
unlock_time=0,
relay=True,
):
""" """
Sends all unlocked balance from the default account to an address. Sends all unlocked balance from the default account to an address.
Returns a list of resulting transactions. Returns a list of resulting transactions.
@ -317,4 +346,5 @@ class Wallet(object):
payment_id=payment_id, payment_id=payment_id,
subaddr_indices=subaddr_indices, subaddr_indices=subaddr_indices,
unlock_time=unlock_time, unlock_time=unlock_time,
relay=relay) relay=relay,
)

@ -61,7 +61,7 @@ from .wordlist import Wordlist
class ChineseSimplified(Wordlist): class ChineseSimplified(Wordlist):
language_name = u"简体中文 (中国)", language_name = (u"简体中文 (中国)",)
english_language_name = "Chinese (simplified)" english_language_name = "Chinese (simplified)"
unique_prefix_length = 1 unique_prefix_length = 1
word_list = [ word_list = [
@ -1690,5 +1690,5 @@ class ChineseSimplified(Wordlist):
u"", u"",
u"", u"",
u"", u"",
u"" u"",
] ]

@ -1689,5 +1689,5 @@ class Dutch(Wordlist):
"zwepen", "zwepen",
"zwiep", "zwiep",
"zwijmel", "zwijmel",
"zworen" "zworen",
] ]

@ -1690,5 +1690,5 @@ class English(Wordlist):
"zodiac", "zodiac",
"zombie", "zombie",
"zones", "zones",
"zoom" "zoom",
] ]

@ -1690,5 +1690,5 @@ class French(Wordlist):
"zeste", "zeste",
"zinc", "zinc",
"zone", "zone",
"zoom" "zoom",
] ]

@ -59,6 +59,7 @@
from .wordlist import Wordlist from .wordlist import Wordlist
class German(Wordlist): class German(Wordlist):
language_name = "Deutsch" language_name = "Deutsch"
english_language_name = "German" english_language_name = "German"
@ -1689,5 +1690,5 @@ class German(Wordlist):
u"Zugvogel", u"Zugvogel",
u"Zündung", u"Zündung",
u"Zweck", u"Zweck",
u"Zyklop" u"Zyklop",
] ]

@ -1690,5 +1690,5 @@ class Italian(Wordlist):
"zoccolo", "zoccolo",
"zolfo", "zolfo",
"zombie", "zombie",
"zucchero" "zucchero",
] ]

@ -1690,5 +1690,5 @@ class Japanese(Wordlist):
u"ひさしぶり", u"ひさしぶり",
u"ひさん", u"ひさん",
u"びじゅつかん", u"びじゅつかん",
u"ひしょ" u"ひしょ",
] ]

@ -59,6 +59,7 @@
from .wordlist import Wordlist from .wordlist import Wordlist
class Lojban(Wordlist): class Lojban(Wordlist):
language_name = "Lojban" language_name = "Lojban"
english_language_name = "Lojban" english_language_name = "Lojban"

@ -1690,5 +1690,5 @@ class Portuguese(Wordlist):
"zefiro", "zefiro",
"zeloso", "zeloso",
"zenite", "zenite",
"zumbi" "zumbi",
] ]

@ -1690,5 +1690,5 @@ class Russian(Wordlist):
u"ясный", u"ясный",
u"яхта", u"яхта",
u"ячейка", u"ячейка",
u"ящик" u"ящик",
] ]

@ -1690,5 +1690,5 @@ class Spanish(Wordlist):
u"riqueza", u"riqueza",
u"risa", u"risa",
u"ritmo", u"ritmo",
u"rito" u"rito",
] ]

@ -14,18 +14,22 @@ _log = logging.getLogger(__name__)
class WordlistType(type): class WordlistType(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
if bases: if bases:
if 'language_name' not in attrs: if "language_name" not in attrs:
raise TypeError("Missing language_name for {0}".format(name)) raise TypeError("Missing language_name for {0}".format(name))
if 'unique_prefix_length' not in attrs: if "unique_prefix_length" not in attrs:
raise TypeError("Missing 'unique_prefix_length' for {0}".format(name)) raise TypeError("Missing 'unique_prefix_length' for {0}".format(name))
if 'word_list' not in attrs: if "word_list" not in attrs:
raise TypeError("Missing 'word_list' for {0}".format(name)) raise TypeError("Missing 'word_list' for {0}".format(name))
if 'english_language_name' not in attrs: if "english_language_name" not in attrs:
_log.warn("No 'english_language_name' for {0} using '{1}'".format(name, language_name)) _log.warn(
attrs['english_language_name'] = attrs['language_name'] "No 'english_language_name' for {0} using '{1}'".format(
name, language_name
)
)
attrs["english_language_name"] = attrs["language_name"]
if len(attrs['word_list']) != 1626: if len(attrs["word_list"]) != 1626:
raise TypeError("Wrong word list length for {0}".format(name)) raise TypeError("Wrong word list length for {0}".format(name))
new_cls = super(WordlistType, cls).__new__(cls, name, bases, attrs) new_cls = super(WordlistType, cls).__new__(cls, name, bases, attrs)
@ -41,11 +45,10 @@ class Wordlist(with_metaclass(WordlistType)):
@classmethod @classmethod
def encode(cls, hex): def encode(cls, hex):
"""Convert hexadecimal string to mnemonic word representation with checksum. """Convert hexadecimal string to mnemonic word representation with checksum."""
"""
out = [] out = []
for i in range(len(hex) // 8): for i in range(len(hex) // 8):
word = endian_swap(hex[8*i:8*i+8]) word = endian_swap(hex[8 * i : 8 * i + 8])
x = int(word, 16) x = int(word, 16)
w1 = x % cls.n w1 = x % cls.n
w2 = (x // cls.n + w1) % cls.n w2 = (x // cls.n + w1) % cls.n
@ -57,16 +60,15 @@ class Wordlist(with_metaclass(WordlistType)):
@classmethod @classmethod
def decode(cls, phrase): def decode(cls, phrase):
"""Calculate hexadecimal representation of the phrase. """Calculate hexadecimal representation of the phrase."""
"""
phrase = phrase.split(" ") phrase = phrase.split(" ")
out = "" out = ""
for i in range(len(phrase) // 3): for i in range(len(phrase) // 3):
word1, word2, word3 = phrase[3*i:3*i+3] word1, word2, word3 = phrase[3 * i : 3 * i + 3]
w1 = cls.word_list.index(word1) w1 = cls.word_list.index(word1)
w2 = cls.word_list.index(word2) % cls.n w2 = cls.word_list.index(word2) % cls.n
w3 = cls.word_list.index(word3) % cls.n w3 = cls.word_list.index(word3) % cls.n
x = w1 + cls.n *((w2 - w1) % cls.n) + cls.n * cls.n * ((w3 - w2) % cls.n) x = w1 + cls.n * ((w2 - w1) % cls.n) + cls.n * cls.n * ((w3 - w2) % cls.n)
out += endian_swap("%08x" % x) out += endian_swap("%08x" % x)
return out return out
@ -85,10 +87,10 @@ class Wordlist(with_metaclass(WordlistType)):
else: else:
# MyMonero format # MyMonero format
phrase = phrase_split[:12] phrase = phrase_split[:12]
wstr = "".join(word[:cls.unique_prefix_length] for word in phrase) wstr = "".join(word[: cls.unique_prefix_length] for word in phrase)
wstr = bytearray(wstr.encode('utf-8')) wstr = bytearray(wstr.encode("utf-8"))
z = ((crc32(wstr) & 0xffffffff) ^ 0xffffffff ) >> 0 z = ((crc32(wstr) & 0xFFFFFFFF) ^ 0xFFFFFFFF) >> 0
z2 = ((z ^ 0xffffffff) >> 0) % len(phrase) z2 = ((z ^ 0xFFFFFFFF) >> 0) % len(phrase)
return phrase_split[z2] return phrase_split[z2]
@ -108,4 +110,4 @@ def endian_swap(word):
:rtype: str :rtype: str
""" """
return "".join([word[i:i+2] for i in [6, 4, 2, 0]]) return "".join([word[i : i + 2] for i in [6, 4, 2, 0]])

@ -2,14 +2,15 @@ import json
import os import os
import unittest import unittest
class JSONTestCase(unittest.TestCase): class JSONTestCase(unittest.TestCase):
jsonrpc_url = 'http://127.0.0.1:18088/json_rpc' jsonrpc_url = "http://127.0.0.1:18088/json_rpc"
data_subdir = None data_subdir = None
def _read(self, *args): def _read(self, *args):
path = os.path.join(os.path.dirname(__file__), 'data') path = os.path.join(os.path.dirname(__file__), "data")
if self.data_subdir: if self.data_subdir:
path = os.path.join(path, self.data_subdir) path = os.path.join(path, self.data_subdir)
path = os.path.join(path, *args) path = os.path.join(path, *args)
with open(path, 'r') as fh: with open(path, "r") as fh:
return json.loads(fh.read()) return json.loads(fh.read())

@ -93,7 +93,11 @@ class Tests(object):
self.assertTrue(a.check_private_view_key(self.svk)) self.assertTrue(a.check_private_view_key(self.svk))
self.assertFalse(a.check_private_view_key(self.psk)) self.assertFalse(a.check_private_view_key(self.psk))
self.assertFalse(a.check_private_view_key(self.pvk)) self.assertFalse(a.check_private_view_key(self.pvk))
self.assertFalse(a.check_private_view_key('0000000000000000000000000000000000000000000000000000000000000000')) self.assertFalse(
a.check_private_view_key(
"0000000000000000000000000000000000000000000000000000000000000000"
)
)
def test_check_private_spend_key(self): def test_check_private_spend_key(self):
a = Address(self.addr) a = Address(self.addr)
@ -101,7 +105,11 @@ class Tests(object):
self.assertFalse(a.check_private_spend_key(self.svk)) self.assertFalse(a.check_private_spend_key(self.svk))
self.assertFalse(a.check_private_spend_key(self.psk)) self.assertFalse(a.check_private_spend_key(self.psk))
self.assertFalse(a.check_private_spend_key(self.pvk)) self.assertFalse(a.check_private_spend_key(self.pvk))
self.assertFalse(a.check_private_spend_key('0000000000000000000000000000000000000000000000000000000000000000')) self.assertFalse(
a.check_private_spend_key(
"0000000000000000000000000000000000000000000000000000000000000000"
)
)
def test_idempotence(self): def test_idempotence(self):
a = Address(self.addr) a = Address(self.addr)
@ -124,24 +132,26 @@ class Tests(object):
self.assertRaises(ValueError, Address, self.addr_invalid) self.assertRaises(ValueError, Address, self.addr_invalid)
self.assertRaises(ValueError, Address, self.iaddr_invalid) self.assertRaises(ValueError, Address, self.iaddr_invalid)
a = Address(self.addr) a = Address(self.addr)
self.assertRaises(TypeError, a.with_payment_id, 2**64+1) self.assertRaises(TypeError, a.with_payment_id, 2 ** 64 + 1)
self.assertRaises(TypeError, a.with_payment_id, "%x" % (2**64+1)) self.assertRaises(TypeError, a.with_payment_id, "%x" % (2 ** 64 + 1))
s = SubAddress(self.subaddr) s = SubAddress(self.subaddr)
self.assertRaises(TypeError, s.with_payment_id, 0) self.assertRaises(TypeError, s.with_payment_id, 0)
self.assertRaises(ValueError, address, 'whatever') self.assertRaises(ValueError, address, "whatever")
self.assertRaises(ValueError, Address, 'whatever') self.assertRaises(ValueError, Address, "whatever")
self.assertRaises(ValueError, SubAddress, 'whatever') self.assertRaises(ValueError, SubAddress, "whatever")
self.assertRaises(ValueError, IntegratedAddress, 'whatever') self.assertRaises(ValueError, IntegratedAddress, "whatever")
# Aeon # Aeon
self.assertRaises( self.assertRaises(
ValueError, ValueError,
address, address,
'Wmtj8UAJhdrhbKvwyBJmLEUZKHcffv2VHNBaq6oTxJFwJjUj3QwMUSS32mddSX7vchbxXdmb4QuZA9TsN47441f61yAYLQYTo') "Wmtj8UAJhdrhbKvwyBJmLEUZKHcffv2VHNBaq6oTxJFwJjUj3QwMUSS32mddSX7vchbxXdmb4QuZA9TsN47441f61yAYLQYTo",
)
# invalid netbyte # invalid netbyte
self.assertRaises( self.assertRaises(
ValueError, ValueError,
address, address,
'Cf6RinMUztY5otm6NEFjg3UWBBkXK6Lh23wKrLFMEcCY7i3A6aPLH9i4QMCkf6CdWk8Q9N7yoJf7ANKgtQMuPM6JANXgCWs') "Cf6RinMUztY5otm6NEFjg3UWBBkXK6Lh23wKrLFMEcCY7i3A6aPLH9i4QMCkf6CdWk8Q9N7yoJf7ANKgtQMuPM6JANXgCWs",
)
def test_type_mismatch(self): def test_type_mismatch(self):
self.assertRaises(ValueError, Address, self.iaddr) self.assertRaises(ValueError, Address, self.iaddr)
@ -157,50 +167,50 @@ class Tests(object):
class AddressTestCase(Tests, unittest.TestCase): class AddressTestCase(Tests, unittest.TestCase):
addr = '47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef' addr = "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef"
ssk = 'e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a' ssk = "e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a"
svk = '6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901' svk = "6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901"
psk = '9f2a76d879aaf0670039dc8dbdca01f0ca26a2f6d93268e3674666bfdc5957e4' psk = "9f2a76d879aaf0670039dc8dbdca01f0ca26a2f6d93268e3674666bfdc5957e4"
pvk = '716cfc7da7e6ce366935c55747839a85be798037ab189c7dd0f10b7f1690cb78' pvk = "716cfc7da7e6ce366935c55747839a85be798037ab189c7dd0f10b7f1690cb78"
pid = '4a6f686e47616c74' pid = "4a6f686e47616c74"
iaddr = '4HMcpBpe4ddJEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw' iaddr = "4HMcpBpe4ddJEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw"
subaddr = '84LooD7i35SFppgf4tQ453Vi3q5WexSUXaVgut69ro8MFnmHwuezAArEZTZyLr9fS6QotjqkSAxSF6d1aDgsPoX849izJ7m' subaddr = "84LooD7i35SFppgf4tQ453Vi3q5WexSUXaVgut69ro8MFnmHwuezAArEZTZyLr9fS6QotjqkSAxSF6d1aDgsPoX849izJ7m"
net = const.NET_MAIN net = const.NET_MAIN
addr_invalid = '47ewoP19TN7JCEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef' addr_invalid = "47ewoP19TN7JCEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef"
iaddr_invalid = '4HMcpBpe4ddJEEnFKUJHAYhGxkyTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw' iaddr_invalid = "4HMcpBpe4ddJEEnFKUJHAYhGxkyTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw"
class TestnetAddressTestCase(Tests, unittest.TestCase): class TestnetAddressTestCase(Tests, unittest.TestCase):
addr = '9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N' addr = "9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N"
ssk = '4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201' ssk = "4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201"
svk = '60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103' svk = "60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103"
psk = '7cf743dcfd23d452e9b2936caeb622c9849f1ff1ddfd62bfdfac64113c1a4e92' psk = "7cf743dcfd23d452e9b2936caeb622c9849f1ff1ddfd62bfdfac64113c1a4e92"
pvk = 'e3924b14d99a9c088e5a45278d5218f2d053b1c03c480f00ed2ee3dce80806c4' pvk = "e3924b14d99a9c088e5a45278d5218f2d053b1c03c480f00ed2ee3dce80806c4"
pid = '4a6f686e47616c74' pid = "4a6f686e47616c74"
subaddr = 'BaU3yLuDqdcETYzeF7vFSVEKNR4sSGxBV1Evrw5yNBf2VMiuAwfDmiF3RHqLHkaA5A6RGiNNRUqvtaqhMtdjA1SQ1tnQV8D' subaddr = "BaU3yLuDqdcETYzeF7vFSVEKNR4sSGxBV1Evrw5yNBf2VMiuAwfDmiF3RHqLHkaA5A6RGiNNRUqvtaqhMtdjA1SQ1tnQV8D"
iaddr = 'A7bzU6hSszTEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh' iaddr = "A7bzU6hSszTEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh"
net = const.NET_TEST net = const.NET_TEST
addr_invalid = '9wuKTHsxGiwEsMp3fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N' addr_invalid = "9wuKTHsxGiwEsMp3fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N"
iaddr_invalid = 'A7bzU6hSszTEsMp2fYzJiVahyhU2aZi2oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh' iaddr_invalid = "A7bzU6hSszTEsMp2fYzJiVahyhU2aZi2oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh"
class StagenetAddressTestCase(Tests, unittest.TestCase): class StagenetAddressTestCase(Tests, unittest.TestCase):
addr = '52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6' addr = "52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6"
ssk = 'a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503' ssk = "a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503"
svk = 'fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f' svk = "fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f"
psk = '180c1d7bbf7f2e11aa90d0f61bf49024370e01cd54f33f2d36bba0357c9c205f' psk = "180c1d7bbf7f2e11aa90d0f61bf49024370e01cd54f33f2d36bba0357c9c205f"
pvk = '94b66a81e646927b3da74392306f789c5024734b4ce6351ad74c4c7d7351b3ad' pvk = "94b66a81e646927b3da74392306f789c5024734b4ce6351ad74c4c7d7351b3ad"
pid = '4a6f686e47616c74' pid = "4a6f686e47616c74"
subaddr = '7AeQwvrLtPeYoXVPRkEu8oEL7N9wnqHjYKwSvTf6YKbHgYmw6AJMsjggzVLo21egMK9qcoV1mxCTfP4FbaGb7JEMDfpLetk' subaddr = "7AeQwvrLtPeYoXVPRkEu8oEL7N9wnqHjYKwSvTf6YKbHgYmw6AJMsjggzVLo21egMK9qcoV1mxCTfP4FbaGb7JEMDfpLetk"
iaddr = '5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS' iaddr = "5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS"
net = const.NET_STAGE net = const.NET_STAGE
addr_invalid = '52jzuBBUMty3xPL3JsQxGP74LDuV6H1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6' addr_invalid = "52jzuBBUMty3xPL3JsQxGP74LDuV6H1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6"
iaddr_invalid = '5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKppEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS' iaddr_invalid = "5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKppEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS"
class KnownBugsTest(unittest.TestCase): class KnownBugsTest(unittest.TestCase):
def test_issue27(self): def test_issue27(self):
addr = '41tjz19p4qc2gudqnwsdrhgcgxud8bgxy84ufe869nyw7ywbxw9s9gqbix7piu9d7qjvbjtrdnbubhcf663ydq3bsxj1brL' addr = "41tjz19p4qc2gudqnwsdrhgcgxud8bgxy84ufe869nyw7ywbxw9s9gqbix7piu9d7qjvbjtrdnbubhcf663ydq3bsxj1brL"
self.assertRaises(ValueError, Address, addr) self.assertRaises(ValueError, Address, addr)
self.assertRaises(ValueError, SubAddress, addr) self.assertRaises(ValueError, SubAddress, addr)
self.assertRaises(ValueError, IntegratedAddress, addr) self.assertRaises(ValueError, IntegratedAddress, addr)

@ -5,19 +5,19 @@ from monero.base58 import decode, encode
class Base58EncodeTestCase(unittest.TestCase): class Base58EncodeTestCase(unittest.TestCase):
def test_encode_empty(self): def test_encode_empty(self):
self.assertEqual(encode(''), '') self.assertEqual(encode(""), "")
def test_encode_invalid_hex_length(self): def test_encode_invalid_hex_length(self):
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValueError) as cm:
encode('abcde') encode("abcde")
self.assertEqual(str(cm.exception), 'Hex string has invalid length: 5') self.assertEqual(str(cm.exception), "Hex string has invalid length: 5")
class Base58DecodeTestCase(unittest.TestCase): class Base58DecodeTestCase(unittest.TestCase):
def test_decode_empty(self): def test_decode_empty(self):
self.assertEqual(decode(''), '') self.assertEqual(decode(""), "")
def test_decode_invalid_length_block(self): def test_decode_invalid_length_block(self):
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValueError) as cm:
decode('f') decode("f")
self.assertEqual(str(cm.exception), 'Invalid encoded length: 1') self.assertEqual(str(cm.exception), "Invalid encoded length: 1")

@ -10,35 +10,41 @@ from monero.transaction import Transaction
class BlockTestCase(unittest.TestCase): class BlockTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.tx1 = Transaction( self.tx1 = Transaction(
hash="7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b") hash="7e5fea8470c5771315bab4b3c77493d2ff534f5201c7c6b2bab069cb7d21ce7b"
)
self.tx2 = Transaction( self.tx2 = Transaction(
hash="3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106") hash="3a2f859dea9d2ad5ecec167719302d4e14e21beef9b74f9583183d8e965d9106"
)
self.tx3 = Transaction( self.tx3 = Transaction(
hash="bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1") hash="bde2b5344b63cbe58ce1a724d0a2276aaa4266be5235d5e5fde969446c3e8de1"
)
self.tx4 = Transaction( self.tx4 = Transaction(
hash="24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0") hash="24fb42f9f324082658524b29b4cf946a9f5fcfa82194070e2f17c1875e15d5d0"
)
self.block1 = Block( self.block1 = Block(
hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89", hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89",
height=451992, height=451992,
difficulty=3590, difficulty=3590,
version= (11,12), version=(11, 12),
nonce=140046906, nonce=140046906,
orphan=False, orphan=False,
prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41", prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41",
reward=from_atomic(15331952645334), reward=from_atomic(15331952645334),
timestamp=datetime.fromtimestamp(1573646422), timestamp=datetime.fromtimestamp(1573646422),
transactions=[self.tx1, self.tx2, self.tx3, self.tx4]) transactions=[self.tx1, self.tx2, self.tx3, self.tx4],
)
self.block1_duplicate = Block( self.block1_duplicate = Block(
hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89", hash="423cd4d170c53729cf25b4243ea576d1e901d86e26c06d6a7f79815f3fcb9a89",
height=451992, height=451992,
difficulty=3590, difficulty=3590,
version= (11,12), version=(11, 12),
nonce=140046906, nonce=140046906,
orphan=False, orphan=False,
prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41", prev_hash="51f6816891b6a7adedd0f1ad57a846eada1baac476421aa9d32d0630ce3dce41",
reward=from_atomic(15331952645334), reward=from_atomic(15331952645334),
timestamp=datetime.fromtimestamp(1573646422), timestamp=datetime.fromtimestamp(1573646422),
transactions=[self.tx1, self.tx2, self.tx3, self.tx4]) transactions=[self.tx1, self.tx2, self.tx3, self.tx4],
)
def test_basic_ops(self): def test_basic_ops(self):
self.assertIsNot(self.block1, self.block1_duplicate) self.assertIsNot(self.block1, self.block1_duplicate)

@ -1,8 +1,9 @@
import unittest import unittest
from monero import ed25519 from monero import ed25519
class Ed25519TestCase(unittest.TestCase): class Ed25519TestCase(unittest.TestCase):
def test_comp_decomp(self): def test_comp_decomp(self):
pts = [(0,0), (1,1), (3,5), (5,3), (7,11), (11,7)] pts = [(0, 0), (1, 1), (3, 5), (5, 3), (7, 11), (11, 7)]
for p in pts: for p in pts:
self.assertEqual(p, ed25519.compress(ed25519.decompress(p))) self.assertEqual(p, ed25519.compress(ed25519.decompress(p)))

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -4,47 +4,52 @@ import unittest
from monero.numbers import to_atomic, from_atomic, as_monero, PaymentID from monero.numbers import to_atomic, from_atomic, as_monero, PaymentID
class NumbersTestCase(unittest.TestCase): class NumbersTestCase(unittest.TestCase):
def test_simple_numbers(self): def test_simple_numbers(self):
self.assertEqual(to_atomic(Decimal('0')), 0) self.assertEqual(to_atomic(Decimal("0")), 0)
self.assertEqual(from_atomic(0), Decimal('0')) self.assertEqual(from_atomic(0), Decimal("0"))
self.assertEqual(to_atomic(Decimal('1')), 1000000000000) self.assertEqual(to_atomic(Decimal("1")), 1000000000000)
self.assertEqual(from_atomic(1000000000000), Decimal('1')) self.assertEqual(from_atomic(1000000000000), Decimal("1"))
self.assertEqual(to_atomic(Decimal('0.000000000001')), 1) self.assertEqual(to_atomic(Decimal("0.000000000001")), 1)
self.assertEqual(from_atomic(1), Decimal('0.000000000001')) self.assertEqual(from_atomic(1), Decimal("0.000000000001"))
def test_numeric_types(self): def test_numeric_types(self):
"Only check if conversion of given type succeeds or fails." "Only check if conversion of given type succeeds or fails."
self.assertTrue(to_atomic(1)) self.assertTrue(to_atomic(1))
self.assertTrue(to_atomic(1.0)) self.assertTrue(to_atomic(1.0))
if hasattr(sys, 'maxint'): # Python 2.x if hasattr(sys, "maxint"): # Python 2.x
self.assertTrue(to_atomic(sys.maxint + 1)) self.assertTrue(to_atomic(sys.maxint + 1))
self.assertRaises(ValueError, to_atomic, '1') self.assertRaises(ValueError, to_atomic, "1")
def test_rounding(self): def test_rounding(self):
self.assertEqual(to_atomic(Decimal('1.0000000000004')), 1000000000000) self.assertEqual(to_atomic(Decimal("1.0000000000004")), 1000000000000)
self.assertEqual(as_monero(Decimal('1.0000000000014')), Decimal('1.000000000001')) self.assertEqual(
as_monero(Decimal("1.0000000000014")), Decimal("1.000000000001")
)
def test_payment_id(self): def test_payment_id(self):
pid = PaymentID('0') pid = PaymentID("0")
self.assertTrue(pid.is_short()) self.assertTrue(pid.is_short())
self.assertEqual(pid, 0) self.assertEqual(pid, 0)
self.assertEqual(pid, '0000000000000000') self.assertEqual(pid, "0000000000000000")
self.assertEqual(PaymentID(pid), pid) self.assertEqual(PaymentID(pid), pid)
self.assertNotEqual(pid, None) self.assertNotEqual(pid, None)
pid = PaymentID('abcdef') pid = PaymentID("abcdef")
self.assertTrue(pid.is_short()) self.assertTrue(pid.is_short())
self.assertEqual(pid, 0xabcdef) self.assertEqual(pid, 0xABCDEF)
self.assertEqual(pid, '0000000000abcdef') self.assertEqual(pid, "0000000000abcdef")
self.assertEqual(PaymentID(pid), pid) self.assertEqual(PaymentID(pid), pid)
pid = PaymentID('1234567812345678') pid = PaymentID("1234567812345678")
self.assertTrue(pid.is_short()) self.assertTrue(pid.is_short())
self.assertEqual(pid, 0x1234567812345678) self.assertEqual(pid, 0x1234567812345678)
self.assertEqual(pid, '1234567812345678') self.assertEqual(pid, "1234567812345678")
self.assertEqual(PaymentID(pid), pid) self.assertEqual(PaymentID(pid), pid)
pid = PaymentID('a1234567812345678') pid = PaymentID("a1234567812345678")
self.assertFalse(pid.is_short()) self.assertFalse(pid.is_short())
self.assertEqual(pid, 0xa1234567812345678) self.assertEqual(pid, 0xA1234567812345678)
self.assertEqual(pid, '00000000000000000000000000000000000000000000000a1234567812345678') self.assertEqual(
pid, "00000000000000000000000000000000000000000000000a1234567812345678"
)
self.assertEqual(PaymentID(pid), pid) self.assertEqual(PaymentID(pid), pid)
self.assertRaises(ValueError, PaymentID, 2**256+1) self.assertRaises(ValueError, PaymentID, 2 ** 256 + 1)

@ -7,8 +7,8 @@ from .base import JSONTestCase
class OfflineTest(unittest.TestCase): class OfflineTest(unittest.TestCase):
addr = '47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef' addr = "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef"
svk = '6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901' svk = "6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901"
def setUp(self): def setUp(self):
self.wallet = Wallet(OfflineWallet(self.addr, view_key=self.svk)) self.wallet = Wallet(OfflineWallet(self.addr, view_key=self.svk))
@ -18,64 +18,72 @@ class OfflineTest(unittest.TestCase):
self.assertRaises(WalletIsOffline, self.wallet.new_account) self.assertRaises(WalletIsOffline, self.wallet.new_account)
self.assertRaises(WalletIsOffline, self.wallet.new_address) self.assertRaises(WalletIsOffline, self.wallet.new_address)
self.assertRaises(WalletIsOffline, self.wallet.export_outputs) self.assertRaises(WalletIsOffline, self.wallet.export_outputs)
self.assertRaises(WalletIsOffline, self.wallet.import_outputs, '') self.assertRaises(WalletIsOffline, self.wallet.import_outputs, "")
self.assertRaises(WalletIsOffline, self.wallet.export_key_images) self.assertRaises(WalletIsOffline, self.wallet.export_key_images)
self.assertRaises(WalletIsOffline, self.wallet.import_key_images, '') self.assertRaises(WalletIsOffline, self.wallet.import_key_images, "")
self.assertRaises(WalletIsOffline, self.wallet.balances) self.assertRaises(WalletIsOffline, self.wallet.balances)
self.assertRaises(WalletIsOffline, self.wallet.balance) self.assertRaises(WalletIsOffline, self.wallet.balance)
self.assertRaises(WalletIsOffline, self.wallet.incoming) self.assertRaises(WalletIsOffline, self.wallet.incoming)
self.assertRaises(WalletIsOffline, self.wallet.outgoing) self.assertRaises(WalletIsOffline, self.wallet.outgoing)
self.assertRaises(WalletIsOffline, self.wallet.transfer, self.wallet.get_address(1,0), 1) self.assertRaises(
self.assertRaises(WalletIsOffline, self.wallet.transfer_multiple, WalletIsOffline, self.wallet.transfer, self.wallet.get_address(1, 0), 1
[(self.wallet.get_address(1,0), 1), (self.wallet.get_address(1,1), 2)]) )
self.assertRaises(
WalletIsOffline,
self.wallet.transfer_multiple,
[(self.wallet.get_address(1, 0), 1), (self.wallet.get_address(1, 1), 2)],
)
class SubaddrTest(object): class SubaddrTest(object):
data_subdir = 'test_offline' data_subdir = "test_offline"
def setUp(self): def setUp(self):
self.wallet = Wallet(OfflineWallet(self.addr, view_key=self.svk, spend_key=self.ssk)) self.wallet = Wallet(
OfflineWallet(self.addr, view_key=self.svk, spend_key=self.ssk)
)
def test_keys(self): def test_keys(self):
self.assertEqual(self.wallet.spend_key(), self.ssk) self.assertEqual(self.wallet.spend_key(), self.ssk)
self.assertEqual(self.wallet.view_key(), self.svk) self.assertEqual(self.wallet.view_key(), self.svk)
self.assertEqual(25, len(self.wallet.seed().phrase.split(' '))) self.assertEqual(25, len(self.wallet.seed().phrase.split(" ")))
def test_subaddresses(self): def test_subaddresses(self):
major = 0 major = 0
for acc in self._read('{}-subaddrs.json'.format(self.net)): for acc in self._read("{}-subaddrs.json".format(self.net)):
minor = 0 minor = 0
for subaddr in acc: for subaddr in acc:
self.assertEqual( self.assertEqual(
self.wallet.get_address(major, minor), self.wallet.get_address(major, minor),
subaddr, subaddr,
msg='major={}, minor={}'.format(major,minor)) msg="major={}, minor={}".format(major, minor),
)
minor += 1 minor += 1
major += 1 major += 1
class AddressTestCase(SubaddrTest, JSONTestCase): class AddressTestCase(SubaddrTest, JSONTestCase):
addr = '47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef' addr = "47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef"
ssk = 'e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a' ssk = "e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a"
svk = '6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901' svk = "6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901"
net = 'mainnet' net = "mainnet"
def test_subaddress_out_of_range(self): def test_subaddress_out_of_range(self):
self.assertRaises(ValueError, self.wallet.get_address, 0, -1) self.assertRaises(ValueError, self.wallet.get_address, 0, -1)
self.assertRaises(ValueError, self.wallet.get_address, -1, 0) self.assertRaises(ValueError, self.wallet.get_address, -1, 0)
self.assertRaises(ValueError, self.wallet.get_address, 1, 2**32) self.assertRaises(ValueError, self.wallet.get_address, 1, 2 ** 32)
self.assertRaises(ValueError, self.wallet.get_address, 2**32, 1) self.assertRaises(ValueError, self.wallet.get_address, 2 ** 32, 1)
class TestnetAddressTestCase(SubaddrTest, JSONTestCase): class TestnetAddressTestCase(SubaddrTest, JSONTestCase):
addr = '9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N' addr = "9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N"
ssk = '4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201' ssk = "4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201"
svk = '60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103' svk = "60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103"
net = 'testnet' net = "testnet"
class StagenetAddressTestCase(SubaddrTest, JSONTestCase): class StagenetAddressTestCase(SubaddrTest, JSONTestCase):
addr = '52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6' addr = "52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6"
ssk = 'a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503' ssk = "a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503"
svk = 'fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f' svk = "fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f"
net = 'stagenet' net = "stagenet"

@ -1,5 +1,6 @@
from decimal import Decimal from decimal import Decimal
import json import json
try: try:
from unittest.mock import patch, Mock from unittest.mock import patch, Mock
except ImportError: except ImportError:
@ -15,6 +16,7 @@ from monero.wallet import Wallet
from .base import JSONTestCase from .base import JSONTestCase
class OutputTestCase(JSONTestCase): class OutputTestCase(JSONTestCase):
data_subdir = "test_outputs" data_subdir = "test_outputs"
daemon_transactions_url = "http://127.0.0.1:38081/get_transactions" daemon_transactions_url = "http://127.0.0.1:38081/get_transactions"
@ -23,43 +25,66 @@ class OutputTestCase(JSONTestCase):
@responses.activate @responses.activate
def test_multiple_outputs(self): def test_multiple_outputs(self):
daemon = Daemon(JSONRPCDaemon(host="127.0.0.1", port=38081)) daemon = Daemon(JSONRPCDaemon(host="127.0.0.1", port=38081))
responses.add(responses.POST, self.wallet_jsonrpc_url, responses.add(
responses.POST,
self.wallet_jsonrpc_url,
json=self._read("test_multiple_outputs-wallet-00-get_accounts.json"), json=self._read("test_multiple_outputs-wallet-00-get_accounts.json"),
status=200) status=200,
responses.add(responses.POST, self.wallet_jsonrpc_url, )
responses.add(
responses.POST,
self.wallet_jsonrpc_url,
json=self._read("test_multiple_outputs-wallet-01-query_key.json"), json=self._read("test_multiple_outputs-wallet-01-query_key.json"),
status=200) status=200,
responses.add(responses.POST, self.wallet_jsonrpc_url, )
responses.add(
responses.POST,
self.wallet_jsonrpc_url,
json=self._read("test_multiple_outputs-wallet-02-addresses-account-0.json"), json=self._read("test_multiple_outputs-wallet-02-addresses-account-0.json"),
status=200) status=200,
responses.add(responses.POST, self.wallet_jsonrpc_url, )
responses.add(
responses.POST,
self.wallet_jsonrpc_url,
json=self._read("test_multiple_outputs-wallet-02-addresses-account-1.json"), json=self._read("test_multiple_outputs-wallet-02-addresses-account-1.json"),
status=200) status=200,
)
wallet = Wallet(JSONRPCWallet(host="127.0.0.1", port=38083)) wallet = Wallet(JSONRPCWallet(host="127.0.0.1", port=38083))
responses.add(responses.POST, self.daemon_transactions_url, responses.add(
responses.POST,
self.daemon_transactions_url,
json=self._read("test_multiple_outputs-daemon-00-get_transactions.json"), json=self._read("test_multiple_outputs-daemon-00-get_transactions.json"),
status=200) status=200,
)
tx = daemon.transactions( tx = daemon.transactions(
"f79a10256859058b3961254a35a97a3d4d5d40e080c6275a3f9779acde73ca8d")[0] "f79a10256859058b3961254a35a97a3d4d5d40e080c6275a3f9779acde73ca8d"
)[0]
outs = tx.outputs(wallet=wallet) outs = tx.outputs(wallet=wallet)
self.assertEqual(len(outs), 5) self.assertEqual(len(outs), 5)
self.assertEqual( self.assertEqual(
outs[0].stealth_address, outs[0].stealth_address,
"d3eb42322566c1d48685ee0d1ad7aed2ba6210291a785ec051d8b13ae797d202") "d3eb42322566c1d48685ee0d1ad7aed2ba6210291a785ec051d8b13ae797d202",
)
self.assertEqual( self.assertEqual(
outs[1].stealth_address, outs[1].stealth_address,
"5bda44d7953e27b84022399850b59ed87408facdf00bbd1a2d4fda4bf9ebf72f") "5bda44d7953e27b84022399850b59ed87408facdf00bbd1a2d4fda4bf9ebf72f",
)
self.assertEqual( self.assertEqual(
outs[2].stealth_address, outs[2].stealth_address,
"4c79c14d5d78696e72959a28a734ec192059ebabb931040b5a0714c67b507e76") "4c79c14d5d78696e72959a28a734ec192059ebabb931040b5a0714c67b507e76",
)
self.assertEqual( self.assertEqual(
outs[3].stealth_address, outs[3].stealth_address,
"64de2b358cdf96d498a9688edafcc0e25c60179e813304747524c876655a8e55") "64de2b358cdf96d498a9688edafcc0e25c60179e813304747524c876655a8e55",
)
self.assertEqual( self.assertEqual(
outs[4].stealth_address, outs[4].stealth_address,
"966240954892294091a48c599c6db2b028e265c67677ed113d2263a7538f9a43") "966240954892294091a48c599c6db2b028e265c67677ed113d2263a7538f9a43",
)
self.assertIsNotNone(outs[0].payment) self.assertIsNotNone(outs[0].payment)
self.assertIsNone(outs[1].payment) # FIXME: isn't that change we should recognize? self.assertIsNone(
outs[1].payment
) # FIXME: isn't that change we should recognize?
self.assertIsNotNone(outs[2].payment) self.assertIsNotNone(outs[2].payment)
self.assertIsNotNone(outs[3].payment) self.assertIsNotNone(outs[3].payment)
self.assertIsNotNone(outs[4].payment) self.assertIsNotNone(outs[4].payment)
@ -74,20 +99,25 @@ class OutputTestCase(JSONTestCase):
self.assertEqual(outs[4].amount, Decimal(8)) self.assertEqual(outs[4].amount, Decimal(8))
self.assertEqual( self.assertEqual(
outs[0].payment.local_address, outs[0].payment.local_address,
"76Qt2xMZ3m7b2tagubEgkvG81pwf9P3JYdxR65H2BEv8c79A9pCBTacEFv87tfdcqXRemBsZLFVGHTWbqBpkoBJENBoJJS9") "76Qt2xMZ3m7b2tagubEgkvG81pwf9P3JYdxR65H2BEv8c79A9pCBTacEFv87tfdcqXRemBsZLFVGHTWbqBpkoBJENBoJJS9",
)
self.assertEqual( self.assertEqual(
outs[2].payment.local_address, outs[2].payment.local_address,
"78zGgzb45TEL8uvRFjCayUjHS98RFry1f7P4PE4LU7oeLh42s9AtP8fYXVzWqUW4r3Nz4g3V64w9RSiV7o3zUbPZVs5DVaU") "78zGgzb45TEL8uvRFjCayUjHS98RFry1f7P4PE4LU7oeLh42s9AtP8fYXVzWqUW4r3Nz4g3V64w9RSiV7o3zUbPZVs5DVaU",
)
self.assertEqual( self.assertEqual(
outs[3].payment.local_address, outs[3].payment.local_address,
"73ndji4W2bu4WED87rJDVALMvUsZLLYstZsigbcGfb5YG9SuNyCSYk7Qbttez2mXciKtWRzRN9aYGJbF9TPBidNQNZppnFw") "73ndji4W2bu4WED87rJDVALMvUsZLLYstZsigbcGfb5YG9SuNyCSYk7Qbttez2mXciKtWRzRN9aYGJbF9TPBidNQNZppnFw",
)
self.assertEqual( self.assertEqual(
outs[4].payment.local_address, outs[4].payment.local_address,
"7BJxHKTa4p5USJ9Z5GY15ZARXL6Qe84qT3FnWkMbSJSoEj9ugGjnpQ1N9H1jqkjsTzLiN5VTbCP8f4MYYVPAcXhr36bHXzP") "7BJxHKTa4p5USJ9Z5GY15ZARXL6Qe84qT3FnWkMbSJSoEj9ugGjnpQ1N9H1jqkjsTzLiN5VTbCP8f4MYYVPAcXhr36bHXzP",
)
self.assertEqual( self.assertEqual(
repr(outs[0]), repr(outs[0]),
"d3eb42322566c1d48685ee0d1ad7aed2ba6210291a785ec051d8b13ae797d202, 4.000000000000 " "d3eb42322566c1d48685ee0d1ad7aed2ba6210291a785ec051d8b13ae797d202, 4.000000000000 "
"to [76Qt2x]") "to [76Qt2x]",
)
def test_coinbase_no_own_output(self): def test_coinbase_no_own_output(self):
txdata = self._read("test_coinbase_no_own_output-26dcb5.json") txdata = self._read("test_coinbase_no_own_output-26dcb5.json")
@ -95,11 +125,15 @@ class OutputTestCase(JSONTestCase):
hash="26dcb55c3c93a2176949fd9ec4e20a9d97ece7c420408d9353c390a909e9a7c1", hash="26dcb55c3c93a2176949fd9ec4e20a9d97ece7c420408d9353c390a909e9a7c1",
height=766459, height=766459,
output_indices=txdata["output_indices"], output_indices=txdata["output_indices"],
json=json.loads(txdata["as_json"])) json=json.loads(txdata["as_json"]),
)
self.assertTrue(tx.is_coinbase) self.assertTrue(tx.is_coinbase)
wallet = Wallet(OfflineWallet( wallet = Wallet(
OfflineWallet(
address="56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", address="56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj",
view_key="e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a")) view_key="e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a",
)
)
outs = tx.outputs(wallet=wallet) outs = tx.outputs(wallet=wallet)
self.assertEqual(len(outs), 1) self.assertEqual(len(outs), 1)
self.assertIsNone(outs[0].payment) self.assertIsNone(outs[0].payment)
@ -111,17 +145,22 @@ class OutputTestCase(JSONTestCase):
tx = Transaction( tx = Transaction(
hash="dc08610685b8a55dc7d64454ecbe12868e4e73c766e2d19ee092885a06fc092d", hash="dc08610685b8a55dc7d64454ecbe12868e4e73c766e2d19ee092885a06fc092d",
height=518147, height=518147,
json=txdata) json=txdata,
)
self.assertTrue(tx.is_coinbase) self.assertTrue(tx.is_coinbase)
wallet = Wallet(OfflineWallet( wallet = Wallet(
OfflineWallet(
address="56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj", address="56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj",
view_key="e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a")) view_key="e507923516f52389eae889b6edc182ada82bb9354fb405abedbe0772a15aea0a",
)
)
outs = tx.outputs(wallet=wallet) outs = tx.outputs(wallet=wallet)
self.assertEqual(len(outs), 1) self.assertEqual(len(outs), 1)
self.assertIsNotNone(outs[0].payment) self.assertIsNotNone(outs[0].payment)
self.assertEqual( self.assertEqual(
outs[0].payment.local_address, outs[0].payment.local_address,
"56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj") "56eDKfprZtQGfB4y6gVLZx5naKVHw6KEKLDoq2WWtLng9ANuBvsw67wfqyhQECoLmjQN4cKAdvMp2WsC5fnw9seKLcCSfjj",
)
self.assertEqual(outs[0].amount, outs[0].payment.amount) self.assertEqual(outs[0].amount, outs[0].payment.amount)
self.assertEqual(outs[0].payment.amount, Decimal("13.515927959357")) self.assertEqual(outs[0].payment.amount, Decimal("13.515927959357"))
@ -129,69 +168,144 @@ class OutputTestCase(JSONTestCase):
tx1 = Transaction( tx1 = Transaction(
hash="2634445086cc48b89f1cd241e89e6f37195008807264684d8fad4a16f479c45a", hash="2634445086cc48b89f1cd241e89e6f37195008807264684d8fad4a16f479c45a",
height=2022660, height=2022660,
json=self._read("test_v1_tx-263444.json")) json=self._read("test_v1_tx-263444.json"),
)
tx2 = Transaction( tx2 = Transaction(
hash="3586a81f051bcb265a45c99f11b19fc4b55bb2abb3332c515a8b88a559cd9f7b", hash="3586a81f051bcb265a45c99f11b19fc4b55bb2abb3332c515a8b88a559cd9f7b",
height=2022660, height=2022660,
json=self._read("test_v1_tx-3586a8.json")) json=self._read("test_v1_tx-3586a8.json"),
)
outs1 = tx1.outputs() outs1 = tx1.outputs()
self.assertEqual(len(outs1), 14) self.assertEqual(len(outs1), 14)
self.assertEqual(outs1[0].stealth_address, "b1ef76960fe245f73131be22e9b548e861f93b727ab8a2a3ff64d86521512382") self.assertEqual(
outs1[0].stealth_address,
"b1ef76960fe245f73131be22e9b548e861f93b727ab8a2a3ff64d86521512382",
)
self.assertEqual(outs1[0].amount, Decimal("0.000000000300")) self.assertEqual(outs1[0].amount, Decimal("0.000000000300"))
self.assertEqual(outs1[1].stealth_address, "dcd66bbcb6e72602dd876e1dad65a3464a2bd831f09ec7c8131147315152e29b") self.assertEqual(
outs1[1].stealth_address,
"dcd66bbcb6e72602dd876e1dad65a3464a2bd831f09ec7c8131147315152e29b",
)
self.assertEqual(outs1[1].amount, Decimal("0.000008000000")) self.assertEqual(outs1[1].amount, Decimal("0.000008000000"))
self.assertEqual(outs1[2].stealth_address, "71efdb68dfd33f5c89a5fa8312ec6e346681f6f60fb406e9426231a5f230351a") self.assertEqual(
outs1[2].stealth_address,
"71efdb68dfd33f5c89a5fa8312ec6e346681f6f60fb406e9426231a5f230351a",
)
self.assertEqual(outs1[2].amount, Decimal("0.007000000000")) self.assertEqual(outs1[2].amount, Decimal("0.007000000000"))
self.assertEqual(outs1[3].stealth_address, "499fb727f61f2ce0fbc3419b309601f2cbf672eeef2cc827aef423b0b70e2529") self.assertEqual(
outs1[3].stealth_address,
"499fb727f61f2ce0fbc3419b309601f2cbf672eeef2cc827aef423b0b70e2529",
)
self.assertEqual(outs1[3].amount, Decimal("0.000000010000")) self.assertEqual(outs1[3].amount, Decimal("0.000000010000"))
self.assertEqual(outs1[4].stealth_address, "297ef9bb654dd6e26472a4f07f037eddb3f8b458cf4315e2cc40d9fd725e28b9") self.assertEqual(
outs1[4].stealth_address,
"297ef9bb654dd6e26472a4f07f037eddb3f8b458cf4315e2cc40d9fd725e28b9",
)
self.assertEqual(outs1[4].amount, Decimal("0.000000500000")) self.assertEqual(outs1[4].amount, Decimal("0.000000500000"))
self.assertEqual(outs1[5].stealth_address, "b2bf18a500afe1775305b19d16d0d5afec0f72096b9f15cca6604d7f5ad6e5f8") self.assertEqual(
outs1[5].stealth_address,
"b2bf18a500afe1775305b19d16d0d5afec0f72096b9f15cca6604d7f5ad6e5f8",
)
self.assertEqual(outs1[5].amount, Decimal("0.300000000000")) self.assertEqual(outs1[5].amount, Decimal("0.300000000000"))
self.assertEqual(outs1[6].stealth_address, "f7a95b33912077e3aca425270f76be13af503919b6230368a591e1053b3c7436") self.assertEqual(
outs1[6].stealth_address,
"f7a95b33912077e3aca425270f76be13af503919b6230368a591e1053b3c7436",
)
self.assertEqual(outs1[6].amount, Decimal("5.000000000000")) self.assertEqual(outs1[6].amount, Decimal("5.000000000000"))
self.assertEqual(outs1[7].stealth_address, "1e93e243a865b71e14fe4df6de0902ca634749b48002c52adc7f046053c2b921") self.assertEqual(
outs1[7].stealth_address,
"1e93e243a865b71e14fe4df6de0902ca634749b48002c52adc7f046053c2b921",
)
self.assertEqual(outs1[7].amount, Decimal("0.000200000000")) self.assertEqual(outs1[7].amount, Decimal("0.000200000000"))
self.assertEqual(outs1[8].stealth_address, "513822bad9697e8494ff82cb4b58a5a693aa433c16f0aafdaaf4a27b026a32e4") self.assertEqual(
outs1[8].stealth_address,
"513822bad9697e8494ff82cb4b58a5a693aa433c16f0aafdaaf4a27b026a32e4",
)
self.assertEqual(outs1[8].amount, Decimal("0.000000000009")) self.assertEqual(outs1[8].amount, Decimal("0.000000000009"))
self.assertEqual(outs1[9].stealth_address, "6e1ace4cfdf3f5363d72c241382e3b9927af1093b549a62f2902f56137d153bc") self.assertEqual(
outs1[9].stealth_address,
"6e1ace4cfdf3f5363d72c241382e3b9927af1093b549a62f2902f56137d153bc",
)
self.assertEqual(outs1[9].amount, Decimal("0.000000000070")) self.assertEqual(outs1[9].amount, Decimal("0.000000000070"))
self.assertEqual(outs1[10].stealth_address, "1df18bd04f42c9da8f6b49afe418aabc8ab973448a941d365534b5d0862a3d46") self.assertEqual(
outs1[10].stealth_address,
"1df18bd04f42c9da8f6b49afe418aabc8ab973448a941d365534b5d0862a3d46",
)
self.assertEqual(outs1[10].amount, Decimal("0.000000002000")) self.assertEqual(outs1[10].amount, Decimal("0.000000002000"))
self.assertEqual(outs1[11].stealth_address, "caf3e6c07f8172fc31a56ba7f541ba8d6cc601f2c7da1a135126f8f3455e3ffc") self.assertEqual(
outs1[11].stealth_address,
"caf3e6c07f8172fc31a56ba7f541ba8d6cc601f2c7da1a135126f8f3455e3ffc",
)
self.assertEqual(outs1[11].amount, Decimal("20.000000000000")) self.assertEqual(outs1[11].amount, Decimal("20.000000000000"))
self.assertEqual(outs1[12].stealth_address, "1ce506bc1ee041dfe36df3e085156023be26e133fb14f5e529b60a2d769a7c7c") self.assertEqual(
outs1[12].stealth_address,
"1ce506bc1ee041dfe36df3e085156023be26e133fb14f5e529b60a2d769a7c7c",
)
self.assertEqual(outs1[12].amount, Decimal("0.000030000000")) self.assertEqual(outs1[12].amount, Decimal("0.000030000000"))
self.assertEqual(outs1[13].stealth_address, "ee1a22b1f49db4df0df56161801974326cda4ceacbbf2a17c795ebe945790281") self.assertEqual(
outs1[13].stealth_address,
"ee1a22b1f49db4df0df56161801974326cda4ceacbbf2a17c795ebe945790281",
)
self.assertEqual(outs1[13].amount, Decimal("0.030000000000")) self.assertEqual(outs1[13].amount, Decimal("0.030000000000"))
outs2 = tx2.outputs() outs2 = tx2.outputs()
self.assertEqual(len(outs2), 10) self.assertEqual(len(outs2), 10)
self.assertEqual(outs2[0].stealth_address, "ddd1d47e5d419cf5e2298e4d9e828364b929976912dfc1bbed25fb20cc681f9f") self.assertEqual(
outs2[0].stealth_address,
"ddd1d47e5d419cf5e2298e4d9e828364b929976912dfc1bbed25fb20cc681f9f",
)
self.assertEqual(outs2[0].amount, Decimal("3.000000000000")) self.assertEqual(outs2[0].amount, Decimal("3.000000000000"))
self.assertEqual(outs2[1].stealth_address, "a0c0edc478a3448a0d371755bd614854505d2f158499d9881bfffa8b05c5b3e8") self.assertEqual(
outs2[1].stealth_address,
"a0c0edc478a3448a0d371755bd614854505d2f158499d9881bfffa8b05c5b3e8",
)
self.assertEqual(outs2[1].amount, Decimal("0.600000000000")) self.assertEqual(outs2[1].amount, Decimal("0.600000000000"))
self.assertEqual(outs2[2].stealth_address, "f9aeb5f16117f363adcd22f6b73d6e35eda64c25fee2f59208bd68d411b6d0c6") self.assertEqual(
outs2[2].stealth_address,
"f9aeb5f16117f363adcd22f6b73d6e35eda64c25fee2f59208bd68d411b6d0c6",
)
self.assertEqual(outs2[2].amount, Decimal("0.000000000700")) self.assertEqual(outs2[2].amount, Decimal("0.000000000700"))
self.assertEqual(outs2[3].stealth_address, "17e36384cf11a4d85be1320c0e221505818edbb2d6634dd54db24e25570d0f75") self.assertEqual(
outs2[3].stealth_address,
"17e36384cf11a4d85be1320c0e221505818edbb2d6634dd54db24e25570d0f75",
)
self.assertEqual(outs2[3].amount, Decimal("0.000000500000")) self.assertEqual(outs2[3].amount, Decimal("0.000000500000"))
self.assertEqual(outs2[4].stealth_address, "8b7e5dac3e0e45f9e7213ec3d4a465c5301b20f8ef30a5b2b5baba80867952b3") self.assertEqual(
outs2[4].stealth_address,
"8b7e5dac3e0e45f9e7213ec3d4a465c5301b20f8ef30a5b2b5baba80867952b3",
)
self.assertEqual(outs2[4].amount, Decimal("0.000000000070")) self.assertEqual(outs2[4].amount, Decimal("0.000000000070"))
self.assertEqual(outs2[5].stealth_address, "d1e24eeaa62232cb0e4be536fc785e03075416457dd2b704437bced16da52500") self.assertEqual(
outs2[5].stealth_address,
"d1e24eeaa62232cb0e4be536fc785e03075416457dd2b704437bced16da52500",
)
self.assertEqual(outs2[5].amount, Decimal("0.000000001000")) self.assertEqual(outs2[5].amount, Decimal("0.000000001000"))
self.assertEqual(outs2[6].stealth_address, "52c26fcce9d0a41f91ec57074e2cbfe301ca96b556e861deba51cd54e3e5b3e3") self.assertEqual(
outs2[6].stealth_address,
"52c26fcce9d0a41f91ec57074e2cbfe301ca96b556e861deba51cd54e3e5b3e3",
)
self.assertEqual(outs2[6].amount, Decimal("0.000010000000")) self.assertEqual(outs2[6].amount, Decimal("0.000010000000"))
self.assertEqual(outs2[7].stealth_address, "c5859574278889dede61d5aa341e14d2fb2acf45941486276f61dd286e7f8895") self.assertEqual(
outs2[7].stealth_address,
"c5859574278889dede61d5aa341e14d2fb2acf45941486276f61dd286e7f8895",
)
self.assertEqual(outs2[7].amount, Decimal("0.000000010000")) self.assertEqual(outs2[7].amount, Decimal("0.000000010000"))
self.assertEqual(outs2[8].stealth_address, "a3556072b7c8f77abdd16fe762fe1099c10c5ab071e16075ce0c667a3eacf1cc") self.assertEqual(
outs2[8].stealth_address,
"a3556072b7c8f77abdd16fe762fe1099c10c5ab071e16075ce0c667a3eacf1cc",
)
self.assertEqual(outs2[8].amount, Decimal("0.090000000000")) self.assertEqual(outs2[8].amount, Decimal("0.090000000000"))
self.assertEqual(outs2[9].stealth_address, "d72affedd142c6a459c42318169447f22042dba0d93c0f7ade42ddb222de8914") self.assertEqual(
outs2[9].stealth_address,
"d72affedd142c6a459c42318169447f22042dba0d93c0f7ade42ddb222de8914",
)
self.assertEqual(outs2[9].amount, Decimal("0.009000000000")) self.assertEqual(outs2[9].amount, Decimal("0.009000000000"))
def test_extra_unknown_tag(self): def test_extra_unknown_tag(self):
# try initializing as string # try initializing as string
ep = ExtraParser( ep = ExtraParser(
"0169858d0d6de79c2dfd94b3f97745a12c9a7a61ffbae16b7a34bbf5b36b75084302086003c919" "0169858d0d6de79c2dfd94b3f97745a12c9a7a61ffbae16b7a34bbf5b36b75084302086003c919"
"1772d1f90300786a796a227d07e9ae41ff9248a6b2e55adb3f6a42eb4c7ccc1c1b3d0f42c524") "1772d1f90300786a796a227d07e9ae41ff9248a6b2e55adb3f6a42eb4c7ccc1c1b3d0f42c524"
)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
pdata = ep.parse() pdata = ep.parse()
@ -199,6 +313,7 @@ class OutputTestCase(JSONTestCase):
ep = ExtraParser( ep = ExtraParser(
b"015894c0e5a8d376e931df4b4ae45b753d9442de52ec8d94036253fba5aeff9782020901cb0e5e" b"015894c0e5a8d376e931df4b4ae45b753d9442de52ec8d94036253fba5aeff9782020901cb0e5e"
b"5a80a4e135ff301ea13334dfbe1508dafcaa32762a86cf12cd4fd193ee9807edcb91bc87f6ccb6" b"5a80a4e135ff301ea13334dfbe1508dafcaa32762a86cf12cd4fd193ee9807edcb91bc87f6ccb6"
b"02384b54dff4664b232a058b8d28ad7d") b"02384b54dff4664b232a058b8d28ad7d"
)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
pdata = ep.parse() pdata = ep.parse()

@ -6,33 +6,51 @@ from monero.address import Address
from monero.seed import Seed from monero.seed import Seed
from monero.wordlists import list_wordlists from monero.wordlists import list_wordlists
class SeedTestCase(unittest.TestCase):
class SeedTestCase(unittest.TestCase):
def test_mnemonic_seed(self): def test_mnemonic_seed(self):
# Known good 25 word seed phrases should construct a class and register valid hex # Known good 25 word seed phrases should construct a class and register valid hex
seed = Seed("wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square") seed = Seed(
self.assertEqual(seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06") "wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square"
)
self.assertEqual(
seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06"
)
# Known good 24 word seed phrases should construct a class, store phrase, and register valid hex # Known good 24 word seed phrases should construct a class, store phrase, and register valid hex
seed = Seed("wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding") seed = Seed(
self.assertEqual(seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06") "wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding"
)
self.assertEqual(
seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06"
)
# Known good 25 word hexadecimal strings should construct a class, store phrase, and register valid hex # Known good 25 word hexadecimal strings should construct a class, store phrase, and register valid hex
seed = Seed("8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06") seed = Seed("8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06")
self.assertEqual(seed.phrase, "wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square") self.assertEqual(
seed.phrase,
"wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square",
)
self.assertTrue(len(seed.hex) % 8 == 0) self.assertTrue(len(seed.hex) % 8 == 0)
# Known good 13 word seed phrases should construct a class and register valid hex # Known good 13 word seed phrases should construct a class and register valid hex
seed = Seed("ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls") seed = Seed(
"ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls"
)
self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516") self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516")
# Known good 12 word seed phrases should construct a class, store phrase, and register valid hex # Known good 12 word seed phrases should construct a class, store phrase, and register valid hex
seed = Seed("ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin") seed = Seed(
"ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin"
)
self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516") self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516")
# Known good 13 word hexadecimal strings should construct a class, store phrase, and register valid hex # Known good 13 word hexadecimal strings should construct a class, store phrase, and register valid hex
seed = Seed("932d70711acc2d536ca11fcb79e05516") seed = Seed("932d70711acc2d536ca11fcb79e05516")
self.assertEqual(seed.phrase, "ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls") self.assertEqual(
seed.phrase,
"ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls",
)
self.assertTrue(len(seed.hex) % 8 == 0) self.assertTrue(len(seed.hex) % 8 == 0)
# Generated seed phrases should be 25 words, register valid hex # Generated seed phrases should be 25 words, register valid hex
@ -55,47 +73,59 @@ class SeedTestCase(unittest.TestCase):
Seed("\\x008") Seed("\\x008")
self.assertEqual(ts.expected, ValueError) self.assertEqual(ts.expected, ValueError)
def test_keys(self): def test_keys(self):
seed = Seed("adjust mugged vaults atlas nasty mews damp toenail suddenly toxic possible "\ seed = Seed(
"framed succeed fuzzy return demonstrate nucleus album noises peculiar virtual "\ "adjust mugged vaults atlas nasty mews damp toenail suddenly toxic possible "
"rowboat inorganic jester fuzzy") "framed succeed fuzzy return demonstrate nucleus album noises peculiar virtual "
"rowboat inorganic jester fuzzy"
)
self.assertFalse(seed.is_mymonero()) self.assertFalse(seed.is_mymonero())
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'482700617ba810f94035d7f4d7ccc1a29878e165b4867872b705204c85406906') "482700617ba810f94035d7f4d7ccc1a29878e165b4867872b705204c85406906",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'09ed72c713d3e9e19bef2f5204cf85f6cb25de7842aa0722abeb12697f171903') "09ed72c713d3e9e19bef2f5204cf85f6cb25de7842aa0722abeb12697f171903",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'4ee576f52b9c6a824a3d5c2832d117177d2bb9992507c2c78788bb8dbaf4b640') "4ee576f52b9c6a824a3d5c2832d117177d2bb9992507c2c78788bb8dbaf4b640",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'e1ef99d66312ec0b16b17c66c591ab59594e21621588b63b62fa69fe615a768e') "e1ef99d66312ec0b16b17c66c591ab59594e21621588b63b62fa69fe615a768e",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'44cWztNFdAqNnycvZbUoj44vsbAEmKnx9aNgkjHdjtMsBrSeKiY8J4s2raH7EMawA2Fwo9utaRTV7Aw8EcTMNMxhH4YtKdH') "44cWztNFdAqNnycvZbUoj44vsbAEmKnx9aNgkjHdjtMsBrSeKiY8J4s2raH7EMawA2Fwo9utaRTV7Aw8EcTMNMxhH4YtKdH",
)
self.assertIsInstance(seed.public_address(), Address) self.assertIsInstance(seed.public_address(), Address)
self.assertEqual( self.assertEqual(
seed.public_address(net='stage'), seed.public_address(net="stage"),
'54pZ5jHDGmwNnycvZbUoj44vsbAEmKnx9aNgkjHdjtMsBrSeKiY8J4s2raH7EMawA2Fwo9utaRTV7Aw8EcTMNMxhH6cuARW') "54pZ5jHDGmwNnycvZbUoj44vsbAEmKnx9aNgkjHdjtMsBrSeKiY8J4s2raH7EMawA2Fwo9utaRTV7Aw8EcTMNMxhH6cuARW",
self.assertIsInstance(seed.public_address(net='stage'), Address) )
self.assertIsInstance(seed.public_address(net="stage"), Address)
seed = Seed("dwelt idols lopped blender haggled rabbits piloted value swagger taunts toolbox upgrade swagger") seed = Seed(
"dwelt idols lopped blender haggled rabbits piloted value swagger taunts toolbox upgrade swagger"
)
self.assertTrue(seed.is_mymonero()) self.assertTrue(seed.is_mymonero())
# check if the same seed without checksum matches the hex # check if the same seed without checksum matches the hex
self.assertEqual(seed.hex, Seed(" ".join(seed.phrase.split(" ")[:12])).hex) self.assertEqual(seed.hex, Seed(" ".join(seed.phrase.split(" ")[:12])).hex)
# the following fails, #21 addresses that # the following fails, #21 addresses that
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'a67505f92004dd6242b64acd16e34ecf788a2d28b6072091e054238d84591403') "a67505f92004dd6242b64acd16e34ecf788a2d28b6072091e054238d84591403",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'83f652cb370948c8cbcf06839df043aa8c0d0ed36e38b3c827c4c00370af1a0f') "83f652cb370948c8cbcf06839df043aa8c0d0ed36e38b3c827c4c00370af1a0f",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'47dwi1w9it69yZyTBBRD52ctQqw3B2FZx79bCEgVUKGHH2m7MjmaXrjeQfchMMkarG6AF9a36JvBWCyRaqEcUixpKLQRxdj') "47dwi1w9it69yZyTBBRD52ctQqw3B2FZx79bCEgVUKGHH2m7MjmaXrjeQfchMMkarG6AF9a36JvBWCyRaqEcUixpKLQRxdj",
)
self.assertIsInstance(seed.public_address(), Address) self.assertIsInstance(seed.public_address(), Address)
def test_languages(self): def test_languages(self):
@ -114,187 +144,266 @@ class SeedTestCase(unittest.TestCase):
self.assertEqual(seed.phrase, seed_from_hex.phrase) self.assertEqual(seed.phrase, seed_from_hex.phrase)
def test_chinese_simplified(self): def test_chinese_simplified(self):
seed = Seed(u"遭 牲 本 点 司 司 仲 吉 虎 只 绝 生 指 纯 伟 破 夫 惊 群 楚 祥 旋 暗 骨 伟", "Chinese (simplified)") seed = Seed(
u"遭 牲 本 点 司 司 仲 吉 虎 只 绝 生 指 纯 伟 破 夫 惊 群 楚 祥 旋 暗 骨 伟", "Chinese (simplified)"
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'2ec46011b23b0c00468946f1d9a64995bf0a89f9ee0bbf4f64058a3acd81a70e') "2ec46011b23b0c00468946f1d9a64995bf0a89f9ee0bbf4f64058a3acd81a70e",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'aa141796baa24539583306300b44a72495bb7823a0cc6ad856de6d372288d10f') "aa141796baa24539583306300b44a72495bb7823a0cc6ad856de6d372288d10f",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'76cc3b927e70fee85a43a6141d019b53c77f46bbcd6c4dc6d814dfc271af361c') "76cc3b927e70fee85a43a6141d019b53c77f46bbcd6c4dc6d814dfc271af361c",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'91ef3783492e173ca366a818ae7ee37f062daea909fd9ed9ca40d41e7d572dd4') "91ef3783492e173ca366a818ae7ee37f062daea909fd9ed9ca40d41e7d572dd4",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'468Dewci4TPfs7TATZ2nf4F1mKAEMp6RraG37wiSU4uT5nAbBwGz5LaB9GWHG23o6ANFJ1Q9cBYk5dRqWNNkmFN4Qx3RqBD') "468Dewci4TPfs7TATZ2nf4F1mKAEMp6RraG37wiSU4uT5nAbBwGz5LaB9GWHG23o6ANFJ1Q9cBYk5dRqWNNkmFN4Qx3RqBD",
)
def test_dutch(self): def test_dutch(self):
seed = Seed(u"ralf tolvrij copier roon ossuarium wedstrijd splijt debbie bomtapijt occlusie oester noren hiaat scenario geshockt veeteler rotten symboliek jarig bock yoghurt plegen weert zeeblauw wedstrijd", "Dutch") seed = Seed(
u"ralf tolvrij copier roon ossuarium wedstrijd splijt debbie bomtapijt occlusie oester noren hiaat scenario geshockt veeteler rotten symboliek jarig bock yoghurt plegen weert zeeblauw wedstrijd",
"Dutch",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'600d3c5022e1844dd2df02f178a074fc2e566793e99d9e1465926adcbfa9b508') "600d3c5022e1844dd2df02f178a074fc2e566793e99d9e1465926adcbfa9b508",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'bb8984647124dafcb8682f1c257b5232bb12b96d682bfc320b4f8ce935e2d303') "bb8984647124dafcb8682f1c257b5232bb12b96d682bfc320b4f8ce935e2d303",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'df4be25f7ccaf632f1525b06fd9b0d7e9f64b21ebfb609353d643a24de16221b') "df4be25f7ccaf632f1525b06fd9b0d7e9f64b21ebfb609353d643a24de16221b",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'2fcd275e4337152ea77ac68ec02f166a243f4917ebd53b2a381ab27b84d24065') "2fcd275e4337152ea77ac68ec02f166a243f4917ebd53b2a381ab27b84d24065",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'4A5uCL4cXoB9XD3WjTrEwvNBQ6JRPTHaY9uVaxfWmcLy5YkE81tW7B28oc42XGzAeRJkhyHjKAxSE84aZnihjVBVCQf15mw') "4A5uCL4cXoB9XD3WjTrEwvNBQ6JRPTHaY9uVaxfWmcLy5YkE81tW7B28oc42XGzAeRJkhyHjKAxSE84aZnihjVBVCQf15mw",
)
def test_esperanto(self): def test_esperanto(self):
seed = Seed(u"knedi aspekti boli asbesto pterido aparta muro sandalo hufumo porcelana degeli utopia ebono lifto dutaga hundo vejno ebono higieno nikotino orkestro arlekeno insekto jaguaro hundo", "Esperanto") seed = Seed(
u"knedi aspekti boli asbesto pterido aparta muro sandalo hufumo porcelana degeli utopia ebono lifto dutaga hundo vejno ebono higieno nikotino orkestro arlekeno insekto jaguaro hundo",
"Esperanto",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'a8e8a30d3638cc4d09d1fa9f4de12ac0096c69a77896774793627c0cc6a28703') "a8e8a30d3638cc4d09d1fa9f4de12ac0096c69a77896774793627c0cc6a28703",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'8b4dcbcbafaf3d195af5bd54aa386d767a8de3b45236c9842cb876212427f103') "8b4dcbcbafaf3d195af5bd54aa386d767a8de3b45236c9842cb876212427f103",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'32c8a782c05db039018caa150bef1f66621831b3cb591401381e1dfc3c3d423e') "32c8a782c05db039018caa150bef1f66621831b3cb591401381e1dfc3c3d423e",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'047963206a0267649657936d268824e35e59e3426c63b9f3b04788b14af1d85f') "047963206a0267649657936d268824e35e59e3426c63b9f3b04788b14af1d85f",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'43YjCQcHm8TAY2kKbSMHz6J8FDZQwjPxw1Cq1vQ7SsQVBNeYEUMwGTQHppi5ffwg3df2m56DYexj2hm5uaQDtqpTBnUVzmD') "43YjCQcHm8TAY2kKbSMHz6J8FDZQwjPxw1Cq1vQ7SsQVBNeYEUMwGTQHppi5ffwg3df2m56DYexj2hm5uaQDtqpTBnUVzmD",
)
def test_french(self): def test_french(self):
seed = Seed(u"sauce exprimer chasse asile larve tacler digestion muguet rondeur sept clore narrer fluor arme torse dans glace tant salon sanguin globe quiche ficher flaque clore", "French") seed = Seed(
u"sauce exprimer chasse asile larve tacler digestion muguet rondeur sept clore narrer fluor arme torse dans glace tant salon sanguin globe quiche ficher flaque clore",
"French",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'597703dd73d0da6b3996b83c3e1e2f602be4f0de453e15846171aa9076901603') "597703dd73d0da6b3996b83c3e1e2f602be4f0de453e15846171aa9076901603",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'f6e448dbbeaa7682a541b3b5b7e2e8ebb614fac032f1c3dff659ca26ab430f09') "f6e448dbbeaa7682a541b3b5b7e2e8ebb614fac032f1c3dff659ca26ab430f09",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'10b42e100196ef2a68eeec191a46d8dc5c83d73c0861c185e5244202cd432087') "10b42e100196ef2a68eeec191a46d8dc5c83d73c0861c185e5244202cd432087",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'34c4c479d53b10d3e9c0a3d11432fd13611b12dc5b721c8ff3802329b7bac328') "34c4c479d53b10d3e9c0a3d11432fd13611b12dc5b721c8ff3802329b7bac328",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'42FpfU7DfLi86RtY3ajKUKdrnKvXTx41WPPx6wsyp9XVPcfnrLDXxhucSphpzt3mDv4F1DMiCrfHmR5WPZq1erzn5bs4eA7') "42FpfU7DfLi86RtY3ajKUKdrnKvXTx41WPPx6wsyp9XVPcfnrLDXxhucSphpzt3mDv4F1DMiCrfHmR5WPZq1erzn5bs4eA7",
)
def test_german(self): def test_german(self):
seed = Seed(u"Erdgas Gesuch beeilen Chiffon Abendrot Alter Helium Salz Almweide Ampel Dichter Rotglut Dialekt Akkord Rampe Gesöff Ziege Boykott keuchen Krach Anbau Labor Esel Ferien Ampel", "German") seed = Seed(
u"Erdgas Gesuch beeilen Chiffon Abendrot Alter Helium Salz Almweide Ampel Dichter Rotglut Dialekt Akkord Rampe Gesöff Ziege Boykott keuchen Krach Anbau Labor Esel Ferien Ampel",
"German",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'193152abe15c5e0a0ff56e3020229398769cd7c6ca5a4e30e439d6702c4f320a') "193152abe15c5e0a0ff56e3020229398769cd7c6ca5a4e30e439d6702c4f320a",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'cdb967c501195827d78a791e1173d4b8826a5ae73b0885984898c84b6c9dd80c') "cdb967c501195827d78a791e1173d4b8826a5ae73b0885984898c84b6c9dd80c",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'32eac115ca4b072c18198966c7ac9cb63b9f701a691eb52bfa18345d0fbcd90f') "32eac115ca4b072c18198966c7ac9cb63b9f701a691eb52bfa18345d0fbcd90f",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'06a2119dfa7c48bdc03ad251026fc509bd01f3a4f7521802ca31b93cf06539ac') "06a2119dfa7c48bdc03ad251026fc509bd01f3a4f7521802ca31b93cf06539ac",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'43Z2BHsCkU68NmZrxzfZuuXUtUHCXWttt8MdcnNyDMkC3WmfoFb9byqYjpeBaC4Xtx2dUUv8YPv1d1U4krZCLzyWLUFif2E') "43Z2BHsCkU68NmZrxzfZuuXUtUHCXWttt8MdcnNyDMkC3WmfoFb9byqYjpeBaC4Xtx2dUUv8YPv1d1U4krZCLzyWLUFif2E",
)
def test_italian(self): def test_italian(self):
seed = Seed(u"tramonto spuntare ruota afrodite binocolo riferire moneta assalire tuta firmare malattia flagello paradiso tacere sindrome spuntare sogliola volare follia versare insulto diagnosi lapide meteo malattia", "Italian") seed = Seed(
u"tramonto spuntare ruota afrodite binocolo riferire moneta assalire tuta firmare malattia flagello paradiso tacere sindrome spuntare sogliola volare follia versare insulto diagnosi lapide meteo malattia",
"Italian",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'29c8d9e91c1cb59e059bddd901e011db85f8d4f00f967226ffb5e185bd10e70d') "29c8d9e91c1cb59e059bddd901e011db85f8d4f00f967226ffb5e185bd10e70d",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'1f224a0330ee358428fe91fa48b6986941030c34f2d1efecc4eb26ea9f838b02') "1f224a0330ee358428fe91fa48b6986941030c34f2d1efecc4eb26ea9f838b02",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'149bdad48fd1ca40e1eb3e323b676132e2cae1eedbd715ac131b97c2c749c6b4') "149bdad48fd1ca40e1eb3e323b676132e2cae1eedbd715ac131b97c2c749c6b4",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'efc1a3382c33ac58ecfdd3a71497b0d0aeef061d0af94e5c49278d653167d643') "efc1a3382c33ac58ecfdd3a71497b0d0aeef061d0af94e5c49278d653167d643",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'42QQUPDR9PoBrSc9rB5VvG9Wf7KmtjXhEVnLhGKif9rDXGK3n1e6rsVFsh62YDqDf5buVQXuL6oLHGSHg4ANgQUu8beDd9R') "42QQUPDR9PoBrSc9rB5VvG9Wf7KmtjXhEVnLhGKif9rDXGK3n1e6rsVFsh62YDqDf5buVQXuL6oLHGSHg4ANgQUu8beDd9R",
)
def test_japanese(self): def test_japanese(self):
seed = Seed(u"いもり すあな いきる しちょう うったえる ちひょう けなみ たいちょう うぶごえ しかい しなぎれ いっせい つかれる しなん ばあさん たいまつばな しひょう おいかける あんがい ていへん せんもん きこく せんく そそぐ つかれる", "Japanese") seed = Seed(
u"いもり すあな いきる しちょう うったえる ちひょう けなみ たいちょう うぶごえ しかい しなぎれ いっせい つかれる しなん ばあさん たいまつばな しひょう おいかける あんがい ていへん せんもん きこく せんく そそぐ つかれる",
"Japanese",
)
self.assertFalse(seed.is_mymonero()) self.assertFalse(seed.is_mymonero())
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'a047598095d2ada065af73758f7082900b9b0d721b5f99a541a78bd461ffc607') "a047598095d2ada065af73758f7082900b9b0d721b5f99a541a78bd461ffc607",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'080c6135edf93233176d41c8535caef0f13d596dc5093b5a5afa4279339dbc00') "080c6135edf93233176d41c8535caef0f13d596dc5093b5a5afa4279339dbc00",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'85d849793fce4d0238d991d3aab7ac790cee73e5732d378c216f11bd3b873e43') "85d849793fce4d0238d991d3aab7ac790cee73e5732d378c216f11bd3b873e43",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'19dc462a6074a26fa7788b45e542a71ffdbd48502e41ae8790c46fd6de556de3') "19dc462a6074a26fa7788b45e542a71ffdbd48502e41ae8790c46fd6de556de3",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'46hHs9s3boi1NZJHGSwMgfMFLpCBaKwdQQSSf7fqVjWdCDxudsDmqqbKgBkpYDX6JA6MMZG8o5yrMPg9ztrXHdEkSfUA131') "46hHs9s3boi1NZJHGSwMgfMFLpCBaKwdQQSSf7fqVjWdCDxudsDmqqbKgBkpYDX6JA6MMZG8o5yrMPg9ztrXHdEkSfUA131",
)
def test_portuguese(self): def test_portuguese(self):
seed = Seed(u"rebuscar mefistofelico luto isca vulva ontologico autuar epiteto jarro invulneravel inquisitorial vietnamita voile potro mamute giroscopio scherzo cheroqui gueto loquaz fissurar fazer violoncelo viquingue vulva", "Portuguese") seed = Seed(
u"rebuscar mefistofelico luto isca vulva ontologico autuar epiteto jarro invulneravel inquisitorial vietnamita voile potro mamute giroscopio scherzo cheroqui gueto loquaz fissurar fazer violoncelo viquingue vulva",
"Portuguese",
)
self.assertFalse(seed.is_mymonero()) self.assertFalse(seed.is_mymonero())
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'60916cfcb10fa0b2b0648e36ecd7037f5c1972d36b2e6d56c2f4feca613a4200') "60916cfcb10fa0b2b0648e36ecd7037f5c1972d36b2e6d56c2f4feca613a4200",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'b23941e3f4da76e0fab171d94a36fe70031fb501f1f80e0cb3b4b4638b5f7106') "b23941e3f4da76e0fab171d94a36fe70031fb501f1f80e0cb3b4b4638b5f7106",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'340c89026a03637e8b0abda566ac99b98a7c85b30a81281be19af869c3631dfb') "340c89026a03637e8b0abda566ac99b98a7c85b30a81281be19af869c3631dfb",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'23bb38c5e34867c49a65f0e7192138483361d419febbd429f256088e5e62a55e') "23bb38c5e34867c49a65f0e7192138483361d419febbd429f256088e5e62a55e",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'43bWUqKAoYWNAdMtuaSF2pY2yptw7zfCB5fV2fXLkYTvj1NNYUKM4aaZtJCVYJunHuD5SNE2CPTCo81wDhZc8bReBidbX1w') "43bWUqKAoYWNAdMtuaSF2pY2yptw7zfCB5fV2fXLkYTvj1NNYUKM4aaZtJCVYJunHuD5SNE2CPTCo81wDhZc8bReBidbX1w",
)
def test_russian(self): def test_russian(self):
seed = Seed(u"дощатый ателье мыло паек азот ружье домашний уныние уплата торговля шкаф кекс газета тревога улица армия лазерный иголка друг хищник пашня дневник кричать лыжный иголка", "Russian") seed = Seed(
u"дощатый ателье мыло паек азот ружье домашний уныние уплата торговля шкаф кекс газета тревога улица армия лазерный иголка друг хищник пашня дневник кричать лыжный иголка",
"Russian",
)
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'6dc31f6ebcf834ab375a69006cb19c66fcccfa0732dfb3ea1b0662b455226b0d') "6dc31f6ebcf834ab375a69006cb19c66fcccfa0732dfb3ea1b0662b455226b0d",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'5467825ef0148a11582115f80b01c9af90fe31216a9cf6fb2d6b3c78698ce80a') "5467825ef0148a11582115f80b01c9af90fe31216a9cf6fb2d6b3c78698ce80a",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'200657c6d14ab19cd3fccd8634e8f23e81290a559b8eb5e58dda3696553ddffc') "200657c6d14ab19cd3fccd8634e8f23e81290a559b8eb5e58dda3696553ddffc",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'f7563d9efb1c03a299b9c91a604caf7fd0c5a6998fdeedf18b58a63930958a24') "f7563d9efb1c03a299b9c91a604caf7fd0c5a6998fdeedf18b58a63930958a24",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'42qVnaWnHSGTERsT6diSvdBTNbHfQZauSfPxpc5EuHc2jK699E28uwpUCRrHr9aaZ4NNyJ9ABdxX6hQHPHv2YcW55A26UbQ') "42qVnaWnHSGTERsT6diSvdBTNbHfQZauSfPxpc5EuHc2jK699E28uwpUCRrHr9aaZ4NNyJ9ABdxX6hQHPHv2YcW55A26UbQ",
)
def test_spanish(self): def test_spanish(self):
seed = Seed(u"riesgo lápiz martes fuerza dinero pupila pago mensaje guion libro órgano juntar imperio puñal historia pasión nación posible paso límite don afirmar receta reposo fuerza", "Spanish") seed = Seed(
u"riesgo lápiz martes fuerza dinero pupila pago mensaje guion libro órgano juntar imperio puñal historia pasión nación posible paso límite don afirmar receta reposo fuerza",
"Spanish",
)
self.assertFalse(seed.is_mymonero()) self.assertFalse(seed.is_mymonero())
self.assertEqual( self.assertEqual(
seed.secret_spend_key(), seed.secret_spend_key(),
'5973d91299466a9a51ddfcd20d1710c776aa1399279b292b264ab6b7ab608105') "5973d91299466a9a51ddfcd20d1710c776aa1399279b292b264ab6b7ab608105",
)
self.assertEqual( self.assertEqual(
seed.secret_view_key(), seed.secret_view_key(),
'5f7a66cf32120515870f89e3a156ec2024154334a3b43af1da05244ec4cf250d') "5f7a66cf32120515870f89e3a156ec2024154334a3b43af1da05244ec4cf250d",
)
self.assertEqual( self.assertEqual(
seed.public_spend_key(), seed.public_spend_key(),
'42161417635c6bd31a8dce8c2bd3b5f4879369fb732073d9f6fa82b18329c7f7') "42161417635c6bd31a8dce8c2bd3b5f4879369fb732073d9f6fa82b18329c7f7",
)
self.assertEqual( self.assertEqual(
seed.public_view_key(), seed.public_view_key(),
'6acc984fecb5894b5661d446954ffcfe302cd1d2cf0e5177c2553aafb1dc3d2a') "6acc984fecb5894b5661d446954ffcfe302cd1d2cf0e5177c2553aafb1dc3d2a",
)
self.assertEqual( self.assertEqual(
seed.public_address(), seed.public_address(),
'448MxehQwbgcJyJ3fKnTYYhuF7g7cs7AJdTXoybMu8UEiPFtFpEVNTaDbsK5vatPHVjWwjvJfyWKiM2pBKXJrg4U5qeGXjZ') "448MxehQwbgcJyJ3fKnTYYhuF7g7cs7AJdTXoybMu8UEiPFtFpEVNTaDbsK5vatPHVjWwjvJfyWKiM2pBKXJrg4U5qeGXjZ",
)
if __name__ == "__main__": if __name__ == "__main__":

@ -9,52 +9,153 @@ from monero.numbers import PaymentID
from monero.transaction import IncomingPayment, Transaction, Output, _ByHeight from monero.transaction import IncomingPayment, Transaction, Output, _ByHeight
from monero import exceptions from monero import exceptions
class FiltersTestCase(unittest.TestCase): class FiltersTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.tx1 = Transaction( self.tx1 = Transaction(
timestamp=datetime(2018, 1, 29, 15, 0, 25), timestamp=datetime(2018, 1, 29, 15, 0, 25),
height=1087606, height=1087606,
hash='a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14', hash="a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14",
fee=Decimal('0.00352891')) fee=Decimal("0.00352891"),
)
self.pm1 = IncomingPayment( self.pm1 = IncomingPayment(
amount=Decimal('1'), amount=Decimal("1"),
local_address=address('Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV'), local_address=address(
payment_id=PaymentID('0166d8da6c0045c51273dd65d6f63734beb8a84e0545a185b2cfd053fced9f5d'), "Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV"
transaction=self.tx1) ),
payment_id=PaymentID(
"0166d8da6c0045c51273dd65d6f63734beb8a84e0545a185b2cfd053fced9f5d"
),
transaction=self.tx1,
)
# setup for one-time output tests # setup for one-time output tests
self.json1 = { # Actual as_json response from TX ee5bcb6430c39757ff27f8d607287572f3956a0ee16bb1d2378891f93746c8f9 self.json1 = { # Actual as_json response from TX ee5bcb6430c39757ff27f8d607287572f3956a0ee16bb1d2378891f93746c8f9
'version': 2, 'unlock_time': 0, 'vin': [{'key': {'amount': 0, 'key_offsets': "version": 2,
[25471133, 261981, 36602, 18967, 13096, 16260, 54279, 3105, 5403, 786, 555], "unlock_time": 0,
'k_image': '4b48346e954a74be9a334b03cadf8aa020542d201fb6ae7416246d19fd04fdb7'}}], "vin": [
'vout': [{'amount': 0, 'target': {'key': 'c55e793b4d673dcf73587e5141b777ef24e255d48826c75ce110ffc23ff762b9'}}, {
{'amount': 0, 'target': {'key': '93b263454cd3cc349245ad60c9c248332b885a1f2d7b5792cfc24fd87434d62a'}}], "key": {
'extra': [1, 209, 170, 43, 245, 190, 68, 82, 131, 116, 79, 134, 175, 104, 216, 127, 99, 49, 127, 141, 255, 65, 204, 101, "amount": 0,
81, 244, 111, 253, 155, 75, 111, 14, 159, 2, 9, 1, 24, 56, 108, 94, 20, 88, 150, 94], 'rct_signatures': {'type': 5, "key_offsets": [
'txnFee': 58560000, 'ecdhInfo': [{'amount': '6c13cf459cb9ed96'}, {'amount': '373bc40c7f600bf4'}], 'outPk': 25471133,
['80521a77ebe954a5daa6f14b13cc74337f999bc68177a58e76f768c18f2fa421', 261981,
'5997e64b90d59f7f810ddbc801f747c4fa43e2de593e4ea48531e16d776c00fd']}} 36602,
18967,
13096,
16260,
54279,
3105,
5403,
786,
555,
],
"k_image": "4b48346e954a74be9a334b03cadf8aa020542d201fb6ae7416246d19fd04fdb7",
}
}
],
"vout": [
{
"amount": 0,
"target": {
"key": "c55e793b4d673dcf73587e5141b777ef24e255d48826c75ce110ffc23ff762b9"
},
},
{
"amount": 0,
"target": {
"key": "93b263454cd3cc349245ad60c9c248332b885a1f2d7b5792cfc24fd87434d62a"
},
},
],
"extra": [
1,
209,
170,
43,
245,
190,
68,
82,
131,
116,
79,
134,
175,
104,
216,
127,
99,
49,
127,
141,
255,
65,
204,
101,
81,
244,
111,
253,
155,
75,
111,
14,
159,
2,
9,
1,
24,
56,
108,
94,
20,
88,
150,
94,
],
"rct_signatures": {
"type": 5,
"txnFee": 58560000,
"ecdhInfo": [
{"amount": "6c13cf459cb9ed96"},
{"amount": "373bc40c7f600bf4"},
],
"outPk": [
"80521a77ebe954a5daa6f14b13cc74337f999bc68177a58e76f768c18f2fa421",
"5997e64b90d59f7f810ddbc801f747c4fa43e2de593e4ea48531e16d776c00fd",
],
},
}
self.outind1 = [25884175, 25884176] self.outind1 = [25884175, 25884176]
self.tx2 = Transaction(json=self.json1, output_indices=self.outind1) self.tx2 = Transaction(json=self.json1, output_indices=self.outind1)
self.oto1 = Output(index=25973289, amount=Decimal('0.000000000000')) self.oto1 = Output(index=25973289, amount=Decimal("0.000000000000"))
self.oto2 = Output(pubkey='0faff18f7149a0db5aa0dc3c9116887740ccbb5dc4d1eeff87895288e55e5052') self.oto2 = Output(
pubkey="0faff18f7149a0db5aa0dc3c9116887740ccbb5dc4d1eeff87895288e55e5052"
)
def test_hash(self): def test_hash(self):
self.assertIn( self.assertIn(
'a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14', "a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14",
repr(self.tx1)) repr(self.tx1),
)
self.assertIn( self.assertIn(
'a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14', "a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14",
repr(self.pm1)) repr(self.pm1),
)
def test_outputs(self): def test_outputs(self):
out1, out2 = self.tx2.outputs() out1, out2 = self.tx2.outputs()
self.assertEqual(out1.transaction, self.tx2) self.assertEqual(out1.transaction, self.tx2)
self.assertEqual(out2.transaction, self.tx2) self.assertEqual(out2.transaction, self.tx2)
self.assertIn(self.json1['vout'][0]['target']['key'], repr(out1)) self.assertIn(self.json1["vout"][0]["target"]["key"], repr(out1))
self.assertFalse(out2 != Output(stealth_address=self.json1['vout'][1]['target']['key'])) self.assertFalse(
self.assertIn('(index=25973289,amount=0E-12)', repr(self.oto1)) out2 != Output(stealth_address=self.json1["vout"][1]["target"]["key"])
self.assertEqual(self.oto1, Output(index=25973289, amount=Decimal('0.000000000000'))) )
self.assertIn("(index=25973289,amount=0E-12)", repr(self.oto1))
self.assertEqual(
self.oto1, Output(index=25973289, amount=Decimal("0.000000000000"))
)
with self.assertRaises(exceptions.TransactionWithoutJSON): with self.assertRaises(exceptions.TransactionWithoutJSON):
self.tx1.outputs() self.tx1.outputs()
@ -72,12 +173,17 @@ class SortingTestCase(unittest.TestCase):
IncomingPayment(transaction=Transaction(height=None)), IncomingPayment(transaction=Transaction(height=None)),
IncomingPayment(transaction=Transaction(height=100)), IncomingPayment(transaction=Transaction(height=100)),
IncomingPayment(transaction=Transaction(height=None)), IncomingPayment(transaction=Transaction(height=None)),
IncomingPayment(transaction=Transaction(height=1)) IncomingPayment(transaction=Transaction(height=1)),
] ]
for i in range(1680): # 1/3 of possible permutations for i in range(1680): # 1/3 of possible permutations
sorted_pmts = sorted(pmts, key=_ByHeight) sorted_pmts = sorted(pmts, key=_ByHeight)
self.assertEqual( self.assertEqual(
list(map(attrgetter('height'), map(attrgetter('transaction'), sorted_pmts))), list(
[None, None, 100, 13, 12, 10, 1]) map(
attrgetter("height"),
map(attrgetter("transaction"), sorted_pmts),
)
),
[None, None, 100, 13, 12, 10, 1],
)
random.shuffle(pmts) random.shuffle(pmts)

@ -9,6 +9,7 @@ from monero.address import address
from monero.numbers import PaymentID from monero.numbers import PaymentID
from monero.transaction import IncomingPayment, Transaction from monero.transaction import IncomingPayment, Transaction
class FiltersTestCase(unittest.TestCase): class FiltersTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
class MockBackend(object): class MockBackend(object):
@ -17,90 +18,124 @@ class FiltersTestCase(unittest.TestCase):
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 15, 0, 25), timestamp=datetime(2018, 1, 29, 15, 0, 25),
height=1087606, height=1087606,
hash='a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14', hash="a0b876ebcf7c1d499712d84cedec836f9d50b608bb22d6cb49fd2feae3ffed14",
fee=Decimal('0.00352891')) fee=Decimal("0.00352891"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('1'), amount=Decimal("1"),
local_address=address('Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV'), local_address=address(
payment_id=PaymentID('0166d8da6c0045c51273dd65d6f63734beb8a84e0545a185b2cfd053fced9f5d'), "Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV"
transaction=tx) ),
payment_id=PaymentID(
"0166d8da6c0045c51273dd65d6f63734beb8a84e0545a185b2cfd053fced9f5d"
),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 14, 57, 47), timestamp=datetime(2018, 1, 29, 14, 57, 47),
height=1087601, height=1087601,
hash='f34b495cec77822a70f829ec8a5a7f1e727128d62e6b1438e9cb7799654d610e', hash="f34b495cec77822a70f829ec8a5a7f1e727128d62e6b1438e9cb7799654d610e",
fee=Decimal('0.008661870000')) fee=Decimal("0.008661870000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('3.000000000000'), amount=Decimal("3.000000000000"),
local_address=address('BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En'), local_address=address(
payment_id=PaymentID('f75ad90e25d71a12'), "BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En"
transaction=tx) ),
payment_id=PaymentID("f75ad90e25d71a12"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 13, 17, 18), timestamp=datetime(2018, 1, 29, 13, 17, 18),
height=1087530, height=1087530,
hash='5c3ab739346e9d98d38dc7b8d36a4b7b1e4b6a16276946485a69797dbf887cd8', hash="5c3ab739346e9d98d38dc7b8d36a4b7b1e4b6a16276946485a69797dbf887cd8",
fee=Decimal('0.000962550000')) fee=Decimal("0.000962550000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('10.000000000000'), amount=Decimal("10.000000000000"),
local_address=address('9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC'), local_address=address(
payment_id=PaymentID('f75ad90e25d71a12'), "9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
transaction=tx) ),
payment_id=PaymentID("f75ad90e25d71a12"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 13, 17, 18), timestamp=datetime(2018, 1, 29, 13, 17, 18),
height=1087530, height=1087530,
hash='4ea70add5d0c7db33557551b15cd174972fcfc73bf0f6a6b47b7837564b708d3', hash="4ea70add5d0c7db33557551b15cd174972fcfc73bf0f6a6b47b7837564b708d3",
fee=Decimal('0.000962550000')) fee=Decimal("0.000962550000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('4.000000000000'), amount=Decimal("4.000000000000"),
local_address=address('9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC'), local_address=address(
payment_id=PaymentID('f75ad90e25d71a12'), "9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
transaction=tx) ),
payment_id=PaymentID("f75ad90e25d71a12"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 13, 17, 18), timestamp=datetime(2018, 1, 29, 13, 17, 18),
height=1087530, height=1087530,
hash='e9a71c01875bec20812f71d155bfabf42024fde3ec82475562b817dcc8cbf8dc', hash="e9a71c01875bec20812f71d155bfabf42024fde3ec82475562b817dcc8cbf8dc",
fee=Decimal('0.000962550000')) fee=Decimal("0.000962550000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('2.120000000000'), amount=Decimal("2.120000000000"),
local_address=address('9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC'), local_address=address(
payment_id=PaymentID('cb248105ea6a9189'), "9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
transaction=tx) ),
payment_id=PaymentID("cb248105ea6a9189"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 14, 57, 47), timestamp=datetime(2018, 1, 29, 14, 57, 47),
height=1087601, height=1087601,
hash='5ef7ead6a041101ed326568fbb59c128403cba46076c3f353cd110d969dac808', hash="5ef7ead6a041101ed326568fbb59c128403cba46076c3f353cd110d969dac808",
fee=Decimal('0.000962430000')) fee=Decimal("0.000962430000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('7.000000000000'), amount=Decimal("7.000000000000"),
local_address=address('BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En'), local_address=address(
payment_id=PaymentID('0000000000000000'), "BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En"
transaction=tx) ),
payment_id=PaymentID("0000000000000000"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 13, 17, 18), timestamp=datetime(2018, 1, 29, 13, 17, 18),
height=1087530, height=1087530,
hash='cc44568337a186c2e1ccc080b43b4ae9db26a07b7afd7edeed60ce2fc4a6477f', hash="cc44568337a186c2e1ccc080b43b4ae9db26a07b7afd7edeed60ce2fc4a6477f",
fee=Decimal('0.000962550000')) fee=Decimal("0.000962550000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('10.000000000000'), amount=Decimal("10.000000000000"),
local_address=address('9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC'), local_address=address(
payment_id=PaymentID('0000000000000000'), "9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
transaction=tx) ),
payment_id=PaymentID("0000000000000000"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
tx = Transaction( tx = Transaction(
timestamp=datetime(2018, 1, 29, 21, 13, 28), timestamp=datetime(2018, 1, 29, 21, 13, 28),
height=None, height=None,
hash='d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c', hash="d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
fee=Decimal('0.000961950000')) fee=Decimal("0.000961950000"),
)
pm = IncomingPayment( pm = IncomingPayment(
amount=Decimal('3.140000000000'), amount=Decimal("3.140000000000"),
local_address=address('9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC'), local_address=address(
payment_id=PaymentID('03f6649304ea4cb2'), "9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
transaction=tx) ),
payment_id=PaymentID("03f6649304ea4cb2"),
transaction=tx,
)
self.transfers.append(pm) self.transfers.append(pm)
def height(self): def height(self):
@ -119,36 +154,47 @@ class FiltersTestCase(unittest.TestCase):
self.assertEqual(len(pmts), 7) self.assertEqual(len(pmts), 7)
def test_filter_payment_id(self): def test_filter_payment_id(self):
pmts = self.wallet.incoming(payment_id='cb248105ea6a9189') pmts = self.wallet.incoming(payment_id="cb248105ea6a9189")
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'e9a71c01875bec20812f71d155bfabf42024fde3ec82475562b817dcc8cbf8dc') "e9a71c01875bec20812f71d155bfabf42024fde3ec82475562b817dcc8cbf8dc",
pmts = self.wallet.incoming(payment_id='f75ad90e25d71a12') )
pmts = self.wallet.incoming(payment_id="f75ad90e25d71a12")
self.assertEqual(len(pmts), 3) self.assertEqual(len(pmts), 3)
pmts = self.wallet.incoming(payment_id=('cb248105ea6a9189', 'f75ad90e25d71a12')) pmts = self.wallet.incoming(payment_id=("cb248105ea6a9189", "f75ad90e25d71a12"))
self.assertEqual(len(pmts), 4) self.assertEqual(len(pmts), 4)
self.assertEqual( self.assertEqual(
pmts, pmts,
self.wallet.incoming(payment_id=(PaymentID('cb248105ea6a9189'), 'f75ad90e25d71a12'))) self.wallet.incoming(
payment_id=(PaymentID("cb248105ea6a9189"), "f75ad90e25d71a12")
),
)
def test_filter_address(self): def test_filter_address(self):
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address='BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En') local_address="BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En"
)
self.assertEqual(len(pmts), 2) self.assertEqual(len(pmts), 2)
self.assertEqual( self.assertEqual(
pmts, pmts,
self.wallet.incoming( self.wallet.incoming(
local_address=address('BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En'))) local_address=address(
"BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En"
)
),
)
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address=( local_address=(
'BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En', "BhE3cQvB7VF2uuXcpXp28Wbadez6GgjypdRS1F1Mzqn8Advd6q8VfaX8ZoEDobjejrMfpHeNXoX8MjY8q8prW1PEALgr1En",
'Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV')) "Bf6ngv7q2TBWup13nEm9AjZ36gLE6i4QCaZ7XScZUKDUeGbYEHmPRdegKGwLT8tBBK7P6L32RELNzCR6QzNFkmogDjvypyV",
)
)
self.assertEqual(len(pmts), 3) self.assertEqual(len(pmts), 3)
def test_filter_mempool(self): def test_filter_mempool(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
pmts = self.wallet.incoming() pmts = self.wallet.incoming()
self.assertEqual(len(pmts), 7) self.assertEqual(len(pmts), 7)
for p in pmts: for p in pmts:
@ -159,7 +205,8 @@ class FiltersTestCase(unittest.TestCase):
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') "d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(self.wallet.confirmations(pmts[0]), 0) self.assertEqual(self.wallet.confirmations(pmts[0]), 0)
self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0) self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
@ -167,35 +214,41 @@ class FiltersTestCase(unittest.TestCase):
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 1) self.assertEqual(len(w), 1)
self.assertIs(w[0].category, RuntimeWarning) self.assertIs(w[0].category, RuntimeWarning)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, max_height=99999999999999) pmts = self.wallet.incoming(
unconfirmed=True, confirmed=False, max_height=99999999999999
)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 2) self.assertEqual(len(w), 2)
self.assertIs(w[1].category, RuntimeWarning) self.assertIs(w[1].category, RuntimeWarning)
pmts = self.wallet.incoming(payment_id='03f6649304ea4cb2') pmts = self.wallet.incoming(payment_id="03f6649304ea4cb2")
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(unconfirmed=True, payment_id='03f6649304ea4cb2') pmts = self.wallet.incoming(unconfirmed=True, payment_id="03f6649304ea4cb2")
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC') local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
)
self.assertEqual(len(pmts), 4) self.assertEqual(len(pmts), 4)
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
unconfirmed=True, unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC') local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
)
self.assertEqual(len(pmts), 5) self.assertEqual(len(pmts), 5)
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC', local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
payment_id='03f6649304ea4cb2') payment_id="03f6649304ea4cb2",
)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
unconfirmed=True, unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC', local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
payment_id='03f6649304ea4cb2') payment_id="03f6649304ea4cb2",
)
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual(len(w), 2) self.assertEqual(len(w), 2)
def test_filter_mempool_absent(self): def test_filter_mempool_absent(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
pmts = self.wallet.incoming() pmts = self.wallet.incoming()
self.assertEqual(len(pmts), 7) self.assertEqual(len(pmts), 7)
for p in pmts: for p in pmts:
@ -210,94 +263,106 @@ class FiltersTestCase(unittest.TestCase):
def test_filter_mempool_present(self): def test_filter_mempool_present(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
pmts = self.wallet.incoming(unconfirmed=True) pmts = self.wallet.incoming(unconfirmed=True)
self.assertEqual(len(pmts), 8) self.assertEqual(len(pmts), 8)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False) pmts = self.wallet.incoming(unconfirmed=True, confirmed=False)
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') "d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(self.wallet.confirmations(pmts[0]), 0) self.assertEqual(self.wallet.confirmations(pmts[0]), 0)
self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0) self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def test_filter_mempool_filter_height(self): def test_filter_mempool_filter_height(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
# mempool is always excluded and warnings are generated # mempool is always excluded and warnings are generated
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, min_height=1) pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, min_height=1)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 1) self.assertEqual(len(w), 1)
self.assertIs(w[0].category, RuntimeWarning) self.assertIs(w[0].category, RuntimeWarning)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, max_height=99999999999999) pmts = self.wallet.incoming(
unconfirmed=True, confirmed=False, max_height=99999999999999
)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 2) self.assertEqual(len(w), 2)
self.assertIs(w[1].category, RuntimeWarning) self.assertIs(w[1].category, RuntimeWarning)
def test_filter_mempool_filter_payment_id(self): def test_filter_mempool_filter_payment_id(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
# mempool excluded # mempool excluded
pmts = self.wallet.incoming(payment_id='03f6649304ea4cb2') pmts = self.wallet.incoming(payment_id="03f6649304ea4cb2")
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
# mempool included # mempool included
pmts = self.wallet.incoming(unconfirmed=True, payment_id='03f6649304ea4cb2') pmts = self.wallet.incoming(unconfirmed=True, payment_id="03f6649304ea4cb2")
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') "d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def test_filter_mempool_filter_address(self): def test_filter_mempool_filter_address(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
# mempool excluded # mempool excluded
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC') local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC"
)
self.assertEqual(len(pmts), 4) self.assertEqual(len(pmts), 4)
# mempool included # mempool included
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
unconfirmed=True, unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC') local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
)
self.assertEqual(len(pmts), 5) self.assertEqual(len(pmts), 5)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def test_filter_mempool_filter_address_and_payment_id(self): def test_filter_mempool_filter_address_and_payment_id(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
# mempool excluded # mempool excluded
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC', local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
payment_id='03f6649304ea4cb2') payment_id="03f6649304ea4cb2",
)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
# mempool included # mempool included
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
unconfirmed=True, unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC', local_address="9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC",
payment_id='03f6649304ea4cb2') payment_id="03f6649304ea4cb2",
)
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') "d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def test_filter_mempool_filter_txid(self): def test_filter_mempool_filter_txid(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter("always")
# mempool excluded # mempool excluded
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
tx_id='d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') tx_id="d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c"
)
self.assertEqual(len(pmts), 0) self.assertEqual(len(pmts), 0)
# mempool included # mempool included
pmts = self.wallet.incoming( pmts = self.wallet.incoming(
unconfirmed=True, unconfirmed=True,
tx_id='d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') tx_id="d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(len(pmts), 1) self.assertEqual(len(pmts), 1)
self.assertEqual( self.assertEqual(
pmts[0].transaction.hash, pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c') "d29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c",
)
self.assertEqual(len(w), 0) self.assertEqual(len(w), 0)
def test_filter_excessive(self): def test_filter_excessive(self):
self.assertRaises(ValueError, self.wallet.incoming, excessive_argument='foo') self.assertRaises(ValueError, self.wallet.incoming, excessive_argument="foo")

Loading…
Cancel
Save