Exemplo n.º 1
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)
Exemplo n.º 2
0
    def squeeze(self, trits, offset=0, length=HASH_LENGTH):
        # type: (MutableSequence[int], Optional[int], Optional[int]) -> None
        """
        Squeeze trits from the sponge.

        :param trits:
            Sequence that the squeezed trits will be copied to.
            Note: this object will be modified!

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

        :param length:
            Number of trits to squeeze, default to ``HASH_LENGTH``
        """
        # Squeeze is kind of like the opposite of absorb; it copies
        # trits from internal state to the ``trits`` parameter, one hash
        # at a time, and transforming internal state in between hashes.
        #
        # However, only the first hash of the state is "public", so we
        # can simplify the implementation somewhat.

        # Ensure length can be mod by HASH_LENGTH
        if length % HASH_LENGTH != 0:
            raise with_context(
                exc=ValueError('Invalid length passed to ``squeeze`.'),
                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                })

        # Ensure that ``trits`` can hold at least one hash worth of
        # trits.
        trits.extend([0] * max(0, length - len(trits)))

        # Check trits with offset can handle hash length
        if len(trits) - offset < HASH_LENGTH:
            raise with_context(
                exc=ValueError('Invalid offset passed to ``squeeze``.'),
                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length
                },
            )

        while length >= HASH_LENGTH:
            # Copy exactly one hash.
            trits[offset:offset + HASH_LENGTH] = self._state[0:HASH_LENGTH]

            # One hash worth of trits copied; now transform.
            self._transform()

            offset += HASH_LENGTH
            length -= HASH_LENGTH
Exemplo n.º 3
0
    async 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 = (await 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)
Exemplo n.º 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)
Exemplo n.º 5
0
    async 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 = await self._execute(self.request)

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

        self.called = True

        return self.response
Exemplo n.º 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
Exemplo n.º 7
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
Exemplo n.º 8
0
    def __init__(self, uri, timeout=None, authentication=None):
        # type: (Union[Text, SplitResult], Optional[int]) -> None
        super(HttpAdapter, self).__init__()

        self.timeout = timeout
        self.authentication = authentication

        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
Exemplo n.º 9
0
    def __init__(
            self,
            trytes,  # type: TrytesCompatible
            balance=None,  # type: Optional[int]
            key_index=None,  # type: Optional[int]
            security_level=None,  # type: Optional[int]
    ):
        # type: (...) -> 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.
        Defaults to ``None``; usually set via the ``getInputs`` command.

        References:

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

        self.key_index = key_index
        """
        Index of the key used to generate this address.
        Defaults to ``None``; usually set via ``AddressGenerator``.

        References:

        - :py:class:`iota_async.crypto.addresses.AddressGenerator`
        """

        self.security_level = security_level
        """
Exemplo n.º 10
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_async.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)
Exemplo n.º 11
0
def convert_value_to_standard_unit(value, symbol='i'):
    # type: (Text, Text) -> float
    """
    Converts between any two standard units of iota_async.

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

    :param symbol:
        Unit symbol of iota_async 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)
Exemplo n.º 12
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
Exemplo n.º 13
0
    def __init__(self, trytes):
        # type: (TrytesCompatible) -> None
        super(Tag, 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,
                },
            )
Exemplo n.º 14
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
Exemplo n.º 15
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
Exemplo n.º 16
0
    def absorb(self, trits, offset=0, length=None):
        # type: (Sequence[int], Optional[int], Optional[int]) -> 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
Exemplo n.º 17
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,
                },
            )
Exemplo n.º 18
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()
Exemplo n.º 19
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
Exemplo n.º 20
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]
Exemplo n.º 21
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
Exemplo n.º 22
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,
             },
         )
Exemplo n.º 23
0
    async def _execute(self, request):
        transaction_hash = request['transaction']  # type: TransactionHash

        bundle = Bundle(await 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],
        }
Exemplo n.º 24
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
Exemplo n.º 25
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
Exemplo n.º 26
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,
            },
        )
Exemplo n.º 27
0
    def sign_input_transactions(self, bundle, start_index):
        # type: (Bundle, int) -> None
        """
        Signs the inputs starting at the specified index.

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

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

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

        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_async.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)
Exemplo n.º 28
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
Exemplo n.º 29
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)
Exemplo n.º 30
0
    def __init__(self, trytes, pad=None):
        # type: (TrytesCompatible, Optional[int]) -> None
        """
        :param trytes:
            Byte string or bytearray.

        :param pad:
            Ensure at least this many trytes.

            If there are too few, null trytes will be appended to the
            TryteString.

            .. note::
                If the TryteString is too long, it will *not* be
                truncated!
        """
        super(TryteString, self).__init__()

        if isinstance(trytes, (int, float)):
            raise with_context(
                exc=TypeError(
                    'Converting {type} is not supported; '
                    '{cls} is not a numeric type.'.format(
                        type=type(trytes).__name__,
                        cls=type(self).__name__,
                    ), ),
                context={
                    'trytes': trytes,
                },
            )

        if isinstance(trytes, TryteString):
            incoming_type = type(trytes)

            if ((incoming_type is TryteString)
                    or issubclass(incoming_type, type(self))):
                # Create a copy of the incoming TryteString's trytes, to
                # ensure we don't modify it when we apply padding.
                trytes = bytearray(trytes._trytes)

            else:
                raise with_context(
                    exc=TypeError(
                        '{cls} cannot be initialized from a(n) {type}.'.format(
                            type=type(trytes).__name__,
                            cls=type(self).__name__,
                        ), ),
                    context={
                        'trytes': trytes,
                    },
                )

        else:
            if isinstance(trytes, text_type):
                trytes = encode(trytes, 'ascii')

            if not isinstance(trytes, bytearray):
                trytes = bytearray(trytes)

            for i, ordinal in enumerate(trytes):
                if ordinal not in AsciiTrytesCodec.index:
                    raise with_context(
                        exc=ValueError(
                            'Invalid character {char!r} at position {i} '
                            '(expected A-Z or 9).'.format(
                                char=chr(ordinal),
                                i=i,
                            ), ),
                        context={
                            'trytes': trytes,
                        },
                    )

        if pad:
            trytes += b'9' * max(0, pad - len(trytes))

        self._trytes = trytes  # type: bytearray