Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit 0a5d564

Browse files
authored
Merge pull request #106 from plenarius/new_address_checksum
Issue #89 - Initial work on adding checksum option to get_new_addresses
2 parents ff5e944 + 9c67ba3 commit 0a5d564

File tree

6 files changed

+131
-16
lines changed

6 files changed

+131
-16
lines changed

examples/address_generator.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
from typing import Optional, Text
1313

1414
from iota import __version__, Iota
15+
from iota.crypto.addresses import AddressGenerator
1516
from iota.crypto.types import Seed
1617
from six import binary_type, moves as compat, text_type
1718

1819

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

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

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

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

115+
parser.add_argument(
116+
'--security',
117+
type = int,
118+
default = AddressGenerator.DEFAULT_SECURITY_LEVEL,
119+
help = 'Security level to be used for the private key / address. '
120+
'Can be 1, 2 or 3',
121+
)
122+
123+
parser.add_argument(
124+
'--with-checksum',
125+
action = 'store_true',
126+
default = False,
127+
dest = 'checksum',
128+
help = 'List the address with the checksum.',
129+
)
130+
114131
main(**vars(parser.parse_args(argv[1:])))

iota/api.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,9 @@ def get_new_addresses(
611611
index = 0,
612612
count = 1,
613613
security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL,
614+
checksum = False,
614615
):
615-
# type: (int, Optional[int], int) -> dict
616+
# type: (int, Optional[int], int, bool) -> dict
616617
"""
617618
Generates one or more new addresses from the seed.
618619
@@ -636,6 +637,10 @@ def get_new_addresses(
636637
637638
This value must be between 1 and 3, inclusive.
638639
640+
:param checksum:
641+
Specify whether to return the address with the checksum.
642+
Defaults to False.
643+
639644
:return:
640645
Dict with the following items::
641646
@@ -651,6 +656,7 @@ def get_new_addresses(
651656
count = count,
652657
index = index,
653658
securityLevel = security_level,
659+
checksum = checksum,
654660
seed = self.seed,
655661
)
656662

iota/commands/extended/get_new_addresses.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import filters as f
88

9-
from iota import Address
9+
from iota import Address, AddressChecksum
1010
from iota.commands import FilterCommand, RequestFilter
1111
from iota.commands.core.find_transactions import FindTransactionsCommand
1212
from iota.crypto.addresses import AddressGenerator
@@ -33,28 +33,33 @@ def get_response_filter(self):
3333
pass
3434

3535
def _execute(self, request):
36+
checksum = request['checksum'] # type: bool
3637
count = request['count'] # type: Optional[int]
3738
index = request['index'] # type: int
3839
security_level = request['securityLevel'] # type: int
3940
seed = request['seed'] # type: Seed
4041

4142
return {
42-
'addresses': self._find_addresses(seed, index, count, security_level),
43+
'addresses':
44+
self._find_addresses(seed, index, count, security_level, checksum),
4345
}
4446

45-
def _find_addresses(self, seed, index, count, security_level):
46-
# type: (Seed, int, Optional[int], int) -> List[Address]
47+
def _find_addresses(self, seed, index, count, security_level, checksum):
48+
# type: (Seed, int, Optional[int], int, bool) -> List[Address]
4749
"""
4850
Find addresses matching the command parameters.
4951
"""
50-
# type: (Seed, int, Optional[int]) -> List[Address]
51-
generator = AddressGenerator(seed, security_level)
52+
generator = AddressGenerator(seed, security_level, checksum)
5253

5354
if count is None:
5455
# Connect to Tangle and find the first address without any
5556
# transactions.
5657
for addy in generator.create_iterator(start=index):
57-
response = FindTransactionsCommand(self.adapter)(addresses=[addy])
58+
# We use addy.address here because FindTransactions does
59+
# not work on an address with a checksum
60+
response = FindTransactionsCommand(self.adapter)(
61+
addresses=[addy.address]
62+
)
5863

5964
if not response.get('hashes'):
6065
return [addy]
@@ -75,8 +80,10 @@ def __init__(self):
7580
super(GetNewAddressesRequestFilter, self).__init__(
7681
{
7782
# Everything except ``seed`` is optional.
78-
'count': f.Type(int) | f.Min(1),
79-
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),
83+
84+
'checksum': f.Type(bool) | f.Optional(default=False),
85+
'count': f.Type(int) | f.Min(1),
86+
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),
8087

8188
'securityLevel':
8289
f.Type(int)
@@ -88,6 +95,7 @@ def __init__(self):
8895
},
8996

9097
allow_missing_keys = {
98+
'checksum',
9199
'count',
92100
'index',
93101
'securityLevel',

iota/crypto/addresses.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ class AddressGenerator(Iterable[Address]):
4040
- :py:class:`iota.transaction.BundleValidator`
4141
"""
4242

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

4747
self.security_level = security_level
48+
self.checksum = checksum
4849
self.seed = Seed(seed)
4950

5051
def __iter__(self):
@@ -175,7 +176,10 @@ def _generate_address(self, key_iterator):
175176
176177
Used in the event of a cache miss.
177178
"""
178-
return self.address_from_digest(self._get_digest(key_iterator))
179+
if self.checksum:
180+
return self.address_from_digest(self._get_digest(key_iterator)).with_valid_checksum()
181+
else:
182+
return self.address_from_digest(self._get_digest(key_iterator))
179183

180184
@staticmethod
181185
def _get_digest(key_iterator):

test/commands/extended/get_new_addresses_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def test_pass_happy_path(self):
3535
'index': 1,
3636
'count': 1,
3737
'securityLevel': 2,
38+
'checksum': False,
3839
}
3940

4041
filter_ = self._filter(request)
@@ -59,6 +60,7 @@ def test_pass_optional_parameters_excluded(self):
5960
'index': 0,
6061
'count': None,
6162
'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL,
63+
'checksum': False,
6264
},
6365
)
6466

@@ -75,6 +77,9 @@ def test_pass_compatible_types(self):
7577
'index': 100,
7678
'count': 8,
7779
'securityLevel': 2,
80+
81+
# ``checksum`` must be boolean.
82+
'checksum': False,
7883
})
7984

8085
self.assertFilterPasses(filter_)
@@ -86,6 +91,7 @@ def test_pass_compatible_types(self):
8691
'index': 100,
8792
'count': 8,
8893
'securityLevel': 2,
94+
'checksum': False,
8995
},
9096
)
9197

@@ -111,6 +117,7 @@ def test_fail_unexpected_parameters(self):
111117
'index': None,
112118
'count': 1,
113119
'securityLevel': 2,
120+
'checksum': False,
114121

115122
# Some men just want to watch the world burn.
116123
'foo': 'bar',
@@ -306,6 +313,21 @@ def test_fail_security_level_wrong_type(self):
306313
},
307314
)
308315

316+
def test_fail_checksum_wrong_type(self):
317+
"""
318+
``checksum`` is not a boolean.
319+
"""
320+
self.assertFilterErrors(
321+
{
322+
'checksum': '2',
323+
'seed': Seed(self.seed),
324+
},
325+
326+
{
327+
'checksum': [f.Type.CODE_WRONG_TYPE],
328+
},
329+
)
330+
309331

310332
class GetNewAddressesCommandTestCase(TestCase):
311333
# noinspection SpellCheckingInspection
@@ -333,6 +355,13 @@ def setUp(self):
333355
b'IWYTLQUUHDWSOVXLIKVJTYZBFKLABWRBFYVSMD9NB',
334356
)
335357

358+
self.addy_1_checksum =\
359+
Address(
360+
b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA'
361+
b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH'
362+
b'9T9FPJROTW',
363+
)
364+
336365
def test_wireup(self):
337366
"""
338367
Verify that the command is wired up correctly.
@@ -446,3 +475,20 @@ def test_get_addresses_online(self):
446475
},
447476
],
448477
)
478+
479+
def test_new_address_checksum(self):
480+
"""
481+
Generate address with a checksum.
482+
"""
483+
response =\
484+
self.command(
485+
checksum = True,
486+
count = 1,
487+
index = 0,
488+
seed = self.seed,
489+
)
490+
491+
self.assertDictEqual(
492+
response,
493+
{'addresses': [self.addy_1_checksum]},
494+
)

test/crypto/addresses_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,37 @@ def test_security_level_elevated(self):
279279
),
280280
],
281281
)
282+
283+
def test_generator_checksum(self):
284+
"""
285+
Creating a generator with checksums on the addresses.
286+
"""
287+
ag = AddressGenerator(
288+
self.seed_2,
289+
security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL,
290+
checksum=True
291+
)
292+
293+
generator = ag.create_iterator()
294+
295+
# noinspection SpellCheckingInspection
296+
self.assertEqual(
297+
next(generator),
298+
299+
Address(
300+
b'FNKCVJPUANHNWNBAHFBTCONMCUBC9KCZ9EKREBCJ'
301+
b'AFMABCTEPLGGXDJXVGPXDCFOUCRBWFJFLEAVOEUPY'
302+
b'ADHVCBXFD',
303+
),
304+
)
305+
306+
# noinspection SpellCheckingInspection
307+
self.assertEqual(
308+
next(generator),
309+
310+
Address(
311+
b'MSYILYYZLSJ99TDMGQHDOBWGHTBARCBGJZE9PIMQ'
312+
b'LTEXJXKTDREGVTPA9NDGGLQHTMGISGRAKSLYPGWMB'
313+
b'WIKQRCIOD',
314+
),
315+
)

0 commit comments

Comments
 (0)