# Module `onionr.onionrproofs`

Onionr - Private P2P Communication

Proof of work module

Source code

    '''
        Onionr - Private P2P Communication

        Proof of work module
    '''
    '''
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
    '''
    import multiprocessing, time, math, threading, binascii, sys, json
    import nacl.encoding, nacl.hash, nacl.utils

    import config, logger
    from onionrblocks import onionrblockapi, storagecounter
    from onionrutils import bytesconverter
    from onionrcrypto import hashers
    config.reload()

    def getDifficultyModifier():
        '''returns the difficulty modifier for block storage based
        on a variety of factors, currently only disk use.
        '''
        percentUse = storagecounter.StorageCounter().get_percent()
        difficultyIncrease = math.floor(4 * percentUse) # difficulty increase is a step function

        return difficultyIncrease

    def getDifficultyForNewBlock(data):
        '''
        Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents
        '''
        if isinstance(data, onionrblockapi.Block):
            dataSizeInBytes = len(bytesconverter.str_to_bytes(data.getRaw()))
        else:
            dataSizeInBytes = len(bytesconverter.str_to_bytes(data))

        minDifficulty = config.get('general.minimum_send_pow', 4)
        totalDifficulty = max(minDifficulty, math.floor(dataSizeInBytes / 1000000.0)) + getDifficultyModifier()

        return totalDifficulty

        return retData

    def getHashDifficulty(h: str):
        '''
            Return the amount of leading zeroes in a hex hash string (hexHash)
        '''
        return len(h) - len(h.lstrip('0'))

    def hashMeetsDifficulty(hexHash):
        '''
            Return bool for a hash string to see if it meets pow difficulty defined in config
        '''
        hashDifficulty = getHashDifficulty(hexHash)

        try:
            expected = int(config.get('general.minimum_block_pow'))
        except TypeError:
            raise ValueError('Missing general.minimum_block_pow config')

        return hashDifficulty >= expected

    class DataPOW:
        def __init__(self, data, minDifficulty = 0, threadCount = 1):
            self.data = data
            self.threadCount = threadCount
            self.difficulty = max(minDifficulty, getDifficultyForNewBlock(data))
            self.rounds = 0
            self.hashing = False
            self.foundHash = False

            try:
                self.data = self.data.encode()
            except AttributeError:
                pass

            self.data = nacl.hash.blake2b(self.data)

            logger.info('Computing POW (difficulty: %s)...' % self.difficulty)

            self.mainHash = '0' * 70
            self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]

            for i in range(max(1, threadCount)):
                t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
                t.start()

        def pow(self, reporting = False):
            startTime = math.floor(time.time())
            self.hashing = True
            self.reporting = reporting
            iFound = False # if current thread is the one that found the answer

            while self.hashing:
                rand = nacl.utils.random()
                token = nacl.hash.blake2b(rand + self.data).decode()
                self.rounds += 1
                #print(token)
                if self.puzzle == token[0:self.difficulty]:
                    self.hashing = False
                    iFound = True
                    break

            if iFound:
                endTime = math.floor(time.time())
                if self.reporting:
                    logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
                    logger.debug('Round count: %s' % (self.rounds,))
                self.result = (token, rand)

        def shutdown(self):
            self.hashing = False
            self.puzzle = ''

        def changeDifficulty(self, newDiff):
            self.difficulty = newDiff

        def getResult(self):
            '''
                Returns the result then sets to false, useful to automatically clear the result
            '''

            try:
                retVal = self.result
            except AttributeError:
                retVal = False

            self.result = False
            return retVal

        def waitForResult(self):
            '''
                Returns the result only when it has been found, False if not running and not found
            '''
            result = False
            try:
                while True:
                    result = self.getResult()
                    if not self.hashing:
                        break
                    else:
                        time.sleep(1)
            except KeyboardInterrupt:
                self.shutdown()
                logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
            return result

    class POW:
        def __init__(self, metadata, data, threadCount = 1, minDifficulty=0):
            self.foundHash = False
            self.difficulty = 0
            self.data = data
            self.metadata = metadata
            self.threadCount = threadCount
            self.hashing = False

            json_metadata = json.dumps(metadata).encode()

            try:
                self.data = self.data.encode()
            except AttributeError:
                pass

            if minDifficulty > 0:
                self.difficulty = minDifficulty
            else:
                # Calculate difficulty. Dumb for now, may use good algorithm in the future.
                self.difficulty = getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))

            logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))

            self.mainHash = '0' * 64
            self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]

            for i in range(max(1, threadCount)):
                t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
                t.start()

        def pow(self, reporting = False):
            startTime = math.floor(time.time())
            self.hashing = True
            self.reporting = reporting
            iFound = False # if current thread is the one that found the answer
            nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
            while self.hashing:
                #token = nacl.hash.blake2b(rand + self.data).decode()
                self.metadata['pow'] = nonce
                payload = json.dumps(self.metadata).encode() + b'\n' + self.data
                token = hashers.sha3_hash(payload)
                try:
                    # on some versions, token is bytes
                    token = token.decode()
                except AttributeError:
                    pass
                if self.puzzle == token[0:self.difficulty]:
                    self.hashing = False
                    iFound = True
                    self.result = payload
                    break
                nonce += 1

            if iFound:
                endTime = math.floor(time.time())
                if self.reporting:
                    logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)

        def shutdown(self):
            self.hashing = False
            self.puzzle = ''

        def changeDifficulty(self, newDiff):
            self.difficulty = newDiff

        def getResult(self):
            '''
                Returns the result then sets to false, useful to automatically clear the result
            '''

            try:
                retVal = self.result
            except AttributeError:
                retVal = False

            self.result = False
            return retVal

        def waitForResult(self):
            '''
                Returns the result only when it has been found, False if not running and not found
            '''
            result = False
            try:
                while True:
                    result = self.getResult()
                    if not self.hashing:
                        break
                    else:
                        time.sleep(1)
            except KeyboardInterrupt:
                self.shutdown()
                logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
            return result

## Sub-modules

`onionr.onionrproofs.subprocesspow`

  Onionr - Private P2P Communication …

## Functions

`def getDifficultyForNewBlock(data)`

  Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents

  Source code

      def getDifficultyForNewBlock(data):
          '''
          Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents
          '''
          if isinstance(data, onionrblockapi.Block):
              dataSizeInBytes = len(bytesconverter.str_to_bytes(data.getRaw()))
          else:
              dataSizeInBytes = len(bytesconverter.str_to_bytes(data))

          minDifficulty = config.get('general.minimum_send_pow', 4)
          totalDifficulty = max(minDifficulty, math.floor(dataSizeInBytes / 1000000.0)) + getDifficultyModifier()

          return totalDifficulty

          return retData

`def getDifficultyModifier()`

  returns the difficulty modifier for block storage based on a variety of factors, currently only disk use.

  Source code

      def getDifficultyModifier():
          '''returns the difficulty modifier for block storage based
          on a variety of factors, currently only disk use.
          '''
          percentUse = storagecounter.StorageCounter().get_percent()
          difficultyIncrease = math.floor(4 * percentUse) # difficulty increase is a step function

          return difficultyIncrease

`def getHashDifficulty(h)`

  Return the amount of leading zeroes in a hex hash string (hexHash)

  Source code

      def getHashDifficulty(h: str):
          '''
              Return the amount of leading zeroes in a hex hash string (hexHash)
          '''
          return len(h) - len(h.lstrip('0'))

`def hashMeetsDifficulty(hexHash)`

  Return bool for a hash string to see if it meets pow difficulty defined in config

  Source code

      def hashMeetsDifficulty(hexHash):
          '''
              Return bool for a hash string to see if it meets pow difficulty defined in config
          '''
          hashDifficulty = getHashDifficulty(hexHash)

          try:
              expected = int(config.get('general.minimum_block_pow'))
          except TypeError:
              raise ValueError('Missing general.minimum_block_pow config')

          return hashDifficulty >= expected

## Classes

`class DataPOW (data, minDifficulty=0, threadCount=1)`
  Source code

      class DataPOW:
          def __init__(self, data, minDifficulty = 0, threadCount = 1):
              self.data = data
              self.threadCount = threadCount
              self.difficulty = max(minDifficulty, getDifficultyForNewBlock(data))
              self.rounds = 0
              self.hashing = False
              self.foundHash = False

              try:
                  self.data = self.data.encode()
              except AttributeError:
                  pass

              self.data = nacl.hash.blake2b(self.data)

              logger.info('Computing POW (difficulty: %s)...' % self.difficulty)

              self.mainHash = '0' * 70
              self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]

              for i in range(max(1, threadCount)):
                  t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
                  t.start()

          def pow(self, reporting = False):
              startTime = math.floor(time.time())
              self.hashing = True
              self.reporting = reporting
              iFound = False # if current thread is the one that found the answer

              while self.hashing:
                  rand = nacl.utils.random()
                  token = nacl.hash.blake2b(rand + self.data).decode()
                  self.rounds += 1
                  #print(token)
                  if self.puzzle == token[0:self.difficulty]:
                      self.hashing = False
                      iFound = True
                      break

              if iFound:
                  endTime = math.floor(time.time())
                  if self.reporting:
                      logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
                      logger.debug('Round count: %s' % (self.rounds,))
                  self.result = (token, rand)

          def shutdown(self):
              self.hashing = False
              self.puzzle = ''

          def changeDifficulty(self, newDiff):
              self.difficulty = newDiff

          def getResult(self):
              '''
                  Returns the result then sets to false, useful to automatically clear the result
              '''

              try:
                  retVal = self.result
              except AttributeError:
                  retVal = False

              self.result = False
              return retVal

          def waitForResult(self):
              '''
                  Returns the result only when it has been found, False if not running and not found
              '''
              result = False
              try:
                  while True:
                      result = self.getResult()
                      if not self.hashing:
                          break
                      else:
                          time.sleep(1)
              except KeyboardInterrupt:
                  self.shutdown()
                  logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
              return result

  ### Methods

  `def changeDifficulty(self, newDiff)`
    Source code

        def changeDifficulty(self, newDiff):
            self.difficulty = newDiff

  `def getResult(self)`

    Returns the result then sets to false, useful to automatically clear the result

    Source code

        def getResult(self):
            '''
                Returns the result then sets to false, useful to automatically clear the result
            '''

            try:
                retVal = self.result
            except AttributeError:
                retVal = False

            self.result = False
            return retVal

  `def pow(self, reporting=False)`
    Source code

        def pow(self, reporting = False):
            startTime = math.floor(time.time())
            self.hashing = True
            self.reporting = reporting
            iFound = False # if current thread is the one that found the answer

            while self.hashing:
                rand = nacl.utils.random()
                token = nacl.hash.blake2b(rand + self.data).decode()
                self.rounds += 1
                #print(token)
                if self.puzzle == token[0:self.difficulty]:
                    self.hashing = False
                    iFound = True
                    break

            if iFound:
                endTime = math.floor(time.time())
                if self.reporting:
                    logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)
                    logger.debug('Round count: %s' % (self.rounds,))
                self.result = (token, rand)

  `def shutdown(self)`
    Source code

        def shutdown(self):
            self.hashing = False
            self.puzzle = ''

  `def waitForResult(self)`

    Returns the result only when it has been found, False if not running and not found

    Source code

        def waitForResult(self):
            '''
                Returns the result only when it has been found, False if not running and not found
            '''
            result = False
            try:
                while True:
                    result = self.getResult()
                    if not self.hashing:
                        break
                    else:
                        time.sleep(1)
            except KeyboardInterrupt:
                self.shutdown()
                logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
            return result

`class POW (metadata, data, threadCount=1, minDifficulty=0)`
  Source code

      class POW:
          def __init__(self, metadata, data, threadCount = 1, minDifficulty=0):
              self.foundHash = False
              self.difficulty = 0
              self.data = data
              self.metadata = metadata
              self.threadCount = threadCount
              self.hashing = False

              json_metadata = json.dumps(metadata).encode()

              try:
                  self.data = self.data.encode()
              except AttributeError:
                  pass

              if minDifficulty > 0:
                  self.difficulty = minDifficulty
              else:
                  # Calculate difficulty. Dumb for now, may use good algorithm in the future.
                  self.difficulty = getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data))

              logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))

              self.mainHash = '0' * 64
              self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))]

              for i in range(max(1, threadCount)):
                  t = threading.Thread(name = 'thread%s' % i, target = self.pow, args = (True,))
                  t.start()

          def pow(self, reporting = False):
              startTime = math.floor(time.time())
              self.hashing = True
              self.reporting = reporting
              iFound = False # if current thread is the one that found the answer
              nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
              while self.hashing:
                  #token = nacl.hash.blake2b(rand + self.data).decode()
                  self.metadata['pow'] = nonce
                  payload = json.dumps(self.metadata).encode() + b'\n' + self.data
                  token = hashers.sha3_hash(payload)
                  try:
                      # on some versions, token is bytes
                      token = token.decode()
                  except AttributeError:
                      pass
                  if self.puzzle == token[0:self.difficulty]:
                      self.hashing = False
                      iFound = True
                      self.result = payload
                      break
                  nonce += 1

              if iFound:
                  endTime = math.floor(time.time())
                  if self.reporting:
                      logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)

          def shutdown(self):
              self.hashing = False
              self.puzzle = ''

          def changeDifficulty(self, newDiff):
              self.difficulty = newDiff

          def getResult(self):
              '''
                  Returns the result then sets to false, useful to automatically clear the result
              '''

              try:
                  retVal = self.result
              except AttributeError:
                  retVal = False

              self.result = False
              return retVal

          def waitForResult(self):
              '''
                  Returns the result only when it has been found, False if not running and not found
              '''
              result = False
              try:
                  while True:
                      result = self.getResult()
                      if not self.hashing:
                          break
                      else:
                          time.sleep(1)
              except KeyboardInterrupt:
                  self.shutdown()
                  logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
              return result

  ### Methods

  `def changeDifficulty(self, newDiff)`
    Source code

        def changeDifficulty(self, newDiff):
            self.difficulty = newDiff

  `def getResult(self)`

    Returns the result then sets to false, useful to automatically clear the result

    Source code

        def getResult(self):
            '''
                Returns the result then sets to false, useful to automatically clear the result
            '''

            try:
                retVal = self.result
            except AttributeError:
                retVal = False

            self.result = False
            return retVal

  `def pow(self, reporting=False)`
    Source code

        def pow(self, reporting = False):
            startTime = math.floor(time.time())
            self.hashing = True
            self.reporting = reporting
            iFound = False # if current thread is the one that found the answer
            nonce = int(binascii.hexlify(nacl.utils.random(64)), 16)
            while self.hashing:
                #token = nacl.hash.blake2b(rand + self.data).decode()
                self.metadata['pow'] = nonce
                payload = json.dumps(self.metadata).encode() + b'\n' + self.data
                token = hashers.sha3_hash(payload)
                try:
                    # on some versions, token is bytes
                    token = token.decode()
                except AttributeError:
                    pass
                if self.puzzle == token[0:self.difficulty]:
                    self.hashing = False
                    iFound = True
                    self.result = payload
                    break
                nonce += 1

            if iFound:
                endTime = math.floor(time.time())
                if self.reporting:
                    logger.debug('Found token after %s seconds: %s' % (endTime - startTime, token), timestamp=True)

  `def shutdown(self)`
    Source code

        def shutdown(self):
            self.hashing = False
            self.puzzle = ''

  `def waitForResult(self)`

    Returns the result only when it has been found, False if not running and not found

    Source code

        def waitForResult(self):
            '''
                Returns the result only when it has been found, False if not running and not found
            '''
            result = False
            try:
                while True:
                    result = self.getResult()
                    if not self.hashing:
                        break
                    else:
                        time.sleep(1)
            except KeyboardInterrupt:
                self.shutdown()
                logger.warn('Got keyboard interrupt while waiting for POW result, stopping')
            return result