Beispiel #1
0
  def _traverse_bundle(self, txn_hash, target_bundle_hash=None):
    # type: (TransactionHash, Optional[BundleHash]) -> List[Transaction]
    """
    Recursively traverse the Tangle, collecting transactions until we
    hit a new bundle.

    This method is (usually) faster than ``findTransactions``, and it
    ensures we don't collect transactions from replayed bundles.
    """
    trytes = GetTrytesCommand(self.adapter)(hashes=[txn_hash])['trytes'] # type: List[TryteString]

    if not trytes:
      raise with_context(
        exc = BadApiResponse(
          'Bundle transactions not visible (``exc.context`` has more info).',
        ),

        context = {
          'transaction_hash':   txn_hash,
          'target_bundle_hash': target_bundle_hash,
        },
      )

    transaction = Transaction.from_tryte_string(trytes[0])

    if (not target_bundle_hash) and transaction.current_index:
      raise with_context(
        exc = BadApiResponse(
          '``_traverse_bundle`` started with a non-tail transaction '
          '(``exc.context`` has more info).',
        ),

        context = {
          'transaction_object': transaction,
          'target_bundle_hash': target_bundle_hash,
        },
      )

    if target_bundle_hash:
      if target_bundle_hash != transaction.bundle_hash:
        # We've hit a different bundle; we can stop now.
        return []
    else:
      target_bundle_hash = transaction.bundle_hash

    if transaction.current_index == transaction.last_index == 0:
      # Bundle only has one transaction.
      return [transaction]

    # Recursively follow the trunk transaction, to fetch the next
    # transaction in the bundle.
    return [transaction] + self._traverse_bundle(
      txn_hash            = transaction.trunk_transaction_hash,
      target_bundle_hash  = target_bundle_hash
    )
Beispiel #2
0
  def sign_inputs(self, key_generator):
    # type: (KeyGenerator) -> None
    """
    Sign inputs in a finalized bundle.
    """
    if not self.hash:
      raise RuntimeError('Cannot sign inputs until bundle is finalized.')

    # Use a counter for the loop so that we can skip ahead as we go.
    i = 0
    while i < len(self):
      txn = self[i]

      if txn.value < 0:
        # In order to sign the input, we need to know the index of
        # the private key used to generate it.
        if txn.address.key_index is None:
          raise with_context(
            exc = ValueError(
              'Unable to sign input {input}; ``key_index`` is None '
              '(``exc.context`` has more info).'.format(
                input = txn.address,
              ),
            ),

            context = {
              'transaction': txn,
            },
          )

        if txn.address.security_level is None:
          raise with_context(
            exc = ValueError(
              'Unable to sign input {input}; ``security_level`` is None '
              '(``exc.context`` has more info).'.format(
                input = txn.address,
              ),
            ),

            context = {
              'transaction': txn,
            },
          )

        self.sign_input_at(i, key_generator.get_key_for(txn.address))

        i += txn.address.security_level
      else:
        # No signature needed (nor even possible, in some cases); skip
        # this transaction.
        i += 1
Beispiel #3
0
  def __init__(self, uri):
    # type: (Union[Text, SplitResult]) -> None
    super(HttpAdapter, self).__init__()

    if isinstance(uri, text_type):
      uri = compat.urllib_parse.urlsplit(uri) # type: SplitResult

    if uri.scheme not in self.supported_protocols:
      raise with_context(
        exc = InvalidUri('Unsupported protocol {protocol!r}.'.format(
          protocol = uri.scheme,
        )),

        context = {
          'uri': uri,
        },
      )

    if not uri.hostname:
      raise with_context(
        exc = InvalidUri(
          'Empty hostname in URI {uri!r}.'.format(
            uri = uri.geturl(),
          ),
        ),

        context = {
          'uri': uri,
        },
      )

    try:
      # noinspection PyStatementEffect
      uri.port
    except ValueError:
      raise with_context(
        exc = InvalidUri(
          'Non-numeric port in URI {uri!r}.'.format(
            uri = uri.geturl(),
          ),
        ),

        context = {
          'uri': uri,
        },
      )

    self.uri = uri
Beispiel #4
0
  def encode(self, input, errors='strict'):
    """
    Encodes a byte string into trytes.
    """
    if isinstance(input, memoryview):
      input = input.tobytes()

    if not isinstance(input, (binary_type, bytearray)):
      raise with_context(
        exc = TypeError("Can't encode {type}; byte string expected.".format(
          type = type(input).__name__,
        )),

        context = {
          'input': input,
        },
      )

    # :bc: In Python 2, iterating over a byte string yields characters
    # instead of integers.
    if not isinstance(input, bytearray):
      input = bytearray(input)

    trytes = bytearray()

    for c in input:
      second, first = divmod(c, len(self.alphabet))

      trytes.append(self.alphabet[first])
      trytes.append(self.alphabet[second])

    return binary_type(trytes), len(input)
Beispiel #5
0
  def __call__(self, **kwargs):
    # type: (**Any) -> dict
    """
    Sends the command to the node.
    """
    if self.called:
      raise with_context(
        exc = RuntimeError('Command has already been called.'),

        context = {
          'last_request':   self.request,
          'last_response':  self.response,
        },
      )

    self.request = kwargs

    replacement = self._prepare_request(self.request)
    if replacement is not None:
      self.request = replacement

    self.response = self._execute(self.request)

    replacement = self._prepare_response(self.response)
    if replacement is not None:
      self.response = replacement

    self.called = True

    return self.response
Beispiel #6
0
  def _apply_filter(value, filter_, failure_message):
    # type: (dict, Optional[f.BaseFilter], Text) -> dict
    """
    Applies a filter to a value.  If the value does not pass the
    filter, an exception will be raised with lots of contextual info
    attached to it.
    """
    if filter_:
      runner = f.FilterRunner(filter_, value)

      if runner.is_valid():
        return runner.cleaned_data
      else:
        raise with_context(
          exc = ValueError(
            '{message} ({error_codes}) '
            '(`exc.context["filter_errors"]` '
            'contains more information).'.format(
              message     = failure_message,
              error_codes = runner.error_codes,
            ),
          ),

          context = {
            'filter_errors': runner.get_errors(with_context=True),
          },
        )

    return value
Beispiel #7
0
  def add_inputs(self, inputs):
    # type: (Iterable[Address]) -> None
    """
    Adds inputs to spend in the bundle.

    Note that each input may require multiple transactions, in order to
    hold the entire signature.

    :param inputs:
      Addresses to use as the inputs for this bundle.

      IMPORTANT: Must have ``balance`` and ``key_index`` attributes!
      Use :py:meth:`iota.api.get_inputs` to prepare inputs.
    """
    if self.hash:
      raise RuntimeError('Bundle is already finalized.')

    for addy in inputs:
      if addy.balance is None:
        raise with_context(
          exc = ValueError(
            'Address {address} has null ``balance`` '
            '(``exc.context`` has more info).'.format(
              address = addy,
            ),
          ),

          context = {
            'address': addy,
          },
        )

      if addy.key_index is None:
        raise with_context(
          exc = ValueError(
            'Address {address} has null ``key_index`` '
            '(``exc.context`` has more info).'.format(
              address = addy,
            ),
          ),

          context = {
            'address': addy,
          },
        )

      self._create_input_transactions(addy)
Beispiel #8
0
  def _execute(self, request):
    stop      = request['stop'] # type: Optional[int]
    seed      = request['seed'] # type: Seed
    start     = request['start'] # type: int
    threshold = request['threshold'] # type: Optional[int]

    # Determine the addresses we will be scanning.
    if stop is None:
      addresses =\
        [addy for addy, _ in iter_used_addresses(self.adapter, seed, start)]
    else:
      addresses = AddressGenerator(seed).get_addresses(start, stop)

    if addresses:
      # Load balances for the addresses that we generated.
      gb_response = GetBalancesCommand(self.adapter)(addresses=addresses)
    else:
      gb_response = {'balances': []}

    result = {
      'inputs': [],
      'totalBalance': 0,
    }

    threshold_met = threshold is None

    for i, balance in enumerate(gb_response['balances']):
      addresses[i].balance = balance

      if balance:
        result['inputs'].append(addresses[i])
        result['totalBalance'] += balance

        if (threshold is not None) and (result['totalBalance'] >= threshold):
          threshold_met = True
          break

    if threshold_met:
      return result
    else:
      # This is an exception case, but note that we attach the result
      # to the exception context so that it can be used for
      # troubleshooting.
      raise with_context(
        exc = BadApiResponse(
          'Accumulated balance {balance} is less than threshold {threshold} '
          '(``exc.context`` contains more information).'.format(
            threshold = threshold,
            balance   = result['totalBalance'],
          ),
        ),

        context = {
          'inputs':         result['inputs'],
          'request':        request,
          'total_balance':  result['totalBalance'],
        },
      )
Beispiel #9
0
  def send_request(self, payload, **kwargs):
    # type: (dict, dict) -> dict
    # Store a snapshot so that we can inspect the request later.
    self.requests.append(dict(payload))

    command = payload['command']

    try:
      response = self.responses[command].popleft()
    except KeyError:
      raise with_context(
        exc = BadApiResponse(
          'No seeded response for {command!r} '
          '(expected one of: {seeds!r}).'.format(
            command = command,
            seeds   = list(sorted(self.responses.keys())),
          ),
        ),

        context = {
          'request': payload,
        },
      )
    except IndexError:
      raise with_context(
        exc = BadApiResponse(
          '{command} called too many times; no seeded responses left.'.format(
            command = command,
          ),
        ),

        context = {
          'request': payload,
        },
      )

    error = response.get('exception') or response.get('error')
    if error:
      raise with_context(BadApiResponse(error), context={'request': payload})

    return response
Beispiel #10
0
def convert_value_to_standard_unit(value, symbol='i'):
  # type: (Text, Text) -> float
  """
    Converts between any two standard units of iota.

    :param value:
      Value (affixed) to convert. For example: '1.618 Mi'.

    :param symbol:
      Unit symbol of iota to convert to. For example: 'Gi'.

    :return:
      Float as units of given symbol to convert to.
  """
  try:
    # Get input value
    value_tuple = value.split()
    amount = float(value_tuple[0])
  except (ValueError, IndexError, AttributeError):
    raise with_context(ValueError('Value to convert is not valid.'),
      context = {
        'value': value,
      },
    )

  try:
    # Set unit symbols and find factor/multiplier.
    unit_symbol_from = value_tuple[1]
    unit_factor_from = float(STANDARD_UNITS[unit_symbol_from])
    unit_factor_to = float(STANDARD_UNITS[symbol])
  except (KeyError, IndexError):
    # Invalid symbol or no factor
    raise with_context(ValueError('Invalid IOTA unit.'),
      context = {
        'value': value,
        'symbol': symbol,
      },
    )

  return amount * (unit_factor_from /  unit_factor_to)
Beispiel #11
0
    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(Nonce, self).__init__(trytes, pad=self.LEN)

        if len(self._trytes) > self.LEN:
            raise with_context(
                exc=ValueError(
                    '{cls} values must be {len} trytes long.'.format(
                        cls=type(self).__name__, len=self.LEN)),
                context={
                    'trytes': trytes,
                },
            )
Beispiel #12
0
    async def send_request(self, payload: Dict, **kwargs: Any) -> dict:
        """
        Mimic asynchronous behavior of `HttpAdapter.send_request`.
        """
        # Store a snapshot so that we can inspect the request later.
        self.requests.append(dict(payload))

        command = payload['command']

        try:
            response = self.responses[command].popleft()
        except KeyError:
            raise with_context(
                exc=BadApiResponse(
                    'No seeded response for {command!r} '
                    '(expected one of: {seeds!r}).'.format(
                        command=command,
                        seeds=list(sorted(self.responses.keys())),
                    ), ),
                context={
                    'request': payload,
                },
            )
        except IndexError:
            raise with_context(
                exc=BadApiResponse(
                    '{command} called too many times; '
                    'no seeded responses left.'.format(command=command, ), ),
                context={
                    'request': payload,
                },
            )

        error = response.get('exception') or response.get('error')
        if error:
            raise with_context(BadApiResponse(error),
                               context={'request': payload})

        return await async_return(response)
Beispiel #13
0
  def __init__(self, seed, start, step, security_level):
    # type: (Seed, int, int, int) -> None
    super(KeyIterator, self).__init__()

    if start < 0:
      raise with_context(
        exc = ValueError('``start`` cannot be negative.'),

        context = {
          'start':          start,
          'step':           step,
          'security_level': security_level,
        },
      )

    if security_level < 1:
      raise with_context(
        exc = ValueError('``security_level`` must be >= 1.'),

        context = {
          'start':          start,
          'step':           step,
          'security_level': security_level,
        },
      )

    # In order to work correctly, the seed must be padded so that it is
    # a multiple of 81 trytes.
    seed += b'9' * (Hash.LEN - ((len(seed) % Hash.LEN) or Hash.LEN))

    self.security_level = security_level
    self.seed_as_trits  = seed.as_trits()
    self.start          = start
    self.step           = step

    self.current = self.start

    self.fragment_length     = FRAGMENT_LENGTH * TRITS_PER_TRYTE
    self.hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN
Beispiel #14
0
    def absorb(self, trits, offset=0, length=None):
        # type: (MutableSequence[int], int, Optional[int]) -> None
        """
    Absorb trits into the sponge from a buffer.

    :param trits:
      Buffer that contains the trits to absorb.

    :param offset:
      Starting offset in ``trits``.

    :param length:
      Number of trits to absorb.  Defaults to ``len(trits)``.
    """
        # Pad input if necessary, so that it can be divided evenly into
        # hashes.
        # Note that this operation creates a COPY of ``trits``; the
        # incoming buffer is not modified!
        pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
        trits += [0] * (TRIT_HASH_LENGTH - pad)

        if length is None:
            length = len(trits)

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``absorb``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        while offset < length:
            stop = min(offset + TRIT_HASH_LENGTH, length)

            # If we're copying over a full chunk, zero last trit
            if stop - offset == TRIT_HASH_LENGTH:
                trits[stop - 1] = 0

            signed_nums = conv.convertToBytes(trits[offset:stop])

            # Convert signed bytes into their equivalent unsigned representation
            # In order to use Python's built-in bytes type
            unsigned_bytes = bytearray(conv.convert_sign(b) for b in signed_nums)

            self.k.update(unsigned_bytes)

            offset += TRIT_HASH_LENGTH
Beispiel #15
0
  def absorb(self, trits, offset=0, length=None):
    # type: (MutableSequence[int], int, Optional[int]) -> None
    """
    Absorb trits into the sponge from a buffer.

    :param trits:
      Buffer that contains the trits to absorb.

    :param offset:
      Starting offset in ``trits``.

    :param length:
      Number of trits to absorb.  Defaults to ``len(trits)``.
    """
    # Pad input if necessary, so that it can be divided evenly into
    # hashes.
    # Note that this operation creates a COPY of ``trits``; the
    # incoming buffer is not modified!
    pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
    trits += [0] * (TRIT_HASH_LENGTH - pad)

    if length is None:
      length = len(trits)

    if length < 1:
      raise with_context(
        exc = ValueError('Invalid length passed to ``absorb``.'),

        context = {
          'trits': trits,
          'offset': offset,
          'length': length,
        },
      )

    while offset < length:
      stop = min(offset + TRIT_HASH_LENGTH, length)

      # If we're copying over a full chunk, zero last trit
      if stop - offset == TRIT_HASH_LENGTH:
        trits[stop - 1] = 0

      signed_nums = conv.convertToBytes(trits[offset:stop])

      # Convert signed bytes into their equivalent unsigned representation
      # In order to use Python's built-in bytes type
      unsigned_bytes = bytearray(conv.convert_sign(b) for b in signed_nums)

      self.k.update(unsigned_bytes)

      offset += TRIT_HASH_LENGTH
Beispiel #16
0
    def absorb(self,
               trits: Sequence[int],
               offset: Optional[int] = 0,
               length: Optional[int] = None) -> None:
        """
        Absorb trits into the sponge.

        :param trits:
            Sequence of trits to absorb.

        :param offset:
            Starting offset in ``trits``.

        :param length:
            Number of trits to absorb.  Defaults to ``len(trits)``.
        """
        pad = ((len(trits) % HASH_LENGTH) or HASH_LENGTH)
        trits += [0] * (HASH_LENGTH - pad)

        if length is None:
            length = len(trits)

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``absorb``.'),
                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        # Copy trits from ``trits`` into internal state, one hash at a
        # time, transforming internal state in between hashes.
        while offset < length:
            start = offset
            stop = min(start + HASH_LENGTH, length)

            # Copy the next hash worth of trits to internal state.
            #
            # Note that we always copy the trits to the start of the
            # state. ``self._state`` is 3 hashes long, but only the
            # first hash is "public"; the other 2 are only accessible to
            # :py:meth:`_transform`.
            self._state[0:stop - start] = trits[start:stop]

            # Transform.
            self._transform()

            # Move on to the next hash.
            offset += HASH_LENGTH
Beispiel #17
0
    def __init__(self,
                 trytes,
                 balance=None,
                 key_index=None,
                 security_level=None):
        # type: (TrytesCompatible, Optional[int], Optional[int], Optional[int]) -> None
        super(Address, self).__init__(trytes, pad=self.LEN)

        self.checksum = None
        if len(self._trytes) == (self.LEN + AddressChecksum.LEN):
            self.checksum = AddressChecksum(
                self[self.LEN:])  # type: Optional[AddressChecksum]

        elif len(self._trytes) > self.LEN:
            raise with_context(
                exc=ValueError(
                    'Address values must be {len_no_checksum} trytes (no checksum), '
                    'or {len_with_checksum} trytes (with checksum).'.format(
                        len_no_checksum=self.LEN,
                        len_with_checksum=self.LEN + AddressChecksum.LEN,
                    ), ),
                context={
                    'trytes': trytes,
                },
            )

        # Make the address sans checksum accessible.
        self.address = self[:self.LEN]  # type: TryteString

        self.balance = balance
        """
    Balance owned by this address.
    Must be set manually via the ``getInputs`` command.

    References:
      - :py:class:`iota.commands.extended.get_inputs`
      - :py:meth:`ProposedBundle.add_inputs`
    """

        self.key_index = key_index
        """
    Index of the key used to generate this address.
    Must be set manually via ``AddressGenerator``.

    References:
      - :py:class:`iota.crypto.addresses.AddressGenerator`
    """

        self.security_level = security_level
        """
Beispiel #18
0
def resolve_adapter(uri):
    # type: (AdapterSpec) -> BaseAdapter
    """
  Given a URI, returns a properly-configured adapter instance.
  """
    if isinstance(uri, BaseAdapter):
        return uri

    parsed = compat.urllib_parse.urlsplit(uri)  # type: SplitResult

    if not parsed.scheme:
        raise with_context(
            exc=InvalidUri(
                'URI must begin with "<protocol>://" (e.g., "udp://").',
            ),

            context={
                'parsed': parsed,
                'uri': uri,
            },
        )

    try:
        adapter_type = adapter_registry[parsed.scheme]
    except KeyError:
        raise with_context(
            exc=InvalidUri('Unrecognized protocol {protocol!r}.'.format(
                protocol=parsed.scheme,
            )),

            context={
                'parsed': parsed,
                'uri': uri,
            },
        )

    return adapter_type.configure(parsed)
Beispiel #19
0
def resolve_adapter(uri):
  # type: (AdapterSpec) -> BaseAdapter
  """
  Given a URI, returns a properly-configured adapter instance.
  """
  if isinstance(uri, BaseAdapter):
    return uri

  parsed = compat.urllib_parse.urlsplit(uri) # type: SplitResult

  if not parsed.scheme:
    raise with_context(
      exc = InvalidUri(
        'URI must begin with "<protocol>://" (e.g., "udp://").',
      ),

      context = {
        'parsed': parsed,
        'uri':    uri,
      },
    )

  try:
    adapter_type = adapter_registry[parsed.scheme]
  except KeyError:
    raise with_context(
      exc = InvalidUri('Unrecognized protocol {protocol!r}.'.format(
        protocol = parsed.scheme,
      )),

      context = {
        'parsed': parsed,
        'uri':    uri,
      },
    )

  return adapter_type.configure(parsed)
Beispiel #20
0
    def __init__(self, seed, start, step, security_level):
        # type: (Seed, int, int, int) -> None
        super(KeyIterator, self).__init__()

        if start < 0:
            raise with_context(
                exc=ValueError('``start`` cannot be negative.'),
                context={
                    'start': start,
                    'step': step,
                    'security_level': security_level,
                },
            )

        if security_level < 1:
            raise with_context(
                exc=ValueError('``security_level`` must be >= 1.'),
                context={
                    'start': start,
                    'step': step,
                    'security_level': security_level,
                },
            )

        # In order to work correctly, the seed must be padded so that it
        # is a multiple of 81 trytes.
        seed += b'9' * (Hash.LEN - ((len(seed) % Hash.LEN) or Hash.LEN))

        self.security_level = security_level
        self.seed_as_trits = seed.as_trits()
        self.start = start
        self.step = step

        self.current = self.start

        self.fragment_length = FRAGMENT_LENGTH * TRITS_PER_TRYTE
        self.hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN
Beispiel #21
0
    def send_request(self, payload, **kwargs):
        # type: (dict, dict) -> dict
        # Store a snapshot so that we can inspect the request later.
        self.requests.append(dict(payload))

        command = payload['command']

        try:
            response = self.responses[command].popleft()
        except KeyError:
            raise with_context(
                exc=BadApiResponse(
                    'No seeded response for {command!r} '
                    '(expected one of: {seeds!r}).'.format(
                        command=command,
                        seeds=list(sorted(self.responses.keys())),
                    ), ),
                context={
                    'request': payload,
                },
            )
        except IndexError:
            raise with_context(
                exc=BadApiResponse(
                    '{command} called too many times; '
                    'no seeded responses left.'.format(command=command, ), ),
                context={
                    'request': payload,
                },
            )

        error = response.get('exception') or response.get('error')
        if error:
            raise with_context(BadApiResponse(error),
                               context={'request': payload})

        return response
Beispiel #22
0
    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(AddressChecksum, self).__init__(trytes, pad=None)

        if len(self._trytes) != self.LEN:
            raise with_context(
                exc=ValueError(
                    '{cls} values must be exactly {len} trytes long.'.format(
                        cls=type(self).__name__,
                        len=self.LEN,
                    ), ),
                context={
                    'trytes': trytes,
                },
            )
Beispiel #23
0
  def __init__(self, trytes):
    # type: (TrytesCompatible) -> None
    super(Nonce, self).__init__(trytes, pad=self.LEN)

    if len(self._trytes) > self.LEN:
      raise with_context(
        exc = ValueError('{cls} values must be {len} trytes long.'.format(
          cls = type(self).__name__,
          len = self.LEN
        )),

        context = {
          'trytes': trytes,
        },
      )
Beispiel #24
0
    def __init__(self, trytes, key_index=None):
        # type: (TrytesCompatible, Optional[int]) -> None
        super(PrivateKey, self).__init__(trytes)

        if len(self._trytes) % FRAGMENT_LENGTH:
            raise with_context(
                exc=ValueError(
                    'Length of {cls} values must be a multiple of {len} trytes.'
                    .format(cls=type(self).__name__, len=FRAGMENT_LENGTH), ),
                context={
                    'trytes': trytes,
                },
            )

        self.key_index = key_index
Beispiel #25
0
  def _interpret_response(self, response, payload, expected_status):
    # type: (Response, dict, Container[int], bool) -> dict
    decoded =\
      super(SandboxAdapter, self)._interpret_response(
        response        = response,
        payload         = payload,
        expected_status = {codes['ok'], codes['accepted']},
      )

    # Check to see if the request was queued for asynchronous
    # execution.
    if response.status_code == codes['accepted']:
      while decoded['status'] in (STATUS_QUEUED, STATUS_RUNNING):
        self._wait_to_poll()

        poll_response = self._send_http_request(
          headers = {'Authorization': self.authorization_header},
          method  = 'get',
          payload = None,
          url     = self.get_jobs_url(decoded['id']),
        )

        decoded =\
          super(SandboxAdapter, self)._interpret_response(
            response        = poll_response,
            payload         = payload,
            expected_status = {codes['ok']},
          )

      if decoded['status'] == STATUS_FINISHED:
        return decoded['{command}Response'.format(command=decoded['command'])]

      raise with_context(
        exc = BadApiResponse(
              decoded.get('error', {}).get('message')
          or  'Command {status}: {decoded}'.format(
                decoded = decoded,
                status  = decoded['status'].lower(),
              ),
        ),

        context = {
          'request':  payload,
          'response': decoded,
        },
      )

    return decoded
Beispiel #26
0
  def __init__(self, trytes, balance=None, key_index=None, security_level=None):
    # type: (TrytesCompatible, Optional[int], Optional[int], Optional[int]) -> None
    super(Address, self).__init__(trytes, pad=self.LEN)

    self.checksum = None
    if len(self._trytes) == (self.LEN + AddressChecksum.LEN):
      self.checksum = AddressChecksum(self[self.LEN:]) # type: Optional[AddressChecksum]

    elif len(self._trytes) > self.LEN:
      raise with_context(
        exc = ValueError(
          'Address values must be {len_no_checksum} trytes (no checksum), '
          'or {len_with_checksum} trytes (with checksum).'.format(
            len_no_checksum   = self.LEN,
            len_with_checksum = self.LEN + AddressChecksum.LEN,
          ),
        ),

        context = {
          'trytes': trytes,
        },
      )

    # Make the address sans checksum accessible.
    self.address = self[:self.LEN] # type: TryteString

    self.balance = balance
    """
    Balance owned by this address.
    Must be set manually via the ``getInputs`` command.

    References:
      - :py:class:`iota.commands.extended.get_inputs`
      - :py:meth:`ProposedBundle.add_inputs`
    """

    self.key_index = key_index
    """
    Index of the key used to generate this address.
    Must be set manually via ``AddressGenerator``.

    References:
      - :py:class:`iota.crypto.addresses.AddressGenerator`
    """

    self.security_level = security_level
    """
Beispiel #27
0
  def sign_inputs(self, key_generator):
    # type: (KeyGenerator) -> None
    """
    Sign inputs in a finalized bundle.
    """
    if not self.hash:
      raise RuntimeError('Cannot sign inputs until bundle is finalized.')

    # Use a counter for the loop so that we can skip ahead as we go.
    i = 0
    while i < len(self):
      txn = self[i]

      if txn.value < 0:
        # In order to sign the input, we need to know the index of
        # the private key used to generate it.
        if txn.address.key_index is None:
          raise with_context(
            exc = ValueError(
              'Unable to sign input {input}; ``key_index`` is None '
              '(``exc.context`` has more info).'.format(
                input = txn.address,
              ),
            ),

            context = {
              'transaction': txn,
            },
          )

        signature_fragment_generator =\
          self._create_signature_fragment_generator(key_generator, txn)

        # We can only fit one signature fragment into each transaction,
        # so we have to split the entire signature among the extra
        # transactions we created for this input in
        # :py:meth:`add_inputs`.
        for j in range(AddressGenerator.DIGEST_ITERATIONS):
          self[i+j].signature_message_fragment =\
            next(signature_fragment_generator)

        i += AddressGenerator.DIGEST_ITERATIONS
      else:
        # No signature needed (nor even possible, in some cases); skip
        # this transaction.
        i += 1
Beispiel #28
0
  def __init__(self, trytes):
    # type: (TrytesCompatible) -> None
    super(AddressChecksum, self).__init__(trytes, pad=None)

    if len(self._trytes) != self.LEN:
      raise with_context(
        exc = ValueError(
          '{cls} values must be exactly {len} trytes long.'.format(
            cls = type(self).__name__,
            len = self.LEN,
          ),
        ),

        context = {
          'trytes': trytes,
        },
      )
Beispiel #29
0
    def as_tryte_string(self):
        # type: () -> TryteString
        """
    Returns a TryteString representation of the transaction.
    """
        if not self.bundle_hash:
            raise with_context(
                exc=RuntimeError(
                    'Cannot get TryteString representation of {cls} instance '
                    'without a bundle hash; call ``bundle.finalize()`` first '
                    '(``exc.context`` has more info).'.format(
                        cls=type(self).__name__, ), ),
                context={
                    'transaction': self,
                },
            )

        return super(ProposedTransaction, self).as_tryte_string()
Beispiel #30
0
    def __init__(self, trytes, key_index=None):
        # type: (TrytesCompatible, Optional[int]) -> None
        super(Digest, self).__init__(trytes)

        # A digest is a series of hashes; its length should reflect that.
        if len(self) % Hash.LEN:
            raise with_context(
                exc=ValueError(
                    'Length of {cls} values must be a multiple of {len} trytes.'
                    .format(
                        cls=type(self).__name__,
                        len=Hash.LEN,
                    ), ),
                context={
                    'trytes': trytes,
                },
            )

        self.key_index = key_index
Beispiel #31
0
 def __eq__(self, other):
     # type: (TrytesCompatible) -> bool
     if isinstance(other, TryteString):
         return self._trytes == other._trytes
     elif isinstance(other, (binary_type, bytearray)):
         return self._trytes == other
     else:
         raise with_context(
             exc=TypeError(
                 'Invalid type for TryteString comparison '
                 '(expected Union[TryteString, {binary_type}, bytearray], '
                 'actual {type}).'.format(
                     binary_type=binary_type.__name__,
                     type=type(other).__name__,
                 ), ),
             context={
                 'other': other,
             },
         )
Beispiel #32
0
    def __setitem__(self, item, trytes):
        # type: (Union[int, slice], TrytesCompatible) -> None
        new_trytes = TryteString(trytes)

        if isinstance(item, slice):
            self._trytes[item] = new_trytes._trytes
        elif len(new_trytes) > 1:
            raise with_context(
                exc=ValueError(
                    'Cannot assign multiple trytes to the same index '
                    '(``exc.context`` has more info).'),
                context={
                    'self': self,
                    'index': item,
                    'new_trytes': new_trytes,
                },
            )
        else:
            self._trytes[item] = new_trytes._trytes[0]
Beispiel #33
0
        async def fetch_and_validate(tx_hash):
            bundle = (await TraverseBundleCommand(self.adapter)(
                transaction=tx_hash
            ))['bundles'][0]  # Currently 1 bundle only

            validator = BundleValidator(bundle)

            if not validator.is_valid():
                raise with_context(
                    exc=BadApiResponse(
                        'Bundle failed validation (``exc.context`` has more info).',
                    ),

                    context={
                        'bundle': bundle,
                        'errors': validator.errors,
                    },
                )

            return bundle
Beispiel #34
0
  def __init__(self, trytes, key_index=None):
    # type: (TrytesCompatible, Optional[int]) -> None
    super(Digest, self).__init__(trytes)

    # A digest is a series of hashes; its length should reflect that.
    if len(self) % Hash.LEN:
      raise with_context(
        exc = ValueError(
          'Length of {cls} values must be a multiple of {len} trytes.'.format(
            cls = type(self).__name__,
            len = Hash.LEN,
          ),
        ),

        context = {
          'trytes': trytes,
        },
      )

    self.key_index = key_index
Beispiel #35
0
  def __init__(self, trytes, key_index=None, security_level=None):
    # type: (TrytesCompatible, Optional[int], Optional[int]) -> None
    super(PrivateKey, self).__init__(trytes)

    if len(self._trytes) % FRAGMENT_LENGTH:
      raise with_context(
        exc = ValueError(
          'Length of {cls} values must be a multiple of {len} trytes.'.format(
            cls = type(self).__name__,
            len = FRAGMENT_LENGTH,
          ),
        ),

        context = {
          'trytes': self._trytes,
        },
      )

    self.key_index      = key_index
    self.security_level = security_level
Beispiel #36
0
 def __add__(self, other):
     # type: (TrytesCompatible) -> TryteString
     if isinstance(other, TryteString):
         return TryteString(self._trytes + other._trytes)
     elif isinstance(other, text_type):
         return TryteString(self._trytes + other.encode('ascii'))
     elif isinstance(other, (binary_type, bytearray)):
         return TryteString(self._trytes + other)
     else:
         raise with_context(
             exc=TypeError(
                 'Invalid type for TryteString concatenation '
                 '(expected Union[TryteString, {binary_type}, bytearray], '
                 'actual {type}).'.format(
                     binary_type=binary_type.__name__,
                     type=type(other).__name__,
                 ), ),
             context={
                 'other': other,
             },
         )
Beispiel #37
0
  def __setitem__(self, item, trytes):
    # type: (Union[int, slice], TrytesCompatible) -> None
    new_trytes = TryteString(trytes)

    if isinstance(item, slice):
      self._trytes[item] = new_trytes._trytes
    elif len(new_trytes) > 1:
      raise with_context(
        exc = ValueError(
          'Cannot assign multiple trytes to the same index '
          '(``exc.context`` has more info).'
        ),

        context = {
          'self':       self,
          'index':      item,
          'new_trytes': new_trytes,
        },
      )
    else:
      self._trytes[item] = new_trytes._trytes[0]
Beispiel #38
0
    def __init__(self,
                 trytes: TrytesCompatible,
                 key_index: Optional[int] = None,
                 security_level: Optional[int] = None) -> None:
        super(PrivateKey, self).__init__(trytes)

        if len(self._trytes) % FRAGMENT_LENGTH:
            raise with_context(
                exc=ValueError(
                    'Length of {cls} values '
                    'must be a multiple of {len} trytes.'.format(
                        cls=type(self).__name__,
                        len=FRAGMENT_LENGTH,
                    ), ),
                context={
                    'trytes': self._trytes,
                },
            )

        self.key_index = key_index
        self.security_level = security_level
Beispiel #39
0
  def as_tryte_string(self):
    # type: () -> TryteString
    """
    Returns a TryteString representation of the transaction.
    """
    if not self.bundle_hash:
      raise with_context(
        exc = RuntimeError(
          'Cannot get TryteString representation of {cls} instance '
          'without a bundle hash; call ``bundle.finalize()`` first '
          '(``exc.context`` has more info).'.format(
            cls = type(self).__name__,
          ),
        ),

        context = {
          'transaction': self,
        },
      )

    return super(ProposedTransaction, self).as_tryte_string()
Beispiel #40
0
    def __eq__(self, other: TrytesCompatible) -> bool:
        if isinstance(other, TryteString):
            return self._trytes == other._trytes
        elif isinstance(other, str):
            return self._trytes == other.encode('ascii')
        elif isinstance(other, (bytes, bytearray)):
            return self._trytes == other
        else:
            raise with_context(
                exc=TypeError(
                    'Invalid type for TryteString comparison '
                    '(expected Union[TryteString, {bytes}, bytearray], '
                    'actual {type}).'.format(
                        bytes=bytes.__name__,
                        type=type(other).__name__,
                    ),
                ),

                context={
                    'other': other,
                },
            )
Beispiel #41
0
    def _execute(self, request):
        transaction_hash = request['transaction']  # type: TransactionHash

        bundle = Bundle(self._traverse_bundle(transaction_hash))
        validator = BundleValidator(bundle)

        if not validator.is_valid():
            raise with_context(
                exc=BadApiResponse(
                    'Bundle failed validation (``exc.context`` has more info).',
                ),
                context={
                    'bundle': bundle,
                    'errors': validator.errors,
                },
            )

        return {
            # Always return a list, so that we have the necessary structure
            # to return multiple bundles in a future iteration.
            'bundles': [bundle],
        }
Beispiel #42
0
  def _execute(self, request):
    transaction_hash = request['transaction'] # type: TransactionHash

    bundle    = Bundle(self._traverse_bundle(transaction_hash))
    validator = BundleValidator(bundle)

    if not validator.is_valid():
      raise with_context(
        exc = BadApiResponse(
          'Bundle failed validation (``exc.context`` has more info).',
        ),

        context = {
          'bundle': bundle,
          'errors': validator.errors,
        },
      )

    return {
      # Always return a list, so that we have the necessary structure
      # to return multiple bundles in a future iteration.
      'bundles': [bundle],
    }
Beispiel #43
0
  def __eq__(self, other):
    # type: (TrytesCompatible) -> bool
    if isinstance(other, TryteString):
      return self._trytes == other._trytes
    elif isinstance(other, text_type):
      return self._trytes == other.encode('ascii')
    elif isinstance(other, (binary_type, bytearray)):
      return self._trytes == other
    else:
      raise with_context(
        exc = TypeError(
          'Invalid type for TryteString comparison '
          '(expected Union[TryteString, {binary_type}, bytearray], '
          'actual {type}).'.format(
            binary_type = binary_type.__name__,
            type        = type(other).__name__,
          ),
        ),

        context = {
          'other': other,
        },
      )
Beispiel #44
0
  def _interpret_response(self, response, payload, expected_status):
    # type: (Response, dict, Container[int], bool) -> dict
    decoded =\
      super(SandboxAdapter, self)._interpret_response(
        response        = response,
        payload         = payload,
        expected_status = {codes['ok'], codes['accepted']},
      )

    # Check to see if the request was queued for asynchronous
    # execution.
    if response.status_code == codes['accepted']:
      poll_count = 0
      while decoded['status'] in (STATUS_QUEUED, STATUS_RUNNING):
        if poll_count >= self.max_polls:
          raise with_context(
            exc =
              BadApiResponse(
                '``{command}`` job timed out after {duration} seconds '
                '(``exc.context`` has more info).'.format(
                  command   = decoded['command'],
                  duration  = self.poll_interval * self.max_polls,
                ),
              ),

            context = {
              'request':  payload,
              'response': decoded,
            },
          )

        self._wait_to_poll()
        poll_count += 1

        poll_response = self._send_http_request(
          headers = {'Authorization': self.authorization_header},
          method  = 'get',
          payload = None,
          url     = self.get_jobs_url(decoded['id']),
        )

        decoded =\
          super(SandboxAdapter, self)._interpret_response(
            response        = poll_response,
            payload         = payload,
            expected_status = {codes['ok']},
          )

      if decoded['status'] == STATUS_FINISHED:
        return decoded['{command}Response'.format(command=decoded['command'])]

      raise with_context(
        exc = BadApiResponse(
              decoded.get('error', {}).get('message')
          or  'Command {status}: {decoded}'.format(
                decoded = decoded,
                status  = decoded['status'].lower(),
              ),
        ),

        context = {
          'request':  payload,
          'response': decoded,
        },
      )

    return decoded
Beispiel #45
0
    def add_inputs(self, inputs):
        # type: (Iterable[Address]) -> None
        """
    Adds inputs to spend in the bundle.

    Note that each input requires two transactions, in order to
    hold the entire signature.

    :param inputs:
      Addresses to use as the inputs for this bundle.

      IMPORTANT: Must have ``balance`` and ``key_index`` attributes!
      Use :py:meth:`iota.api.get_inputs` to prepare inputs.
    """
        if self.hash:
            raise RuntimeError('Bundle is already finalized.')

        for addy in inputs:
            if addy.balance is None:
                raise with_context(
                    exc=ValueError(
                        'Address {address} has null ``balance`` '
                        '(``exc.context`` has more info).'.format(
                            address=addy, ), ),
                    context={
                        'address': addy,
                    },
                )

            if addy.key_index is None:
                raise with_context(
                    exc=ValueError(
                        'Address {address} has null ``key_index`` '
                        '(``exc.context`` has more info).'.format(
                            address=addy, ), ),
                    context={
                        'address': addy,
                    },
                )

            # Add the input as a transaction.
            self._transactions.append(
                ProposedTransaction(
                    address=addy,
                    tag=self.tag,

                    # Spend the entire address balance; if necessary, we will add a
                    # change transaction to the bundle.
                    value=-addy.balance,
                ))

            # Signatures require additional transactions to store, due to
            # transaction length limit.
            # Subtract 1 to account for the transaction we just added.
            for _ in range(AddressGenerator.DIGEST_ITERATIONS - 1):
                self._transactions.append(
                    ProposedTransaction(
                        address=addy,
                        tag=self.tag,

                        # Note zero value; this is a meta transaction.
                        value=0,
                    ))
Beispiel #46
0
  def create_generator(self, start=0, step=1, iterations=1):
    # type: (int, int) -> Generator[PrivateKey]
    """
    Creates a generator that can be used to progressively generate new
    keys.

    :param start:
      Starting index.

      Warning: This method may take awhile to reset if ``start``
      is a large number!

    :param step:
      Number of indexes to advance after each key.

      This value can be negative; the generator will exit if it
      reaches an index < 0.

      Warning: The generator may take awhile to advance between
      iterations if ``step`` is a large number!

    :param iterations:
      Number of _transform iterations to apply to each key.
      Must be >= 1.

      Increasing this value makes key generation slower, but more
      resistant to brute-forcing.
    """
    if start < 0:
      raise with_context(
        exc = ValueError('``start`` cannot be negative.'),

        context = {
          'start':      start,
          'step':       step,
          'iterations': iterations,
        },
      )

    if iterations < 1:
      raise with_context(
        exc = ValueError('``iterations`` must be >= 1.'),

        context = {
          'start':      start,
          'step':       step,
          'iterations': iterations,
        },
      )

    current = start

    fragment_length     = FRAGMENT_LENGTH * TRITS_PER_TRYTE
    hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN

    while current >= 0:
      sponge = self._create_sponge(current)

      key     = [0] * (fragment_length * iterations)
      buffer  = [0] * HASH_LENGTH # type: MutableSequence[int]

      for fragment_seq in range(iterations):
        # Squeeze trits from the buffer and append them to the key, one
        # hash at a time.
        for hash_seq in range(hashes_per_fragment):
          sponge.squeeze(buffer)

          key_start =\
            (fragment_seq * fragment_length) + (hash_seq * HASH_LENGTH)

          key_stop = key_start + HASH_LENGTH

          key[key_start:key_stop] = buffer

      private_key = PrivateKey.from_trits(key)
      private_key.key_index = current
      yield private_key

      current += step
Beispiel #47
0
  def get_keys(self, start, count=1, step=1, iterations=1):
    # type: (int, int, int, int) -> List[PrivateKey]
    """
    Generates and returns one or more keys at the specified index(es).

    This is a one-time operation; if you want to create lots of keys
    across multiple contexts, consider invoking
    :py:meth:`create_generator` and sharing the resulting generator
    object instead.

    Warning: This method may take awhile to run if the starting index
    and/or the number of requested keys is a large number!

    :param start:
      Starting index.
      Must be >= 0.

    :param count:
      Number of keys to generate.
      Must be > 0.

    :param step:
      Number of indexes to advance after each key.
      This may be any non-zero (positive or negative) integer.

    :param iterations:
      Number of _transform iterations to apply to each key.
      Must be >= 1.

      Increasing this value makes key generation slower, but more
      resistant to brute-forcing.

    :return:
      Always returns a list, even if only one key is generated.

      The returned list will contain ``count`` keys, except when
      ``step * count < start`` (only applies when ``step`` is
      negative).
    """
    if count < 1:
      raise with_context(
        exc = ValueError('``count`` must be positive.'),

        context = {
          'start':      start,
          'count':      count,
          'step':       step,
          'iterations': iterations,
        },
      )

    if not step:
      raise with_context(
        exc = ValueError('``step`` must not be zero.'),

        context = {
          'start':      start,
          'count':      count,
          'step':       step,
          'iterations': iterations,
        },
      )

    generator = self.create_generator(start, step, iterations)

    keys = []
    for _ in range(count):
      try:
        next_key = next(generator)
      except StopIteration:
        break
      else:
        keys.append(next_key)

    return keys
Beispiel #48
0
  def __init__(
      self,
      uri,
      auth_token,
      poll_interval = DEFAULT_POLL_INTERVAL,
      max_polls     = DEFAULT_MAX_POLLS,
  ):
    # type: (Union[Text, SplitResult], Optional[Text], int, int) -> None
    """
    :param uri:
      URI of the node to connect to.
      ``https://` URIs are recommended!

      Note: Make sure the URI specifies the correct path!

      Example:

      - Incorrect: ``https://sandbox.iota:14265``
      - Correct:   ``https://sandbox.iota:14265/api/v1/``

    :param auth_token:
      Authorization token used to authenticate requests.

      Contact the node's maintainer to obtain a token.

      If ``None``, the adapter will not include authorization metadata
      with requests.

    :param poll_interval:
      Number of seconds to wait between requests to check job status.
      Must be a positive integer.

      Smaller values will cause the adapter to return a result sooner
      (once the node completes the job), but it increases traffic to
      the node (which may trip a rate limiter and/or incur additional
      costs).

    :param max_polls:
      Max number of times to poll for job status before giving up.
      Must be a positive integer.

      This is effectively a timeout setting for asynchronous jobs;
      multiply by ``poll_interval`` to get the timeout duration.
    """
    super(SandboxAdapter, self).__init__(uri)

    if not (isinstance(auth_token, text_type) or (auth_token is None)):
      raise with_context(
        exc =
          TypeError(
            '``auth_token`` must be a unicode string or ``None`` '
            '(``exc.context`` has more info).'
          ),

        context = {
          'auth_token': auth_token,
        },
      )

    if auth_token == '':
      raise with_context(
        exc =
          ValueError(
            'Set ``auth_token=None`` if requests do not require authorization '
            '(``exc.context`` has more info).',
          ),

        context = {
          'auth_token': auth_token,
        },
      )

    if not isinstance(poll_interval, int):
      raise with_context(
        exc =
          TypeError(
            '``poll_interval`` must be an int '
            '(``exc.context`` has more info).',
          ),

        context = {
          'poll_interval': poll_interval,
        },
      )

    if poll_interval < 1:
      raise with_context(
        exc =
          ValueError(
            '``poll_interval`` must be > 0 '
            '(``exc.context`` has more info).',
          ),

        context = {
          'poll_interval': poll_interval,
        },
      )

    if not isinstance(max_polls, int):
      raise with_context(
        exc =
          TypeError(
            '``max_polls`` must be an int '
            '(``exc.context`` has more info).',
          ),

        context = {
          'max_polls': max_polls,
        },
      )

    if max_polls < 1:
      raise with_context(
        exc =
          ValueError(
            '``max_polls`` must be > 0 '
            '(``exc.context`` has more info).',
          ),

        context = {
          'max_polls': max_polls,
        },
      )

    self.auth_token     = auth_token # type: Optional[Text]
    self.poll_interval  = poll_interval # type: int
    self.max_polls      = max_polls # type: int
Beispiel #49
0
    def squeeze(self, trits, offset=0, length=None):
        # type: (MutableSequence[int], int, Optional[int]) -> None
        """
    Squeeze trits from the sponge into a buffer.

    :param trits:
      Buffer that will hold the squeezed trits.

      IMPORTANT:  If ``trits`` is too small, it will be extended!

    :param offset:
      Starting offset in ``trits``.

    :param length:
      Number of trits to squeeze from the sponge.

      If not specified, defaults to :py:data:`TRIT_HASH_LENGTH` (i.e.,
      by default, we will try to squeeze exactly 1 hash).
    """
        # Pad input if necessary, so that it can be divided evenly into
        # hashes.
        pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
        trits += [0] * (TRIT_HASH_LENGTH - pad)

        if length is None:
            # By default, we will try to squeeze one hash.
            # Note that this is different than ``absorb``.
            length = len(trits) or TRIT_HASH_LENGTH

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``squeeze``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        while offset < length:
            unsigned_hash = self.k.digest()

            if PY2:
                unsigned_hash = map(ord, unsigned_hash)  # type: ignore

            signed_hash = [conv.convert_sign(b) for b in unsigned_hash]

            trits_from_hash = conv.convertToTrits(signed_hash)
            trits_from_hash[TRIT_HASH_LENGTH - 1] = 0

            stop = min(TRIT_HASH_LENGTH, length - offset)
            trits[offset:offset + stop] = trits_from_hash[0:stop]

            flipped_bytes = bytearray(conv.convert_sign(~b) for b in unsigned_hash)

            # Reset internal state before feeding back in
            self.reset()
            self.k.update(flipped_bytes)

            offset += TRIT_HASH_LENGTH
Beispiel #50
0
    def _interpret_response(self, response, payload, expected_status):
        # type: (Response, dict, Container[int]) -> dict
        """
        Interprets the HTTP response from the node.

        :param response:
            The response object received from
            :py:meth:`_send_http_request`.

        :param payload:
            The request payload that was sent (used for debugging).

        :param expected_status:
            The response should match one of these status codes to be
            considered valid.
        """
        raw_content = response.text
        if not raw_content:
            raise with_context(
                exc=BadApiResponse(
                    'Empty {status} response from node.'.format(
                        status=response.status_code, ), ),
                context={
                    'request': payload,
                },
            )

        try:
            decoded = json.loads(raw_content)  # type: dict
        # :bc: py2k doesn't have JSONDecodeError
        except ValueError:
            raise with_context(
                exc=BadApiResponse('Non-JSON {status} response from node: '
                                   '{raw_content}'.format(
                                       status=response.status_code,
                                       raw_content=raw_content,
                                   )),
                context={
                    'request': payload,
                    'raw_response': raw_content,
                },
            )

        if not isinstance(decoded, dict):
            raise with_context(
                exc=BadApiResponse(
                    'Malformed {status} response from node: {decoded!r}'.
                    format(
                        status=response.status_code,
                        decoded=decoded,
                    ), ),
                context={
                    'request': payload,
                    'response': decoded,
                },
            )

        if response.status_code in expected_status:
            return decoded

        error = None
        try:
            if response.status_code == codes['bad_request']:
                error = decoded['error']
            elif response.status_code == codes['internal_server_error']:
                error = decoded['exception']
        except KeyError:
            pass

        raise with_context(
            exc=BadApiResponse(
                '{status} response from node: {error}'.format(
                    error=error or decoded,
                    status=response.status_code,
                ), ),
            context={
                'request': payload,
                'response': decoded,
            },
        )
Beispiel #51
0
  def add_inputs(self, inputs):
    # type: (Iterable[MultisigAddress]) -> None
    """
    Adds inputs to spend in the bundle.

    Note that each input may require multiple transactions, in order to
    hold the entire signature.

    :param inputs:
      MultisigAddresses to use as the inputs for this bundle.

      Note: at this time, only a single multisig input is supported.
    """
    if self.hash:
      raise RuntimeError('Bundle is already finalized.')

    count = 0
    for addy in inputs:
      if count > 0:
        raise ValueError(
          '{cls} only supports 1 input.'.format(cls=type(self).__name__),
        )

      if not isinstance(addy, MultisigAddress):
        raise with_context(
          exc =
            TypeError(
              'Incorrect input type for {cls} '
              '(expected {expected}, actual {actual}).'.format(
                actual    = type(addy).__name__,
                cls       = type(self).__name__,
                expected  = MultisigAddress.__name__,
              ),
            ),

          context = {
            'actual_input': addy,
          },
        )

      security_level = addy.security_level
      if security_level < 1:
        raise with_context(
          exc =
            ValueError(
              'Unable to determine security level for {type} '
              '(is ``digests`` populated correctly?).'.format(
                type = type(addy).__name__,
              ),
            ),

          context = {
            'actual_input':   addy,
            'security_level': security_level,
          },
        )

      if not addy.balance:
        raise with_context(
          exc =
            ValueError(
              'Cannot add input with empty/unknown balance to {type} '
              '(use ``Iota.get_balances`` to get balance first).'.format(
                type = type(self).__name__,
              ),
            ),

          context = {
            'actual_input': addy,
          },
        )

      self._create_input_transactions(addy)

      count += 1
Beispiel #52
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(),
        }
Beispiel #53
0
    def add_inputs(self, inputs):
        # type: (Iterable[MultisigAddress]) -> None
        """
    Adds inputs to spend in the bundle.

    Note that each input may require multiple transactions, in order to
    hold the entire signature.

    :param inputs:
      MultisigAddresses to use as the inputs for this bundle.

      Note: at this time, only a single multisig input is supported.
    """
        if self.hash:
            raise RuntimeError('Bundle is already finalized.')

        count = 0
        for addy in inputs:
            if count > 0:
                raise ValueError(
                    '{cls} only supports 1 input.'.format(
                        cls=type(self).__name__), )

            if not isinstance(addy, MultisigAddress):
                raise with_context(
                    exc=TypeError(
                        'Incorrect input type for {cls} '
                        '(expected {expected}, actual {actual}).'.format(
                            actual=type(addy).__name__,
                            cls=type(self).__name__,
                            expected=MultisigAddress.__name__,
                        ), ),
                    context={
                        'actual_input': addy,
                    },
                )

            security_level = addy.security_level
            if security_level < 1:
                raise with_context(
                    exc=ValueError(
                        'Unable to determine security level for {type} '
                        '(is ``digests`` populated correctly?).'.format(
                            type=type(addy).__name__, ), ),
                    context={
                        'actual_input': addy,
                        'security_level': security_level,
                    },
                )

            if not addy.balance:
                raise with_context(
                    exc=ValueError(
                        'Cannot add input with empty/unknown balance to {type} '
                        '(use ``Iota.get_balances`` to get balance first).'.
                        format(type=type(self).__name__, ), ),
                    context={
                        'actual_input': addy,
                    },
                )

            self._create_input_transactions(addy)

            count += 1
Beispiel #54
0
  def get_addresses(self, start, count=1, step=1):
    # type: (int, int, int) -> List[Address]
    """
    Generates and returns one or more addresses at the specified
    index(es).

    This is a one-time operation; if you want to create lots of
    addresses across multiple contexts, consider invoking
    :py:meth:`create_iterator` and sharing the resulting generator
    object instead.

    Warning: This method may take awhile to run if the starting index
    and/or the number of requested addresses is a large number!

    :param start:
      Starting index.
      Must be >= 0.

    :param count:
      Number of addresses to generate.
      Must be > 0.

    :param step:
      Number of indexes to advance after each address.
      This may be any non-zero (positive or negative) integer.

    :return:
      Always returns a list, even if only one address is generated.

      The returned list will contain ``count`` addresses, except when
      ``step * count < start`` (only applies when ``step`` is
      negative).
    """
    if count < 1:
      raise with_context(
        exc = ValueError('``count`` must be positive.'),

        context = {
          'start':  start,
          'count':  count,
          'step':   step,
        },
      )

    if not step:
      raise with_context(
        exc = ValueError('``step`` must not be zero.'),

        context = {
          'start':  start,
          'count':  count,
          'step':   step,
        },
      )

    generator = self.create_iterator(start, step)

    addresses = []
    for _ in range(count):
      try:
        next_addy = next(generator)
      except StopIteration:
        break
      else:
        addresses.append(next_addy)

    return addresses
  def _execute(self, request):
    change_address  = request['changeAddress'] # type: Optional[Address]
    multisig_input  = request['multisigInput'] # type: MultisigAddress
    transfers       = request['transfers'] # type: List[ProposedTransaction]

    bundle = ProposedMultisigBundle(transfers)

    want_to_spend = bundle.balance
    if want_to_spend > 0:
      gb_response =\
        GetBalancesCommand(self.adapter)(
          addresses = [multisig_input],
        )

      multisig_input.balance = gb_response['balances'][0]

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

          # The structure of this context object is intended to match
          # the one from ``PrepareTransferCommand``.
          context = {
            'available_to_spend': multisig_input.balance,
            'confirmed_inputs':   [multisig_input],
            'request':            request,
            'want_to_spend':      want_to_spend,
          },
        )

      bundle.add_inputs([multisig_input])

      if bundle.balance < 0:
        if change_address:
          bundle.send_unspent_inputs_to(change_address)
        else:
          #
          # Unlike :py:meth:`iota.api.Iota.prepare_transfer` where all
          # of the inputs are owned by the same seed, creating a
          # multisig transfer usually involves multiple people.
          #
          # It would be unfair to the participants of the transaction
          # if we were to automatically generate a change address using
          # the seed of whoever happened to invoke the
          # :py:meth:`MultisigIota.prepare_multisig_transfer` method!
          #
          raise with_context(
            exc =
              ValueError(
                'Bundle has unspent inputs, but no change address specified.',
              ),

            context = {
              'available_to_spend': multisig_input.balance,
              'balance':            bundle.balance,
              'confirmed_inputs':   [multisig_input],
              'request':            request,
              'want_to_spend':      want_to_spend,
            },
          )
    else:
      raise with_context(
        exc =
          ValueError(
            'Use ``prepare_transfer`` '
            'to create a bundle without spending IOTAs.',
          ),

        context = {
          'request': request,
        },
      )

    bundle.finalize()

    # Return the bundle with inputs unsigned.
    return {
      'trytes': bundle.as_tryte_strings(),
    }
Beispiel #56
0
  def squeeze(self, trits, offset=0, length=None):
    # type: (MutableSequence[int], int, Optional[int]) -> None
    """
    Squeeze trits from the sponge into a buffer.

    :param trits:
      Buffer that will hold the squeezed trits.

      IMPORTANT:  If ``trits`` is too small, it will be extended!

    :param offset:
      Starting offset in ``trits``.

    :param length:
      Number of trits to squeeze from the sponge.

      If not specified, defaults to :py:data:`TRIT_HASH_LENGTH` (i.e.,
      by default, we will try to squeeze exactly 1 hash).
    """
    # Pad input if necessary, so that it can be divided evenly into
    # hashes.
    pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
    trits += [0] * (TRIT_HASH_LENGTH - pad)

    if length is None:
      # By default, we will try to squeeze one hash.
      # Note that this is different than ``absorb``.
      length = len(trits) or TRIT_HASH_LENGTH

    if length < 1:
      raise with_context(
        exc = ValueError('Invalid length passed to ``squeeze``.'),

        context = {
          'trits': trits,
          'offset': offset,
          'length': length,
        },
      )

    while offset < length:
      unsigned_hash = self.k.digest()

      if PY2:
        unsigned_hash = map(ord, unsigned_hash) # type: ignore

      signed_hash = [conv.convert_sign(b) for b in unsigned_hash]

      trits_from_hash = conv.convertToTrits(signed_hash)
      trits_from_hash[TRIT_HASH_LENGTH - 1] = 0

      stop = min(TRIT_HASH_LENGTH, length-offset)
      trits[offset:offset+stop] = trits_from_hash[0:stop]

      flipped_bytes = bytearray(conv.convert_sign(~b) for b in unsigned_hash)

      # Reset internal state before feeding back in
      self.reset()
      self.k.update(flipped_bytes)

      offset += TRIT_HASH_LENGTH
Beispiel #57
0
    def get_addresses(self, start, count=1, step=1):
        # type: (int, int, int) -> List[Address]
        """
        Generates and returns one or more addresses at the specified
        index(es).

        This is a one-time operation; if you want to create lots of
        addresses across multiple contexts, consider invoking
        :py:meth:`create_iterator` and sharing the resulting generator
        object instead.

        Warning: This method may take awhile to run if the starting
        index and/or the number of requested addresses is a large
        number!

        :param start:
            Starting index.
            Must be >= 0.

        :param count:
            Number of addresses to generate.
            Must be > 0.

        :param step:
            Number of indexes to advance after each address.
            This may be any non-zero (positive or negative) integer.

        :return:
            Always returns a list, even if only one address is generated.

            The returned list will contain ``count`` addresses, except
            when ``step * count < start`` (only applies when ``step`` is
            negative).
        """
        if count < 1:
            raise with_context(
                exc=ValueError('``count`` must be positive.'),
                context={
                    'start': start,
                    'count': count,
                    'step': step,
                },
            )

        if not step:
            raise with_context(
                exc=ValueError('``step`` must not be zero.'),
                context={
                    'start': start,
                    'count': count,
                    'step': step,
                },
            )

        generator = self.create_iterator(start, step)

        addresses = []
        for _ in range(count):
            try:
                next_addy = next(generator)
            except StopIteration:
                break
            else:
                addresses.append(next_addy)

        return addresses
Beispiel #58
0
  def get_keys(self, start, count=1, step=1, iterations=1):
    # type: (int, int, int, int) -> List[PrivateKey]
    """
    Generates and returns one or more keys at the specified index(es).

    This is a one-time operation; if you want to create lots of keys
    across multiple contexts, consider invoking
    :py:meth:`create_iterator` and sharing the resulting generator
    object instead.

    Warning: This method may take awhile to run if the starting index
    and/or the number of requested keys is a large number!

    :param start:
      Starting index.
      Must be >= 0.

    :param count:
      Number of keys to generate.
      Must be > 0.

    :param step:
      Number of indexes to advance after each key.
      This may be any non-zero (positive or negative) integer.

    :param iterations:
      Number of transform iterations to apply to each key, also known
      as security level.
      Must be >= 1.

      Increasing this value makes key generation slower, but more
      resistant to brute-forcing.

    :return:
      Always returns a list, even if only one key is generated.

      The returned list will contain ``count`` keys, except when
      ``step * count < start`` (only applies when ``step`` is
      negative).
    """
    if count < 1:
      raise with_context(
        exc = ValueError('``count`` must be positive.'),

        context = {
          'start':      start,
          'count':      count,
          'step':       step,
          'iterations': iterations,
        },
      )

    if not step:
      raise with_context(
        exc = ValueError('``step`` must not be zero.'),

        context = {
          'start':      start,
          'count':      count,
          'step':       step,
          'iterations': iterations,
        },
      )

    iterator = self.create_iterator(start, step, iterations)

    keys = []
    for _ in range(count):
      try:
        next_key = next(iterator)
      except StopIteration:
        break
      else:
        keys.append(next_key)

    return keys
Beispiel #59
0
    def sign_input_transactions(self, bundle, start_index):
        # type: (Bundle, int) -> None
        """
        Signs the inputs starting at the specified index.

        :param Bundle bundle:
            The bundle that contains the input transactions to sign.

        :param int start_index:
            The index of the first input transaction.

            If necessary, the resulting signature will be split across
            subsequent transactions automatically.

        :raises ValuError:
            - if ``bundle`` is not finalized.
            - if attempting to sign non-input transactions.
            - if attempting to sign transactions with non-empty
              ``signature_message_fragment`` field.
        :raises IndexError: if wrong ``start_index`` is provided.
        """

        if not bundle.hash:
            raise with_context(
                exc=ValueError('Cannot sign inputs without a bundle hash!'),
                context={
                    'bundle': bundle,
                    'key_index': self.key_index,
                    'start_index': start_index,
                },
            )

        from iota.crypto.signing import SignatureFragmentGenerator
        signature_fragment_generator = (SignatureFragmentGenerator(
            self, bundle.hash))

        # We can only fit one signature fragment into each transaction,
        # so we have to split the entire signature.
        for j in range(self.security_level):
            # Do lots of validation before we attempt to sign the
            # transaction, and attach lots of context info to any
            # exception.
            #
            # This method is likely to be invoked at a very low level in
            # the application, so if anything goes wrong, we want to
            # make sure it's as easy to troubleshoot as possible!
            try:
                txn = bundle[start_index + j]
            except IndexError as e:
                raise with_context(
                    exc=e,
                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'current_index': start_index + j,
                    },
                )

            # Only inputs can be signed.
            if txn.value > 0:
                raise with_context(
                    exc=ValueError(
                        'Attempting to sign non-input transaction #{i} '
                        '(value={value}).'.format(
                            i=txn.current_index,
                            value=txn.value,
                        ), ),
                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'start_index': start_index,
                    },
                )

            if txn.signature_message_fragment:
                raise with_context(
                    exc=ValueError(
                        'Attempting to sign input transaction #{i}, '
                        'but it has a non-empty fragment '
                        '(is it already signed?).'.format(
                            i=txn.current_index, ), ),
                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'start_index': start_index,
                    },
                )

            txn.signature_message_fragment = next(signature_fragment_generator)
Beispiel #60
0
  def decode(self, input, errors='strict'):
    """
    Decodes a tryte string into bytes.
    """
    if isinstance(input, memoryview):
      input = input.tobytes()

    if not isinstance(input, (binary_type, bytearray)):
      raise with_context(
        exc = TypeError("Can't decode {type}; byte string expected.".format(
          type = type(input).__name__,
        )),

        context = {
          'input': input,
        },
      )

    # :bc: In Python 2, iterating over a byte string yields characters
    #   instead of integers.
    if not isinstance(input, bytearray):
      input = bytearray(input)

    bytes_ = bytearray()

    for i in range(0, len(input), 2):
      try:
        first, second = input[i:i+2]
      except ValueError:
        if errors == 'strict':
          raise with_context(
            exc = TrytesDecodeError(
              "'{name}' codec can't decode value; "
              "tryte sequence has odd length.".format(
                name = self.name,
              ),
            ),

            context = {
              'input': input,
            },
          )
        elif errors == 'replace':
          bytes_ += b'?'

        continue

      try:
        bytes_.append(
            self.index[first]
          + (self.index[second] * len(self.index))
        )
      except ValueError:
        # This combination of trytes yields a value > 255 when
        # decoded.  Naturally, we can't represent this using ASCII.
        if errors == 'strict':
          raise with_context(
            exc = TrytesDecodeError(
              "'{name}' codec can't decode trytes {pair} at position {i}-{j}: "
              "ordinal not in range(255)".format(
                name  = self.name,
                pair  = chr(first) + chr(second),
                i     = i,
                j     = i+1,
              ),
            ),

            context = {
              'input': input,
            }
          )
        elif errors == 'replace':
          bytes_ += b'?'

    return binary_type(bytes_), len(input)