Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions examples/address_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
from typing import Optional, Text

from iota import __version__, Iota
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Seed
from six import binary_type, moves as compat, text_type


def main(uri, index, count):
# type: (Text, int, Optional[int], bool) -> None
def main(uri, index, count, security, checksum):
# type: (Text, int, Optional[int], Optional[int], bool) -> None
seed = get_seed()

# Create the API instance.
Expand All @@ -34,7 +35,7 @@ def main(uri, index, count):
print('')

# Here's where all the magic happens!
api_response = api.get_new_addresses(index, count)
api_response = api.get_new_addresses(index, count, security, checksum)
for addy in api_response['addresses']:
print(binary_type(addy).decode('ascii'))

Expand Down Expand Up @@ -111,4 +112,20 @@ def output_seed(seed):
'If not specified, the first unused address will be returned.'
)

parser.add_argument(
'--security',
type = int,
default = AddressGenerator.DEFAULT_SECURITY_LEVEL,
help = 'Security level to be used for the private key / address. '
'Can be 1, 2 or 3',
)

parser.add_argument(
'--with-checksum',
action = 'store_true',
default = False,
dest = 'checksum',
help = 'List the address with the checksum.',
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


main(**vars(parser.parse_args(argv[1:])))
8 changes: 7 additions & 1 deletion iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,9 @@ def get_new_addresses(
index = 0,
count = 1,
security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL,
checksum = False,
):
# type: (int, Optional[int], int) -> dict
# type: (int, Optional[int], int, bool) -> dict
"""
Generates one or more new addresses from the seed.

Expand All @@ -636,6 +637,10 @@ def get_new_addresses(

This value must be between 1 and 3, inclusive.

:param checksum:
Specify whether to return the address with the checksum.
Defaults to False.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there's some documentation we could include a link to here, but I can't find any ¯\_(ツ)_/¯

Side note: typing the shrug dude in a Markdown editor sure is tricky!


:return:
Dict with the following items::

Expand All @@ -651,6 +656,7 @@ def get_new_addresses(
count = count,
index = index,
securityLevel = security_level,
checksum = checksum,
seed = self.seed,
)

Expand Down
26 changes: 17 additions & 9 deletions iota/commands/extended/get_new_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import filters as f

from iota import Address
from iota import Address, AddressChecksum
from iota.commands import FilterCommand, RequestFilter
from iota.commands.core.find_transactions import FindTransactionsCommand
from iota.crypto.addresses import AddressGenerator
Expand All @@ -33,28 +33,33 @@ def get_response_filter(self):
pass

def _execute(self, request):
checksum = request['checksum'] # type: bool
count = request['count'] # type: Optional[int]
index = request['index'] # type: int
security_level = request['securityLevel'] # type: int
seed = request['seed'] # type: Seed

return {
'addresses': self._find_addresses(seed, index, count, security_level),
'addresses':
self._find_addresses(seed, index, count, security_level, checksum),
}

def _find_addresses(self, seed, index, count, security_level):
# type: (Seed, int, Optional[int], int) -> List[Address]
def _find_addresses(self, seed, index, count, security_level, checksum):
# type: (Seed, int, Optional[int], int, bool) -> List[Address]
"""
Find addresses matching the command parameters.
"""
# type: (Seed, int, Optional[int]) -> List[Address]
generator = AddressGenerator(seed, security_level)
generator = AddressGenerator(seed, security_level, checksum)

if count is None:
# Connect to Tangle and find the first address without any
# transactions.
for addy in generator.create_iterator(start=index):
response = FindTransactionsCommand(self.adapter)(addresses=[addy])
# We use addy.address here because FindTransactions does
# not work on an address with a checksum
response = FindTransactionsCommand(self.adapter)(
addresses=[addy.address]
)

if not response.get('hashes'):
return [addy]
Expand All @@ -75,8 +80,10 @@ def __init__(self):
super(GetNewAddressesRequestFilter, self).__init__(
{
# Everything except ``seed`` is optional.
'count': f.Type(int) | f.Min(1),
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),

'checksum': f.Type(bool) | f.Optional(default=False),
'count': f.Type(int) | f.Min(1),
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),

'securityLevel':
f.Type(int)
Expand All @@ -88,6 +95,7 @@ def __init__(self):
},

allow_missing_keys = {
'checksum',
'count',
'index',
'securityLevel',
Expand Down
10 changes: 7 additions & 3 deletions iota/crypto/addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ class AddressGenerator(Iterable[Address]):
- :py:class:`iota.transaction.BundleValidator`
"""

def __init__(self, seed, security_level=DEFAULT_SECURITY_LEVEL):
# type: (TrytesCompatible, int) -> None
def __init__(self, seed, security_level=DEFAULT_SECURITY_LEVEL, checksum=False):
# type: (TrytesCompatible, int, bool) -> None
super(AddressGenerator, self).__init__()

self.security_level = security_level
self.checksum = checksum
self.seed = Seed(seed)

def __iter__(self):
Expand Down Expand Up @@ -175,7 +176,10 @@ def _generate_address(self, key_iterator):

Used in the event of a cache miss.
"""
return self.address_from_digest(self._get_digest(key_iterator))
if self.checksum:
return self.address_from_digest(self._get_digest(key_iterator)).with_valid_checksum()
else:
return self.address_from_digest(self._get_digest(key_iterator))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that work just for 3 extra lines of code :P


@staticmethod
def _get_digest(key_iterator):
Expand Down
46 changes: 46 additions & 0 deletions test/commands/extended/get_new_addresses_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def test_pass_happy_path(self):
'index': 1,
'count': 1,
'securityLevel': 2,
'checksum': False,
}

filter_ = self._filter(request)
Expand All @@ -59,6 +60,7 @@ def test_pass_optional_parameters_excluded(self):
'index': 0,
'count': None,
'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL,
'checksum': False,
},
)

Expand All @@ -75,6 +77,9 @@ def test_pass_compatible_types(self):
'index': 100,
'count': 8,
'securityLevel': 2,

# ``checksum`` must be boolean.
'checksum': False,
})

self.assertFilterPasses(filter_)
Expand All @@ -86,6 +91,7 @@ def test_pass_compatible_types(self):
'index': 100,
'count': 8,
'securityLevel': 2,
'checksum': False,
},
)

Expand All @@ -111,6 +117,7 @@ def test_fail_unexpected_parameters(self):
'index': None,
'count': 1,
'securityLevel': 2,
'checksum': False,

# Some men just want to watch the world burn.
'foo': 'bar',
Expand Down Expand Up @@ -306,6 +313,21 @@ def test_fail_security_level_wrong_type(self):
},
)

def test_fail_checksum_wrong_type(self):
"""
``checksum`` is not a boolean.
"""
self.assertFilterErrors(
{
'checksum': '2',
'seed': Seed(self.seed),
},

{
'checksum': [f.Type.CODE_WRONG_TYPE],
},
)


class GetNewAddressesCommandTestCase(TestCase):
# noinspection SpellCheckingInspection
Expand Down Expand Up @@ -333,6 +355,13 @@ def setUp(self):
b'IWYTLQUUHDWSOVXLIKVJTYZBFKLABWRBFYVSMD9NB',
)

self.addy_1_checksum =\
Address(
b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA'
b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH'
b'9T9FPJROTW',
)

def test_wireup(self):
"""
Verify that the command is wired up correctly.
Expand Down Expand Up @@ -446,3 +475,20 @@ def test_get_addresses_online(self):
},
],
)

def test_new_address_checksum(self):
"""
Generate address with a checksum.
"""
response =\
self.command(
checksum = True,
count = 1,
index = 0,
seed = self.seed,
)

self.assertDictEqual(
response,
{'addresses': [self.addy_1_checksum]},
)
Copy link
Contributor

@todofixthis todofixthis Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests look great, thank you!

Since AddressGenerator._generate_address() now also has a checksum argument, could we add a test to AddressGeneratorTestCase as well?

That way, if we ever modify GetNewAddressesCommand and change the way it does checksums, we will still have coverage for AddressGenerator's checksum functionality.

34 changes: 34 additions & 0 deletions test/crypto/addresses_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,37 @@ def test_security_level_elevated(self):
),
],
)

def test_generator_checksum(self):
"""
Creating a generator with checksums on the addresses.
"""
ag = AddressGenerator(
self.seed_2,
security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL,
checksum=True
)

generator = ag.create_iterator()

# noinspection SpellCheckingInspection
self.assertEqual(
next(generator),

Address(
b'FNKCVJPUANHNWNBAHFBTCONMCUBC9KCZ9EKREBCJ'
b'AFMABCTEPLGGXDJXVGPXDCFOUCRBWFJFLEAVOEUPY'
b'ADHVCBXFD',
),
)

# noinspection SpellCheckingInspection
self.assertEqual(
next(generator),

Address(
b'MSYILYYZLSJ99TDMGQHDOBWGHTBARCBGJZE9PIMQ'
b'LTEXJXKTDREGVTPA9NDGGLQHTMGISGRAKSLYPGWMB'
b'WIKQRCIOD',
),
)