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

Commit 6d3f2b5

Browse files
committed
[UNSTABLE][#84] Detect insecure bundle hashes.
Note: `PrepareTransferCommand` tests still need to be refactored.
1 parent 7fc2eeb commit 6d3f2b5

File tree

2 files changed

+81
-12
lines changed

2 files changed

+81
-12
lines changed

iota/transaction/creation.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99

1010
from iota.crypto import HASH_LENGTH
1111
from iota.crypto.kerl import Kerl
12-
from iota.crypto.signing import KeyGenerator
12+
from iota.crypto.signing import KeyGenerator, normalize
1313
from iota.crypto.types import PrivateKey
1414
from iota.exceptions import with_context
1515
from iota.transaction.base import Bundle, Transaction
16-
from iota.transaction.types import BundleHash, Fragment, TransactionHash, Nonce
16+
from iota.transaction.types import BundleHash, Fragment, Nonce, TransactionHash
1717
from iota.transaction.utils import get_current_timestamp
18-
from iota.types import Address, Hash, Tag, TryteString
18+
from iota.trits import add_trits
19+
from iota.types import Address, Tag, TryteString
1920

2021
__all__ = [
2122
'ProposedBundle',
@@ -83,6 +84,17 @@ def as_tryte_string(self):
8384

8485
return super(ProposedTransaction, self).as_tryte_string()
8586

87+
def increment_legacy_tag(self):
88+
"""
89+
Increments the transaction's legacy tag, used to fix insecure
90+
bundle hashes when finalizing a bundle.
91+
92+
References:
93+
- https://github.com/iotaledger/iota.lib.py/issues/84
94+
"""
95+
self._legacy_tag =\
96+
Tag.from_trits(add_trits(self.legacy_tag.as_trits(), [1]))
97+
8698

8799
Transfer = ProposedTransaction
88100
"""
@@ -322,20 +334,31 @@ def finalize(self):
322334
)
323335

324336
# Generate bundle hash.
325-
sponge = Kerl()
326-
last_index = len(self) - 1
337+
while True:
338+
sponge = Kerl()
339+
last_index = len(self) - 1
340+
341+
for (i, txn) in enumerate(self): # type: Tuple[int, ProposedTransaction]
342+
txn.current_index = i
343+
txn.last_index = last_index
327344

328-
for (i, txn) in enumerate(self): # type: Tuple[int, ProposedTransaction]
329-
txn.current_index = i
330-
txn.last_index = last_index
345+
sponge.absorb(txn.get_signature_validation_trytes().as_trits())
331346

332-
sponge.absorb(txn.get_signature_validation_trytes().as_trits())
347+
bundle_hash_trits = [0] * HASH_LENGTH # type: MutableSequence[int]
348+
sponge.squeeze(bundle_hash_trits)
333349

334-
bundle_hash_trits = [0] * HASH_LENGTH # type: MutableSequence[int]
335-
sponge.squeeze(bundle_hash_trits)
350+
bundle_hash = BundleHash.from_trits(bundle_hash_trits)
351+
352+
# Check that we generated a secure bundle hash.
353+
# https://github.com/iotaledger/iota.lib.py/issues/84
354+
if any(13 in fragment for fragment in normalize(bundle_hash)):
355+
# Increment the legacy tag and try again.
356+
tail_transaction = self.tail_transaction # type: ProposedTransaction
357+
tail_transaction.increment_legacy_tag()
358+
else:
359+
break
336360

337361
# Copy bundle hash to individual transactions.
338-
bundle_hash = BundleHash.from_trits(bundle_hash_trits)
339362
for txn in self:
340363
txn.bundle_hash = bundle_hash
341364

test/transaction/creation_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
TryteString
99
from iota.crypto.signing import KeyGenerator
1010
from iota.crypto.types import Seed
11+
from iota.transaction.types import BundleHash
1112

1213

1314
class ProposedBundleTestCase(TestCase):
@@ -489,6 +490,51 @@ def test_finalize_error_positive_balance(self):
489490
with self.assertRaises(ValueError):
490491
self.bundle.finalize()
491492

493+
def test_finalize_insecure_bundle(self):
494+
"""
495+
When finalizing, the bundle detects an insecure bundle hash.
496+
497+
References:
498+
- https://github.com/iotaledger/iota.lib.py/issues/84
499+
"""
500+
# noinspection SpellCheckingInspection
501+
bundle =\
502+
ProposedBundle([
503+
ProposedTransaction(
504+
address =\
505+
Address(
506+
'9XV9RJGFJJZWITDPKSQXRTHCKJAIZZY9BYLBEQUX'
507+
'UNCLITRQDR9CCD99AANMXYEKD9GLJGVB9HIAGRIBQ',
508+
),
509+
510+
tag = Tag('PPDIDNQDJZGUQKOWJ9JZRCKOVGP'),
511+
timestamp = 1509136296,
512+
value = 0,
513+
),
514+
])
515+
516+
bundle.finalize()
517+
518+
# The resulting bundle hash is insecure (contains a [1, 1, 1]), so
519+
# the legacy tag is manipulated until a secure hash is generated.
520+
# noinspection SpellCheckingInspection
521+
self.assertEqual(bundle[0].legacy_tag, Tag('ZTDIDNQDJZGUQKOWJ9JZRCKOVGP'))
522+
523+
# The proper tag is left alone, however.
524+
# noinspection SpellCheckingInspection
525+
self.assertEqual(bundle[0].tag, Tag('PPDIDNQDJZGUQKOWJ9JZRCKOVGP'))
526+
527+
# The bundle hash takes the modified legacy tag into account.
528+
# noinspection SpellCheckingInspection
529+
self.assertEqual(
530+
bundle.hash,
531+
532+
BundleHash(
533+
'NYSJSEGCWESDAFLIFCNJFWGZ9PCYDOT9VCSALKBD'
534+
'9UUNKBJAJCB9KVMTHZDPRDDXC9UFJQBJBQFUPJKFC',
535+
)
536+
)
537+
492538
def test_sign_inputs(self):
493539
"""
494540
Signing inputs in a finalized bundle, using a key generator.

0 commit comments

Comments
 (0)