def test_error_zero_iotas_transferred(self): """ The bundle doesn't spend any IOTAs. This is considered an error case because :py:meth:`MultisigIota.prepare_multisig_transfer` is specialized for generating bundles that require multisig inputs. Any bundle that doesn't require multisig functionality should be generated using :py:meth:`iota_async.api.Iota.prepare_transfer` instead. """ with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 0, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), )
def test_sign_input_at_error_already_signed(self): """ Attempting to sign an input that is already signed. """ # Add a transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42]) self.bundle.finalize() # The existing signature fragment doesn't have to be valid; it just # has to be not empty. self.bundle[1].signature_message_fragment = Fragment(b'A') private_key =\ KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42) with self.assertRaises(ValueError): self.bundle.sign_input_at(1, private_key)
def test_fail_changeAddress_wrong_type(self): """ ``changeAddress`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'changeAddress': 42, 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }, { 'changeAddress': [f.Type.CODE_WRONG_TYPE], }, )
def test_pass_happy_path(self): """ Request is valid. """ request = { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_pass_changeAddress_multisig_address(self): """ ``changeAddress`` is allowed to be a MultisigAddress. """ change_addy =\ MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_1 ) filter_ = self._filter({ 'changeAddress': change_addy, 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }) self.assertFilterPasses(filter_) self.assertIs(filter_.cleaned_data['changeAddress'], change_addy)
def test_fail_multisigInput_wrong_type(self): """ ``multisigInput`` is not a MultisigAddress. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), # This value must be a MultisigAddress, so that we know the # total security level of the digests used to create it. 'multisigInput': Address(self.trytes_2), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], }, { 'multisigInput': [f.Type.CODE_WRONG_TYPE], }, )
def test_fail_transfers_wrong_type(self): """ ``transfers`` is not an array. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), # ``transfers`` must be an array, even if there's only one # transaction. 'transfers': ProposedTransaction( address = Address(self.trytes_3), value = 42, ), }, { 'transfers': [f.Array.CODE_WRONG_TYPE], }, )
def test_fail_transfers_contents_invalid(self): """ ``transfers`` is a non-empty array, but it contains invalid values. """ self.assertFilterErrors( { 'transfers': [ None, # This value is valid; just adding it to make sure the filter # doesn't cheat! ProposedTransaction(address=Address(self.trytes2), value=42), { 'address': Address(self.trytes2), 'value': 42 }, ], 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), }, { 'transfers.0': [f.Required.CODE_EMPTY], 'transfers.2': [f.Type.CODE_WRONG_TYPE], }, )
def test_error_insufficient_inputs(self): """ The multisig input does not contain sufficient IOTAs to cover the spends. """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [42], 'duration': 86, }, ) with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 101, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), )
def test_add_inputs_security_level(self): """ Each input's security level determines the number of transactions we will need in order to store the entire signature. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address( b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH', ), value=84, ), ) self.bundle.add_inputs([ self.input_4_bal_eq_42_sl_2, self.input_5_bal_eq_42_sl_3, ]) self.bundle.finalize() # Each input's security level determines how many transactions will # be needed to hold all of its signature fragments: # 1 spend + 2 fragments for input 0 + 3 fragments for input 1 self.assertEqual(len(self.bundle), 6)
def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [ ProposedTransaction( address = Address(self.trytes_3), value = 42, ), ], # Oh come on! 'foo': 'bar', }, { 'foo': [f.FilterMapper.CODE_EXTRA_KEY], }, )
def test_add_inputs_with_change(self): """ Adding inputs to a bundle results in unspent inputs. """ tag = Tag(b'CHANGE9TXN') # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999VELDTF' b'QHDFTHIHFE9II9WFFDFHEATEI99GEDC9BAUH9EBGZ'), value=29, )) # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999OGVEEF' b'BCYAM9ZEAADBGBHH9BPBOHFEGCFAM9DESCCHODZ9Y'), tag=tag, value=13, )) self.bundle.add_inputs([self.input_3_bal_eq_100]) # noinspection SpellCheckingInspection change_address =\ Address( b'TESTVALUE9DONTUSEINPRODUCTION99999KAFGVC' b'IBLHS9JBZCEFDELEGFDCZGIEGCPFEIQEYGA9UFPAE' ) self.bundle.send_unspent_inputs_to(change_address) self.bundle.finalize() # 2 spends + 1 input (with security level 1) + 1 change self.assertEqual(len(self.bundle), 4) change_txn = self.bundle[-1] self.assertEqual(change_txn.address, change_address) self.assertEqual(change_txn.value, 58) self.assertEqual(change_txn.tag, tag)
def test_add_transaction_error_already_finalized(self): """ Attempting to add a transaction to a bundle that is already finalized. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION999999DCBIE' b'U9AIE9H9BCKGMCVCUGYDKDLCAEOHOHZGW9KGS9VGH'), value=0, )) self.bundle.finalize() with self.assertRaises(RuntimeError): self.bundle.add_transaction( ProposedTransaction( address=Address(b''), value=0, ))
def test_add_inputs_no_change(self): """ Adding inputs to cover the exact amount of the bundle spend. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999VELDTF' b'QHDFTHIHFE9II9WFFDFHEATEI99GEDC9BAUH9EBGZ'), value=29, )) # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999OGVEEF' b'BCYAM9ZEAADBGBHH9BPBOHFEGCFAM9DESCCHODZ9Y'), value=13, )) self.bundle.add_inputs([ self.input_1_bal_eq_40, self.input_2_bal_eq_2, ]) # Just to be tricky, add an unnecessary change address, just to # make sure the bundle ignores it. # noinspection SpellCheckingInspection self.bundle.send_unspent_inputs_to( Address(b'TESTVALUE9DONTUSEINPRODUCTION99999FDCDFD' b'VAF9NFLCSCSFFCLCW9KFL9TCAAO9IIHATCREAHGEA'), ) self.bundle.finalize() # All of the addresses that we generate for this test case have # security level set to 1, so we only need 1 transaction per # input (4 total, including the spends). # # Also note: because the transaction is already balanced, no change # transaction is necessary. self.assertEqual(len(self.bundle), 4)
def test_add_transaction_error_negative_value(self): """ Attempting to add a transaction with a negative value to a bundle. Use :py:meth:`ProposedBundle.add_inputs` to add inputs to a bundle. """ with self.assertRaises(ValueError): self.bundle.add_transaction( ProposedTransaction( address=Address(b''), value=-1, ))
def test_create_tag_from_string(self): """ Check if string value of tag is converted into a Tag object """ transaction = ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), tag="AAAZZZZ999", value=42, ) self.assertEqual(type(transaction.tag), type(Tag(b'')))
def setUp(self): super(SendTransferRequestFilterTestCase, self).setUp() # Define some tryte sequences that we can reuse between tests. self.trytes1 = (b'TESTVALUEONE9DONTUSEINPRODUCTION99999JBW' b'GEC99GBXFFBCHAEJHLC9DX9EEPAI9ICVCKBX9FFII') self.trytes2 = (b'TESTVALUETWO9DONTUSEINPRODUCTION99999THZ' b'BODYHZM99IR9KOXLZXVUOJM9LQKCQJBWMTY999999') self.trytes3 = (b'TESTVALUETHREE9DONTUSEINPRODUCTIONG99999' b'GTQ9CSNUFPYW9MBQ9LFQJSORCF9LGTY9BWQFY9999') self.trytes4 = (b'TESTVALUEFOUR9DONTUSEINPRODUCTION99999ZQ' b'HOGCBZCOTZVZRFBEHQKHENBIZWDTUQXTOVWEXRIK9') self.transfer1 =\ ProposedTransaction( address = Address( b'TESTVALUEFIVE9DONTUSEINPRODUCTION99999MG' b'AAAHJDZ9BBG9U9R9XEOHCBVCLCWCCCCBQCQGG9WHK' ), value = 42, ) self.transfer2 =\ ProposedTransaction( address = Address( b'TESTVALUESIX9DONTUSEINPRODUCTION99999GGT' b'FODSHHELBDERDCDRBCINDCGQEI9NAWDJBC9TGPFME' ), value = 86, )
def test_add_inputs_error_already_finalized(self): """ Attempting to add inputs to a bundle that is already finalized. """ self.bundle.add_transaction( ProposedTransaction( address = Address(self.trytes_1), value = 0, ), ) self.bundle.finalize() with self.assertRaises(RuntimeError): self.bundle.add_inputs([])
def test_add_transaction_short_message(self): """ Adding a transaction to a bundle, with a message short enough to fit inside a single transaction. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999AETEXB' b'D9YBTH9EMFKF9CAHJIAIKDBEPAMH99DEN9DAJETGN'), message=TryteString.from_unicode('Hello, IOTA!'), value=42, )) # We can fit the message inside a single fragment, so only one # transaction is necessary. self.assertEqual(len(self.bundle), 1)
def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ txn =\ ProposedTransaction( address = Address(self.trytes_3), value = 42, ) filter_ =\ self._filter({ # ``changeAddress`` can be any value that resolves to an # :py:class:`Address`. 'changeAddress': self.trytes_1, # ``multisigInput`` must be a :py:class:`MultisigInput` object. 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), # ``transfers`` must contain an array of # :py:class:`ProposedTransaction` objects. 'transfers': [txn], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'changeAddress': Address(self.trytes_1), 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [txn], }, )
def test_finalize_error_already_finalized(self): """ Attempting to finalize a bundle that is already finalized. """ # Add 1 transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH'), value=0, )) self.bundle.finalize() with self.assertRaises(RuntimeError): self.bundle.finalize()
def test_pass_optional_parameters_excluded(self): """ Request omits optional parameters. """ txn =\ ProposedTransaction( address = Address(self.trytes_3), value = 42, ) filter_ =\ self._filter({ # ``changeAddress`` is optional. # Technically, it's required if there are unspent inputs, but # the filter has no way to know whether this is the case. # 'changeAddress': self.trytes_1, # These parameters are required. 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [txn], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'changeAddress': None, 'multisigInput': MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), 'transfers': [txn], }, )
def test_sign_inputs_security_level(self): """ You may include inputs with different security levels in the same bundle. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address( b'TESTVALUE9DONTUSEINPRODUCTION99999XE9IVG' b'EFNDOCQCMERGUATCIEGGOHPHGFIAQEZGNHQ9W99CH', ), value=84, ), ) self.bundle.add_inputs([ self.input_4_bal_eq_42_sl_2, self.input_5_bal_eq_42_sl_3, ]) self.bundle.finalize() self.bundle.sign_inputs(KeyGenerator(self.seed)) # Quick sanity check. self.assertEqual(len(self.bundle), 6) # The spending transaction does not have a signature. self.assertEqual( self.bundle[0].signature_message_fragment, Fragment(b''), ) # The signature fragments are really long, and we already have unit # tests for the signature fragment generator, so to keep this test # focused, we are only interested in whether a signature fragment # gets applied. # # References: # - :py:class:`test.crypto.signing_test.SignatureFragmentGeneratorTestCase` for i in range(1, len(self.bundle)): if self.bundle[i].signature_message_fragment == Fragment(b''): self.fail( "Transaction {i}'s signature fragment is unexpectedly empty!" .format(i=i, ), )
def test_sign_inputs(self): """ Signing inputs in a finalized bundle, using a key generator. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_1_bal_eq_40, self.input_2_bal_eq_2]) self.bundle.finalize() self.bundle.sign_inputs(KeyGenerator(self.seed)) # Quick sanity check: # 1 spend + 2 inputs (security level 1) = 3 transactions. # Applying signatures should not introduce any new transactions # into the bundle. # # Note: we will see what happens when we use inputs with different # security levels in the next test. self.assertEqual(len(self.bundle), 3) # The spending transaction does not have a signature. self.assertEqual( self.bundle[0].signature_message_fragment, Fragment(b''), ) # The signature fragments are really long, and we already have unit # tests for the signature fragment generator, so to keep this test # focused, we are only interested in whether a signature fragment # gets applied. # # References: # - :py:class:`test.crypto.signing_test.SignatureFragmentGeneratorTestCase` for i in range(1, len(self.bundle)): if self.bundle[i].signature_message_fragment == Fragment(b''): self.fail( "Transaction {i}'s signature fragment is unexpectedly empty!" .format(i=i, ), )
def test_sign_input_at_single_fragment(self): """ Signing an input at the specified index, only 1 fragment needed. """ # Add a transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42]) self.bundle.finalize() private_key =\ KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42) self.bundle.sign_input_at(1, private_key) # Only 2 transactions are needed for this bundle: # 1 spend + 1 input (security level = 1). self.assertEqual(len(self.bundle), 2) # The spending transaction does not have a signature. self.assertEqual( self.bundle[0].signature_message_fragment, Fragment(b''), ) # The signature fragments are really long, and we already have unit # tests for the signature fragment generator, so to keep this test # focused, we are only interested in whether a signature fragment # gets applied. # # References: # - :py:class:`test.crypto.signing_test.SignatureFragmentGeneratorTestCase` for i in range(1, len(self.bundle)): if self.bundle[i].signature_message_fragment == Fragment(b''): self.fail( "Transaction {i}'s signature fragment is unexpectedly empty!" .format(i=i, ), )
def test_finalize_insecure_bundle(self): """ When finalizing, the bundle detects an insecure bundle hash. References: - https://github.com/iotaledger/iota.lib.py/issues/84 """ # noinspection SpellCheckingInspection bundle =\ ProposedBundle([ ProposedTransaction( address =\ Address( '9XV9RJGFJJZWITDPKSQXRTHCKJAIZZY9BYLBEQUX' 'UNCLITRQDR9CCD99AANMXYEKD9GLJGVB9HIAGRIBQ', ), tag = Tag('PPDIDNQDJZGUQKOWJ9JZRCKOVGP'), timestamp = 1509136296, value = 0, ), ]) bundle.finalize() # The resulting bundle hash is insecure (contains a [1, 1, 1]), so # the legacy tag is manipulated until a secure hash is generated. # noinspection SpellCheckingInspection self.assertEqual(bundle[0].legacy_tag, Tag('ZTDIDNQDJZGUQKOWJ9JZRCKOVGP')) # The proper tag is left alone, however. # noinspection SpellCheckingInspection self.assertEqual(bundle[0].tag, Tag('PPDIDNQDJZGUQKOWJ9JZRCKOVGP')) # The bundle hash takes the modified legacy tag into account. # noinspection SpellCheckingInspection self.assertEqual( bundle.hash, BundleHash( 'NYSJSEGCWESDAFLIFCNJFWGZ9PCYDOT9VCSALKBD' '9UUNKBJAJCB9KVMTHZDPRDDXC9UFJQBJBQFUPJKFC', ))
def test_finalize_error_negative_balance(self): """ Attempting to finalize a bundle with unspent inputs. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999IGEFUG' b'LIHIJGJGZ9CGRENCRHF9XFEAWD9ILFWEJFKDLITCC'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42, self.input_2_bal_eq_2]) # Bundle spends 42 IOTAs, but inputs total 44 IOTAs. self.assertEqual(self.bundle.balance, -2) # In order to finalize this bundle, we need to specify a change # address. with self.assertRaises(ValueError): self.bundle.finalize()
def test_sign_input_at_error_index_invalid(self): """ The specified index doesn't exist in the bundle. """ # Add a transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42]) self.bundle.finalize() private_key =\ KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42) with self.assertRaises(IndexError): self.bundle.sign_input_at(2, private_key)
def test_sign_inputs_error_not_finalized(self): """ Attempting to sign inputs in a bundle that hasn't been finalized yet. """ # Add a transaction so that we can finalize the bundle. # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999QARFLF' b'TDVATBVFTFCGEHLFJBMHPBOBOHFBSGAGWCM9PG9GX'), value=42, )) self.bundle.add_inputs([self.input_0_bal_eq_42]) # Oops; did we forget something? # self.bundle.finalize() with self.assertRaises(RuntimeError): self.bundle.sign_inputs(KeyGenerator(b''))
def test_finalize_error_positive_balance(self): """ Attempting to finalize a bundle with insufficient inputs. """ # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address=Address(b'TESTVALUE9DONTUSEINPRODUCTION99999IGEFUG' b'LIHIJGJGZ9CGRENCRHF9XFEAWD9ILFWEJFKDLITCC'), value=42, )) self.bundle.add_inputs([self.input_1_bal_eq_40]) # Bundle spends 42 IOTAs, but inputs total only 40 IOTAs. self.assertEqual(self.bundle.balance, 2) # In order to finalize this bundle, we need to provide additional # inputs. with self.assertRaises(ValueError): self.bundle.finalize()