Example #1
0
    def __init__(
            self,
            seed: Seed,
            start: int,
            step: int,
            security_level: 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
Example #2
0
class KeyGenerator(object):
  """
  Generates signing keys for messages.
  """
  def __init__(self, seed):
    # type: (TrytesCompatible) -> None
    super(KeyGenerator, self).__init__()

    self.seed = Seed(seed)

  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

  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

  def _create_sponge(self, index):
    # type: (int) -> Curl
    """
    Prepares the Curl sponge for the generator.
    """
    seed = self.seed.as_trits() # type: MutableSequence[int]

    for i in range(index):
      # Treat ``seed`` like a really big number and add ``index``.
      # Note that addition works a little bit differently in balanced
      # ternary.
      for j in range(len(seed)):
        seed[j] += 1

        if seed[j] > 1:
          seed[j] = -1
        else:
          break

    sponge = Curl()
    sponge.absorb(seed)

    # Squeeze all of the trits out of the sponge and re-absorb them.
    # Note that Curl transforms several times per operation, so this
    # sequence is not as redundant as it looks at first glance.
    sponge.squeeze(seed)
    sponge.reset()
    sponge.absorb(seed)

    return sponge