Example #1
0
class ProposedBundleTestCase(TestCase):
  def setUp(self):
    super(ProposedBundleTestCase, self).setUp()

    # We will use a seed to generate addresses and private keys, to
    # ensure a realistic scenario (and because the alternative is to
    # inject mocks all over the place!).
    # noinspection SpellCheckingInspection
    self.seed =\
      Seed(
        b'TESTVALUE9DONTUSEINPRODUCTION99999RLC9CS'
        b'ZUILGDTLJMRCJSDVEEJO9A9LHAEHMNAMVXRMOXTBN'
      )

    # To speed things up a little bit, though, we can pre-generate a
    # few addresses to use as inputs.

    # noinspection SpellCheckingInspection
    self.input_0_bal_eq_42 =\
      Address(
        balance         = 42,
        key_index       = 0,
        security_level  = 1,

        trytes =
          b'JBLDCCSI9VKU9ZHNZCUTC9NLQIIJX9SIKUJNKNKE'
          b'9KKMHXFMIXHLKQQAVTTNPRCZENGLIPALHKLNKTXCU',
      )

    # noinspection SpellCheckingInspection
    self.input_1_bal_eq_40 =\
      Address(
        balance         = 40,
        key_index       = 1,
        security_level  = 1,

        trytes =
          b'KHWHSTISMVVSDCOMHVFIFCTINWZT9EHJUATYSMCX'
          b'DSMZXPL9KXREBBYHJGRBCYVGPJQEHEDPXLBDJNQNX',
      )

    # noinspection SpellCheckingInspection
    self.input_2_bal_eq_2 =\
      Address(
        balance         = 2,
        key_index       = 2,
        security_level  = 1,

        trytes =
          b'GOAAMRU9EALPO9GKBOWUVZVQEJMB9CSGIZJATHRB'
          b'TRRJPNTSQRZTASRBTQCRFAIDOGTWSHIDGOUUULQIG',
      )

    # noinspection SpellCheckingInspection
    self.input_3_bal_eq_100 =\
      Address(
        balance         = 100,
        key_index       = 3,
        security_level  = 1,

        trytes =
          b'9LPQCSJGYUJMLWKMLJ9KYUYJ9RMDBZZWPHXMGKRG'
          b'YLOAZNKJR9VDYSONVAJRIPVWCOZKFMEKUSWHPSDDZ',
      )

    # noinspection SpellCheckingInspection
    self.input_4_bal_eq_42_sl_2 =\
      Address(
        balance         = 42,
        key_index       = 4,
        security_level  = 2,

        trytes =
          b'NVGLHFZWLEQAWBDJXCWJBMVBVNXEG9DALNBTAYMK'
          b'EMMJ9BCDVVHJJLSTQW9JEJXUUX9JNFGALBNASRDUD',
      )

    # noinspection SpellCheckingInspection
    self.input_5_bal_eq_42_sl_3 =\
      Address(
        balance         = 42,
        key_index       = 5,
        security_level  = 3,

        trytes =
          b'XXYRPQ9BDZGKZZQLYNSBDD9HZLI9OFRK9TZCTU9P'
          b'FAJYXZIZGO9BWLOCNGVMTLFQFMGJWYRMLXSCW9UTQ',
      )

    self.bundle = ProposedBundle()

  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_string('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_add_transaction_long_message(self):
    """
    Adding a transaction to a bundle, with a message so long that it
    has to be split into multiple transactions.
    """
    # noinspection SpellCheckingInspection
    address = Address(
      b'TESTVALUE9DONTUSEINPRODUCTION99999N9GIUF'
      b'HCFIUGLBSCKELC9IYENFPHCEWHIDCHCGGEH9OFZBN'
    )

    tag = Tag.from_string('H2G2')

    self.bundle.add_transaction(ProposedTransaction(
      address = address,
      tag     = tag,

      message = TryteString.from_string(
        '''
"Good morning," said Deep Thought at last.
"Er... Good morning, O Deep Thought," said Loonquawl nervously.
  "Do you have... er, that is..."
"... an answer for you?" interrupted Deep Thought majestically. "Yes. I have."
The two men shivered with expectancy. Their waiting had not been in vain.
"There really is one?" breathed Phouchg.
"There really is one," confirmed Deep Thought.
"To Everything? To the great Question of Life, the Universe and Everything?"
"Yes."
Both of the men had been trained for this moment; their lives had been a
  preparation for it; they had been selected at birth as those who would
  witness the answer; but even so they found themselves gasping and squirming
  like excited children.
"And you're ready to give it to us?" urged Loonquawl.
"I am."
"Now?"
"Now," said Deep Thought.
They both licked their dry lips.
"Though I don't think," added Deep Thought, "that you're going to like it."
"Doesn't matter," said Phouchg. "We must know it! Now!"
"Now?" enquired Deep Thought.
"Yes! Now!"
"All right," said the computer and settled into silence again.
  The two men fidgeted. The tension was unbearable.
"You're really not going to like it," observed Deep Thought.
"Tell us!"
"All right," said Deep Thought. "The Answer to the Great Question..."
"Yes?"
"Of Life, the Universe and Everything..." said Deep Thought.
"Yes??"
"Is..."
"Yes?!"
"Forty-two," said Deep Thought, with infinite majesty and calm.
        '''
      ),

      # Now you know....
      # Eh, who am I kidding?  You probably knew before I did (:
      value = 42,
    ))

    # Because the message is too long to fit into a single fragment,
    # the transaction is split into two parts.
    self.assertEqual(len(self.bundle), 2)

    txn1 = self.bundle[0]
    self.assertEqual(txn1.address, address)
    self.assertEqual(txn1.tag, tag)
    self.assertEqual(txn1.value, 42)

    txn2 = self.bundle[1]
    self.assertEqual(txn2.address, address)
    self.assertEqual(txn2.tag, tag)
    # Supplementary transactions are assigned zero IOTA value.
    self.assertEqual(txn2.value, 0)

  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_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_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_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_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_add_inputs_error_already_finalized(self):
    """
    Attempting to add inputs to 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):
      # Even though no inputs are provided, it's still an error; you
      # shouldn't even be calling ``add_inputs`` once the bundle is
      # finalized!
      self.bundle.add_inputs([])

  def test_send_unspent_inputs_to_error_already_finalized(self):
    """
    Invoking ``send_unspent_inputs_to`` on 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.send_unspent_inputs_to(Address(b''))

  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_finalize_error_no_transactions(self):
    """
    Attempting to finalize a bundle with no transactions.
    """
    with self.assertRaises(ValueError):
      self.bundle.finalize()

  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_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()

  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_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_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_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_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_sign_input_at_multiple_fragments(self):
    """
    Signing an input at the specified index, multiple fragments 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_5_bal_eq_42_sl_3])
    self.bundle.finalize()

    private_key =\
      KeyGenerator(self.seed).get_key_for(self.input_5_bal_eq_42_sl_3)

    self.bundle.sign_input_at(1, private_key)

    # 1 spend + 3 inputs (security level = 3).
    self.assertEqual(len(self.bundle), 4)

    # 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_error_not_finalized(self):
    """
    Cannot sign inputs because the bundle isn't 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()

    private_key =\
      KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42)

    with self.assertRaises(RuntimeError):
      self.bundle.sign_input_at(1, private_key)

  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_input_at_error_index_not_input(self):
    """
    The specified index references a transaction that is not an input.
    """
    # 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(ValueError):
      # You can't sign the spend transaction, silly!
      self.bundle.sign_input_at(0, private_key)

  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)
Example #2
0
    def _execute(self, request):
        # Required parameters.
        seed = request['seed']  # type: Seed
        bundle = ProposedBundle(request['transfers'])

        # Optional parameters.
        change_address = request.get(
            'changeAddress')  # type: Optional[Address]
        proposed_inputs = request.get(
            'inputs')  # type: Optional[List[Address]]

        want_to_spend = bundle.balance
        if want_to_spend > 0:
            # We are spending inputs, so we need to gather and sign them.
            if proposed_inputs is None:
                # No inputs provided.  Scan addresses for unspent inputs.
                gi_response = GetInputsCommand(self.adapter)(
                    seed=seed,
                    threshold=want_to_spend,
                )

                confirmed_inputs = gi_response['inputs']
            else:
                # Inputs provided.  Check to make sure we have sufficient
                # balance.
                available_to_spend = 0
                confirmed_inputs = []  # type: List[Address]

                gb_response = GetBalancesCommand(self.adapter)(
                    addresses=[i.address for i in proposed_inputs], )

                for i, balance in enumerate(gb_response.get('balances') or []):
                    input_ = proposed_inputs[i]

                    if balance > 0:
                        available_to_spend += balance

                        # Update the address balance from the API response, just in
                        # case somebody tried to cheat.
                        input_.balance = balance
                        confirmed_inputs.append(input_)

                if available_to_spend < want_to_spend:
                    raise with_context(
                        exc=BadApiResponse(
                            'Insufficient balance; found {found}, need {need} '
                            '(``exc.context`` has more info).'.format(
                                found=available_to_spend,
                                need=want_to_spend,
                            ), ),
                        context={
                            'available_to_spend': available_to_spend,
                            'confirmed_inputs': confirmed_inputs,
                            'request': request,
                            'want_to_spend': want_to_spend,
                        },
                    )

            bundle.add_inputs(confirmed_inputs)

            if bundle.balance < 0:
                if not change_address:
                    change_address =\
                      GetNewAddressesCommand(self.adapter)(seed=seed)['addresses'][0]

                bundle.send_unspent_inputs_to(change_address)

            bundle.finalize()

            if confirmed_inputs:
                bundle.sign_inputs(KeyGenerator(seed))
        else:
            bundle.finalize()

        return {
            'trytes': bundle.as_tryte_strings(),
        }
    value=0,
    tag=tag,
    message=iota.TryteString('HELLO')
)

bundle.add_transaction(pt)

addy = gna_result["addresses"][0]
addy.balance = 0
addy.key_index = 0
bundle.add_inputs([
    addy
])

bundle.send_unspent_inputs_to(
    gna_result["addresses"][0]
)

bundle.finalize()
bundle.sign_inputs(KeyGenerator(b'BXOM9LUNLPSEXBRJV9UUNLHSUHABEOGHQOGNBNBUEYSGOFZOEPYKEYRSFTXBOEJLUODUQXXGQ9NWQBSGH'))

tips = json.loads(call_iota_api(getTips))
print("llsdldld")
for x in tips['hashes']:
    getTransactionsToApprove = {"command": "getTransactionsToApprove", "depth": 15, "reference": x}
    result = json.loads(call_iota_api(getTransactionsToApprove))
    if "exception" in result:
        print(result)
        continue
    elif "error" in result:
        print(result)
Example #4
0
class ProposedBundleTestCase(TestCase):
    def setUp(self):
        super(ProposedBundleTestCase, self).setUp()

        # We will use a seed to generate addresses and private keys, to
        # ensure a realistic scenario (and because the alternative is to
        # inject mocks all over the place!).
        # noinspection SpellCheckingInspection
        self.seed =\
          Seed(
            b'TESTVALUE9DONTUSEINPRODUCTION99999RLC9CS'
            b'ZUILGDTLJMRCJSDVEEJO9A9LHAEHMNAMVXRMOXTBN'
          )

        # To speed things up a little bit, though, we can pre-generate a
        # few addresses to use as inputs.

        # noinspection SpellCheckingInspection
        self.input_0_bal_eq_42 =\
          Address(
            balance         = 42,
            key_index       = 0,
            security_level  = 1,

            trytes =
              b'JBLDCCSI9VKU9ZHNZCUTC9NLQIIJX9SIKUJNKNKE'
              b'9KKMHXFMIXHLKQQAVTTNPRCZENGLIPALHKLNKTXCU',
          )

        # noinspection SpellCheckingInspection
        self.input_1_bal_eq_40 =\
          Address(
            balance         = 40,
            key_index       = 1,
            security_level  = 1,

            trytes =
              b'KHWHSTISMVVSDCOMHVFIFCTINWZT9EHJUATYSMCX'
              b'DSMZXPL9KXREBBYHJGRBCYVGPJQEHEDPXLBDJNQNX',
          )

        # noinspection SpellCheckingInspection
        self.input_2_bal_eq_2 =\
          Address(
            balance         = 2,
            key_index       = 2,
            security_level  = 1,

            trytes =
              b'GOAAMRU9EALPO9GKBOWUVZVQEJMB9CSGIZJATHRB'
              b'TRRJPNTSQRZTASRBTQCRFAIDOGTWSHIDGOUUULQIG',
          )

        # noinspection SpellCheckingInspection
        self.input_3_bal_eq_100 =\
          Address(
            balance         = 100,
            key_index       = 3,
            security_level  = 1,

            trytes =
              b'9LPQCSJGYUJMLWKMLJ9KYUYJ9RMDBZZWPHXMGKRG'
              b'YLOAZNKJR9VDYSONVAJRIPVWCOZKFMEKUSWHPSDDZ',
          )

        # noinspection SpellCheckingInspection
        self.input_4_bal_eq_42_sl_2 =\
          Address(
            balance         = 42,
            key_index       = 4,
            security_level  = 2,

            trytes =
              b'NVGLHFZWLEQAWBDJXCWJBMVBVNXEG9DALNBTAYMK'
              b'EMMJ9BCDVVHJJLSTQW9JEJXUUX9JNFGALBNASRDUD',
          )

        # noinspection SpellCheckingInspection
        self.input_5_bal_eq_42_sl_3 =\
          Address(
            balance         = 42,
            key_index       = 5,
            security_level  = 3,

            trytes =
              b'XXYRPQ9BDZGKZZQLYNSBDD9HZLI9OFRK9TZCTU9P'
              b'FAJYXZIZGO9BWLOCNGVMTLFQFMGJWYRMLXSCW9UTQ',
          )

        self.bundle = ProposedBundle()

    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_string('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_add_transaction_long_message(self):
        """
    Adding a transaction to a bundle, with a message so long that it
    has to be split into multiple transactions.
    """
        # noinspection SpellCheckingInspection
        address = Address(b'TESTVALUE9DONTUSEINPRODUCTION99999N9GIUF'
                          b'HCFIUGLBSCKELC9IYENFPHCEWHIDCHCGGEH9OFZBN')

        tag = Tag.from_string('H2G2')

        self.bundle.add_transaction(
            ProposedTransaction(
                address=address,
                tag=tag,
                message=TryteString.from_string('''
"Good morning," said Deep Thought at last.
"Er... Good morning, O Deep Thought," said Loonquawl nervously.
  "Do you have... er, that is..."
"... an answer for you?" interrupted Deep Thought majestically. "Yes. I have."
The two men shivered with expectancy. Their waiting had not been in vain.
"There really is one?" breathed Phouchg.
"There really is one," confirmed Deep Thought.
"To Everything? To the great Question of Life, the Universe and Everything?"
"Yes."
Both of the men had been trained for this moment; their lives had been a
  preparation for it; they had been selected at birth as those who would
  witness the answer; but even so they found themselves gasping and squirming
  like excited children.
"And you're ready to give it to us?" urged Loonquawl.
"I am."
"Now?"
"Now," said Deep Thought.
They both licked their dry lips.
"Though I don't think," added Deep Thought, "that you're going to like it."
"Doesn't matter," said Phouchg. "We must know it! Now!"
"Now?" enquired Deep Thought.
"Yes! Now!"
"All right," said the computer and settled into silence again.
  The two men fidgeted. The tension was unbearable.
"You're really not going to like it," observed Deep Thought.
"Tell us!"
"All right," said Deep Thought. "The Answer to the Great Question..."
"Yes?"
"Of Life, the Universe and Everything..." said Deep Thought.
"Yes??"
"Is..."
"Yes?!"
"Forty-two," said Deep Thought, with infinite majesty and calm.
        '''),

                # Now you know....
                # Eh, who am I kidding?  You probably knew before I did (:
                value=42,
            ))

        # Because the message is too long to fit into a single fragment,
        # the transaction is split into two parts.
        self.assertEqual(len(self.bundle), 2)

        txn1 = self.bundle[0]
        self.assertEqual(txn1.address, address)
        self.assertEqual(txn1.tag, tag)
        self.assertEqual(txn1.value, 42)

        txn2 = self.bundle[1]
        self.assertEqual(txn2.address, address)
        self.assertEqual(txn2.tag, tag)
        # Supplementary transactions are assigned zero IOTA value.
        self.assertEqual(txn2.value, 0)

    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_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_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_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_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_add_inputs_error_already_finalized(self):
        """
    Attempting to add inputs to 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):
            # Even though no inputs are provided, it's still an error; you
            # shouldn't even be calling ``add_inputs`` once the bundle is
            # finalized!
            self.bundle.add_inputs([])

    def test_send_unspent_inputs_to_error_already_finalized(self):
        """
    Invoking ``send_unspent_inputs_to`` on 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.send_unspent_inputs_to(Address(b''))

    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_finalize_error_no_transactions(self):
        """
    Attempting to finalize a bundle with no transactions.
    """
        with self.assertRaises(ValueError):
            self.bundle.finalize()

    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_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()

    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_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_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_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_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_sign_input_at_multiple_fragments(self):
        """
    Signing an input at the specified index, multiple fragments 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_5_bal_eq_42_sl_3])
        self.bundle.finalize()

        private_key =\
          KeyGenerator(self.seed).get_key_for(self.input_5_bal_eq_42_sl_3)

        self.bundle.sign_input_at(1, private_key)

        # 1 spend + 3 inputs (security level = 3).
        self.assertEqual(len(self.bundle), 4)

        # 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_error_not_finalized(self):
        """
    Cannot sign inputs because the bundle isn't 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()

        private_key =\
          KeyGenerator(self.seed).get_key_for(self.input_0_bal_eq_42)

        with self.assertRaises(RuntimeError):
            self.bundle.sign_input_at(1, private_key)

    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_input_at_error_index_not_input(self):
        """
    The specified index references a transaction that is not an input.
    """
        # 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(ValueError):
            # You can't sign the spend transaction, silly!
            self.bundle.sign_input_at(0, private_key)

    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)
Example #5
0
  def _execute(self, request):
    # Required parameters.
    seed      = request['seed'] # type: Seed
    bundle    = ProposedBundle(request['transfers'])

    # Optional parameters.
    change_address  = request.get('changeAddress') # type: Optional[Address]
    proposed_inputs = request.get('inputs') # type: Optional[List[Address]]

    want_to_spend = bundle.balance
    if want_to_spend > 0:
      # We are spending inputs, so we need to gather and sign them.
      if proposed_inputs is None:
        # No inputs provided.  Scan addresses for unspent inputs.
        gi_response = GetInputsCommand(self.adapter)(
          seed      = seed,
          threshold = want_to_spend,
        )

        confirmed_inputs = gi_response['inputs']
      else:
        # Inputs provided.  Check to make sure we have sufficient
        # balance.
        available_to_spend  = 0
        confirmed_inputs    = [] # type: List[Address]

        gb_response = GetBalancesCommand(self.adapter)(
          addresses = [i.address for i in proposed_inputs],
        )

        for i, balance in enumerate(gb_response.get('balances') or []):
          input_ = proposed_inputs[i]

          if balance > 0:
            available_to_spend += balance

            # Update the address balance from the API response, just in
            # case somebody tried to cheat.
            input_.balance = balance
            confirmed_inputs.append(input_)

        if available_to_spend < want_to_spend:
          raise with_context(
            exc = BadApiResponse(
              'Insufficient balance; found {found}, need {need} '
              '(``exc.context`` has more info).'.format(
                found = available_to_spend,
                need  = want_to_spend,
              ),
            ),

            context = {
              'available_to_spend': available_to_spend,
              'confirmed_inputs':   confirmed_inputs,
              'request':            request,
              'want_to_spend':      want_to_spend,
            },
          )

      bundle.add_inputs(confirmed_inputs)

      if bundle.balance < 0:
        if not change_address:
          change_address =\
            GetNewAddressesCommand(self.adapter)(seed=seed)['addresses'][0]

        bundle.send_unspent_inputs_to(change_address)

      bundle.finalize()

      if confirmed_inputs:
        bundle.sign_inputs(KeyGenerator(seed))
    else:
      bundle.finalize()

    return {
      'trytes': bundle.as_tryte_strings(),
    }