Merge branch 'master' into 0.5.x

This commit is contained in:
Michał Sałaban 2019-01-02 23:16:03 +00:00
commit 6c1f667840
10 changed files with 136 additions and 74 deletions

View file

@ -14,7 +14,7 @@ Python Monero module
A comprehensive Python module for handling Monero cryptocurrency.
* release 0.4.3
* release 0.4.4
* open source: https://github.com/emesik/monero-python
* works with Monero 0.12.x and `the latest source`_ (at least we try to keep up)
* Python 2.x and 3.x compatible

View file

@ -55,9 +55,9 @@ author = 'Michal Salaban'
# built documents.
#
# The short X.Y version.
version = '0.4.3'
version = '0.4.4'
# The full version, including alpha/beta/rc tags.
release = '0.4.3'
release = '0.4.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -102,9 +102,13 @@ html_static_path = ['_static']
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'index': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html'
],
'**': [
'searchbox.html',
'globaltoc.html'
]
}
@ -164,6 +168,3 @@ texinfo_documents = [
author, 'MoneroPythonmodule', 'One line description of project.',
'Miscellaneous'),
]

View file

@ -177,6 +177,10 @@ and ``unconfirmed`` query parameters that accept boolean values:
You may as well query for both confirmed and unconfirmed transactions using
``wallet.incoming(unconfirmed=True)`` (the default value for ``confirmed`` is ``True``).
.. note:: Mempool transactions don't belong to the blockchain (yet), therefore they have no height.
Setting ``min_height`` or ``max_height`` arguments will **always exclude mempool
transactions**. If ``unconfirmed`` is also set to ``True``, a warning will be issued.
.. _sending-payments:
Sending payments

View file

@ -1,3 +1,3 @@
from . import address, account, daemon, wallet, numbers, prio, wordlists, seed
__version__ = '0.4.3'
__version__ = '0.4.4'

View file

@ -4,6 +4,7 @@ import struct
from sha3 import keccak_256
from . import base58
from . import ed25519
from . import numbers
_ADDR_REGEX = re.compile(r'^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{95}$')
@ -90,6 +91,20 @@ class Address(BaseAddress):
"""
return hexlify(self._decoded[1:33]).decode()
def check_private_view_key(self, key):
"""Checks if private view key matches this address.
:rtype: bool
"""
return ed25519.public_from_secret_hex(key) == self.view_key()
def check_private_spend_key(self, key):
"""Checks if private spend key matches this address.
:rtype: bool
"""
return ed25519.public_from_secret_hex(key) == self.spend_key()
def with_payment_id(self, payment_id=0):
"""Integrates payment id into the address.

View file

@ -3,6 +3,7 @@
#
# Parts Copyright (c) 2016 The MoneroPy Developers. Released under the BSD 3-Clause
from binascii import hexlify, unhexlify
import hashlib
import operator as _oper
import sys as _sys
@ -133,3 +134,10 @@ def scalarmultbase(e):
if e & 1: Q = edwards(Q, B)
return Q
def public_from_secret(k):
keyInt = decodeint(k)
aG = scalarmultbase(keyInt)
return encodepoint(aG)
def public_from_secret_hex(hk):
return hexlify(public_from_secret(unhexlify(hk))).decode()

View file

@ -137,14 +137,10 @@ class Seed(object):
return self.sc_reduce(h.digest())
def public_spend_key(self):
keyInt = ed25519.decodeint(unhexlify(self.secret_spend_key()))
aG = ed25519.scalarmultbase(keyInt)
return hexlify(ed25519.encodepoint(aG)).decode()
return ed25519.public_from_secret_hex(self.secret_spend_key())
def public_view_key(self):
keyInt = ed25519.decodeint(unhexlify(self.secret_view_key()))
aG = ed25519.scalarmultbase(keyInt)
return hexlify(ed25519.encodepoint(aG)).decode()
return ed25519.public_from_secret_hex(self.secret_view_key())
def public_address(self, net='mainnet'):
"""Returns the master :class:`Address <monero.address.Address>` represented by the seed.

View file

@ -1,4 +1,5 @@
import sys
import warnings
from .address import address
from .numbers import PaymentID
@ -160,7 +161,12 @@ class PaymentFilter(object):
_payment_id = filterparams.pop('payment_id', None)
if len(filterparams) > 0:
raise ValueError("Excessive arguments for payment query: {}".format(filterparams))
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. "
"As mempool transactions have no height at all, they will be excluded "
"from the result.",
RuntimeWarning)
if _local_address is None:
self.local_addresses = []
else:

View file

@ -78,6 +78,22 @@ class Tests(object):
self.assertNotEqual(a, 0)
def test_check_private_view_key(self):
a = Address(self.addr)
self.assertFalse(a.check_private_view_key(self.ssk))
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.pvk))
self.assertFalse(a.check_private_view_key('0000000000000000000000000000000000000000000000000000000000000000'))
def test_check_private_spend_key(self):
a = Address(self.addr)
self.assertTrue(a.check_private_spend_key(self.ssk))
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.pvk))
self.assertFalse(a.check_private_spend_key('0000000000000000000000000000000000000000000000000000000000000000'))
def test_idempotence(self):
a = Address(self.addr)
a_idem = Address(a)
@ -130,46 +146,53 @@ class Tests(object):
sa = SubAddress(self.subaddr)
self.assertRaises(TypeError, sa.with_payment_id, self.pid)
class AddressTestCase(unittest.TestCase, Tests):
addr = '43aeKax1ts4BoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuh7PFUAmd'
psk = '33a7ceb933b793408d49e82c0a34664a4be7117243cb77a64ef280b866d8aa6e'
pvk = '96f70d63d9d3558b97a5dd200a170b4f45b3177a274aa90496ea683896ff6438'
addr = '47ewoP19TN7JEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef'
ssk = 'e0fe01d5794e240a26609250c0d7e01673219eececa3f499d5cfa20a75739b0a'
svk = '6d9056aa2c096bfcd2f272759555e5764ba204dd362604a983fa3e0aafd35901'
psk = '9f2a76d879aaf0670039dc8dbdca01f0ca26a2f6d93268e3674666bfdc5957e4'
pvk = '716cfc7da7e6ce366935c55747839a85be798037ab189c7dd0f10b7f1690cb78'
pid = '4a6f686e47616c74'
subaddr = '83bK2pMxCQXdRyd6W1haNWYRsF6Qb3iGa8gxKEynm9U7cYoXrMHFwRrFFuxRSgnLtGe7LM8SmrPY6L3TVBa3UV3YLuVJ7Rw'
iaddr = '4DHKLPmWW8aBoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuhAR6GpL18QNwE8h3TuF'
iaddr = '4HMcpBpe4ddJEEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw'
subaddr = '84LooD7i35SFppgf4tQ453Vi3q5WexSUXaVgut69ro8MFnmHwuezAArEZTZyLr9fS6QotjqkSAxSF6d1aDgsPoX849izJ7m'
mainnet = True
testnet = False
stagenet = False
addr_invalid = '43aeKax1ts4boEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuh7PFUAmd'
iaddr_invalid = '4DHKLpmWW8aBoEbSyzKVbbDRmc8nsnpZLUpQBYvhUxs3KVrodnaFaBEQMDp69u4VaiEG3LSQXA6M61mXPrztCLuhAR6GpL18QNwE8h3TuF'
addr_invalid = '47ewoP19TN7JCEnFKUJHAYhGxkeTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8Ebsmgef'
iaddr_invalid = '4HMcpBpe4ddJEEnFKUJHAYhGxkyTRH82sf36giEp9AcNfDBfkAtRLX7A6rZz18bbNHPNV7ex6WYbMN3aKisFRJZ8M7yKhzQhKW3ECCLWQw'
class TestnetAddressTestCase(AddressTestCase, Tests):
addr = '9vgV48wWAPTWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YQN2Q9ag'
psk = '5cbcfbcea7cc62b1aeb76758ad8df5f8cbe0c63d40c8cd9c49377bbc9c9b9520'
pvk = 'de048ca310ff7d6e3b6714bccdebd62c56d680a10272846c875241fa2c5fc1cf'
addr = '9wuKTHsxGiwEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N'
ssk = '4f5b7af2c1942067ba33d34318b9735cb46ab5d50b75294844c82a9dd872c201'
svk = '60cf228f2bf7f6a70643afe9468fde254145dbd3aab4072ede14bf8bae914103'
psk = '7cf743dcfd23d452e9b2936caeb622c9849f1ff1ddfd62bfdfac64113c1a4e92'
pvk = 'e3924b14d99a9c088e5a45278d5218f2d053b1c03c480f00ed2ee3dce80806c4'
pid = '4a6f686e47616c74'
iaddr = 'A6PA4wkzmeyWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YbfyqvDecDn3E7cvp9b'
subaddr = 'BbBjyYoYNNwFfL8RRVRTMiZUofBLpjRxdNnd5E4LyGcAK5CEsnL3gmE5QkrDRta7RPficGHcFdR6rUwWcjnwZVvCE3tLxhJ'
subaddr = 'BaU3yLuDqdcETYzeF7vFSVEKNR4sSGxBV1Evrw5yNBf2VMiuAwfDmiF3RHqLHkaA5A6RGiNNRUqvtaqhMtdjA1SQ1tnQV8D'
iaddr = 'A7bzU6hSszTEsMp2fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh'
mainnet = False
testnet = True
stagenet = False
addr_invalid = '9vgV48wWAPTWik5QSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUbbV6YQN2Q9ag'
iaddr_invalid = 'A6PA4wkzmeyWik5qSUSoGYicdvvsbSNHrT9Arsx1XBTz6VrWPSgfmnUKSPZDMyX4Ms8R9TkhB4uFqK9s5LUBbV6YbfyqvDecDn3E7cvp9b'
addr_invalid = '9wuKTHsxGiwEsMp3fYzJiVahyhU2aZi1oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwP7hMs5N'
iaddr_invalid = 'A7bzU6hSszTEsMp2fYzJiVahyhU2aZi2oZ6R6fK5U64uRa1Pxi8diZh2S1GJFqYXRRhcbfzfWiPD819zKEZkXTMwZqGSmLeBXqMEBnZVkh'
class StagenetAddressTestCase(AddressTestCase, Tests):
addr = '56cXYWG13YKaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkeUVogGN3'
psk = '7e33891fe6ea30c7fd79d48e250906329104dc77407cf732699f41564df8ca8e'
pvk = '77a3720428f91f0f58a196bb374f703b3ca45fa55f0764adc81ff241c4c797f3'
addr = '52jzuBBUMty3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6'
ssk = 'a8733c61797115db4ec8a5ce39fb811f81dd4ec163b880526683e059c7e62503'
svk = 'fd5c0d25f8f994268079a4f7844274dc870a7c2b88fbfc24ba318375e1d9430f'
psk = '180c1d7bbf7f2e11aa90d0f61bf49024370e01cd54f33f2d36bba0357c9c205f'
pvk = '94b66a81e646927b3da74392306f789c5024734b4ce6351ad74c4c7d7351b3ad'
pid = '4a6f686e47616c74'
iaddr = '5GKCZK5VeoqaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkehhE4RH8N7QfEAC8jMy'
subaddr = '7417qYoKBoYXCugU2KvJBZExmyjav4n1MVME74AeWNwxQ39wKtbWdyP6YGuMK6C7HkAjBuVcbUYmCWbxDLwk9GAX4qyb48U'
subaddr = '7AeQwvrLtPeYoXVPRkEu8oEL7N9wnqHjYKwSvTf6YKbHgYmw6AJMsjggzVLo21egMK9qcoV1mxCTfP4FbaGb7JEMDfpLetk'
iaddr = '5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS'
mainnet = False
testnet = False
stagenet = True
addr_invalid ='7417qYoKBoYXCugU2KvJBZExmyjav4n1MVME74AeWNwxQ39wKtbWdyP6YGuMK6C7HkAjBuVcbUYmCWbyDLwk9GAX4qyb48U'
iaddr_invalid = '5GKCZK5VeuqaT9z1aEy2hb9TZNnxrW3zE9S4nTQVDux5Qq7UYsmjuux3Zstxkorj9HAufyWLU3FwHW4uERQF6tkehhE4RH8N7QfEAC8jMy'
addr_invalid = '52jzuBBUMty3xPL3JsQxGP74LDuV6H1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKpbEgC5VPhfoAiVj8LdAqbp6'
iaddr_invalid = '5CSfuyzxyAV3xPL3JsQxGP74LDuV6E1LS8Zda1PbdqQjGzFmH6N9ep9McbFKMALujVT9S5mKppEgC5VPhfoAiVj8Vz8ySmoqYgTE8dR1yS'
class KnownBugsTest(unittest.TestCase):

View file

@ -1,12 +1,13 @@
from datetime import datetime
from decimal import Decimal
import unittest
import warnings
from monero.wallet import Wallet
from monero.account import Account
from monero.address import address
from monero.numbers import PaymentID
from monero.transaction import IncomingPayment, OutgoingPayment, Transaction
from monero.transaction import IncomingPayment, Transaction
class FiltersTestCase(unittest.TestCase):
def setUp(self):
@ -146,43 +147,51 @@ class FiltersTestCase(unittest.TestCase):
self.assertEqual(len(pmts), 3)
def test_filter_mempool(self):
pmts = self.wallet.incoming()
self.assertEqual(len(pmts), 7)
for p in pmts:
self.assertGreater(self.wallet.confirmations(p.transaction), 0)
pmts = self.wallet.incoming(unconfirmed=True)
self.assertEqual(len(pmts), 8)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False)
self.assertEqual(len(pmts), 1)
self.assertEqual(
pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c')
self.assertEqual(self.wallet.confirmations(pmts[0]), 0)
self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, min_height=1)
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, max_height=99999999999999)
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(unconfirmed=True, payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 1)
pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC')
self.assertEqual(len(pmts), 4)
pmts = self.wallet.incoming(
unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC')
self.assertEqual(len(pmts), 5)
pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC',
payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(
unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC',
payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 1)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
pmts = self.wallet.incoming()
self.assertEqual(len(pmts), 7)
for p in pmts:
self.assertGreater(self.wallet.confirmations(p.transaction), 0)
pmts = self.wallet.incoming(unconfirmed=True)
self.assertEqual(len(pmts), 8)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False)
self.assertEqual(len(pmts), 1)
self.assertEqual(
pmts[0].transaction.hash,
'd29264ad317e8fdb55ea04484c00420430c35be7b3fe6dd663f99aebf41a786c')
self.assertEqual(self.wallet.confirmations(pmts[0]), 0)
self.assertEqual(self.wallet.confirmations(pmts[0].transaction), 0)
self.assertEqual(len(w), 0)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, min_height=1)
self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 1)
self.assertIs(w[0].category, RuntimeWarning)
pmts = self.wallet.incoming(unconfirmed=True, confirmed=False, max_height=99999999999999)
self.assertEqual(len(pmts), 0)
self.assertEqual(len(w), 2)
self.assertIs(w[1].category, RuntimeWarning)
pmts = self.wallet.incoming(payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(unconfirmed=True, payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 1)
pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC')
self.assertEqual(len(pmts), 4)
pmts = self.wallet.incoming(
unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC')
self.assertEqual(len(pmts), 5)
pmts = self.wallet.incoming(
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC',
payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 0)
pmts = self.wallet.incoming(
unconfirmed=True,
local_address='9tQoHWyZ4yXUgbz9nvMcFZUfDy5hxcdZabQCxmNCUukKYicXegsDL7nQpcUa3A1pF6K3fhq3scsyY88tdB1MqucULcKzWZC',
payment_id='03f6649304ea4cb2')
self.assertEqual(len(pmts), 1)
self.assertEqual(len(w), 2)
def test_filter_excessive(self):
self.assertRaises(ValueError, self.wallet.incoming, excessive_argument='foo')