Advertisement
k98kurz

difficulty.py

Jan 11th, 2022
1,214
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.41 KB | None | 0 0
  1. """Difficulty: calculate or check the PoW difficulty (preceding null bits) of an input"""
  2.  
  3. from hashlib import new
  4. from sys import argv
  5.  
  6.  
  7. def calculate(hash: bytes, bigendian: bool = True) -> int:
  8.     """Calculate the difficulty (number of preceding null bits)."""
  9.     # prepare variables
  10.     number = int.from_bytes(hash, byteorder = 'big' if bigendian else 'little')
  11.     length = len(hash)
  12.     trailing_bits = 0
  13.  
  14.     # shift off bits until a null result is achieved
  15.     while number != 0 and trailing_bits < length * 8:
  16.         number = number >> 1
  17.         trailing_bits += 1
  18.  
  19.     # difficulty achieved is the preceeding null bits
  20.     return length * 8 - trailing_bits
  21.  
  22. def calculate_linear(hash: bytes, bigendian: bool = True) -> int:
  23.     """Calculates the difficulty as the expected average number of tries."""
  24.     return 2**calculate(hash, bigendian)
  25.  
  26. def calculate_query_bytes(target: bytes, hash_len: int) -> int:
  27.     """Calculates the expected number of tries to find a hash containing the
  28.        target sequence of bytes.
  29.    """
  30.     target_len = len(target)
  31.     permutations = hash_len - target_len + 1
  32.     return 2**(target_len*8)/permutations
  33.  
  34. def check(diff: int, hash: bytes, bigendian: bool = True) -> bool:
  35.     """Checks if a hash meets the minimum difficulty threshold (preceeding null bits)."""
  36.     # prepare variables
  37.     number = int.from_bytes(hash, byteorder='big' if bigendian else 'little')
  38.     length = len(hash)
  39.  
  40.     # raise exception if difficulty is impossible to achieve
  41.     if diff > length * 8:
  42.         raise Exception('Difficulty cannot exceed bit length of input')
  43.  
  44.     # shift off all but {diff} preceding bits
  45.     preceding_bits = number >> (length * 8 - diff)
  46.  
  47.     # it meets the difficulty if preceding bits are 0
  48.     return preceding_bits == 0
  49.  
  50.  
  51. def license():
  52.     """Copyleft (c) 2022 k98kurz
  53.  
  54.        Permission to use, copy, modify, and/or distribute this software
  55.        for any purpose with or without fee is hereby granted, provided
  56.        that the above copyleft notice and this permission notice appear in
  57.        all copies.
  58.  
  59.        THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  60.        WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  61.        WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  62.        AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  63.        CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  64.        OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  65.        NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  66.        CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  67.    """
  68.     return license.__doc__
  69.  
  70.  
  71. def usage(name):
  72.     print('Calculate difficulty of a given hash/value or check if it reaches a threshold.')
  73.     print(f'usage: {name} [check:{{number}}|calc|linear|query] [hash:{{hashlib_algo}}|hex|str] [input_string] [bigendian=True]')
  74.     print(f'\toperators:')
  75.     print(f'\t\tcalc\t\tcalculate the number of preceding null bits of the given hash')
  76.     print(f'\t\tlinear\t\tcalculate the average expected number of tries of the given hash')
  77.     print(f'\t\tquery\t\tcalculate the average expected number of tries for a 32 byte hash containing the input_string')
  78.     print(f'\tmodes:')
  79.     print(f'\t\thash:{{hashlib_algo}}\thash the input_string before running the operation')
  80.     print(f'\t\t\thash mode is not available for the query operation')
  81.     print(f'\t\thex\t\tinterpret the input_string as hexadecimal')
  82.     print(f'\t\tstr\t\tinterpret the input_string as a utf-8 string')
  83.     print(f'\tbigendian\t(bool) the endianess for byte interpretation is big or little')
  84.     print(f'\texamples:')
  85.     print(f'\t\t`{name} check:8 hex 0001` checks if the hex string 0001 has 8 preceding null bits; should return `pass`')
  86.     print(f'\t\t`{name} check:1 hash:sha256 1234` checks if the hex string 0001 has 8 preceding null bits; should return `pass`')
  87.     print(f'\t\t`{name} calc hex 0f` calculates the number of preceding null bits in the hex string 0f; should return 4')
  88.     print(f'\t\t`{name} linear hex 0f` calculates the expected number of tries to get the number of preceding null bits in the hex string 0f; should return 16')
  89.     print(f'\t\t`{name} query str X` calculates the expected number of tries to find a hash containing the hex string 0f; should return 8.0')
  90.  
  91.  
  92. def main(args):
  93.     """Main function for invoking as cli tool."""
  94.     if len(args) < 4:
  95.         return usage(args[0])
  96.  
  97.     # parse arguments
  98.     op = args[1].split(':')
  99.     mode = args[2].split(':')
  100.     input = args[3]
  101.     bigendian = True if len(args) < 5 or args[4] in ('True', 'true', '1') else False
  102.  
  103.     if len(op) == 1:
  104.         # calculate or query
  105.         if op[0] in ('calc', 'linear'):
  106.             if len(mode) == 1:
  107.                 # default mode, i.e. no hashing
  108.                 input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
  109.                 print(calculate_linear(input, bigendian) if op[0] == 'linear' else calculate(input, bigendian))
  110.             else:
  111.                 # hashing mode
  112.                 if 'shake' in mode[1]:
  113.                     input = new(mode[1], bytes(input, 'utf-8')).digest(len(input))
  114.                 else:
  115.                     input = new(mode[1], bytes(input, 'utf-8')).digest()
  116.                 print(calculate_linear(input, bigendian) if op[0] == 'linear' else calculate(input, bigendian))
  117.         elif op[0] == 'query':
  118.             if len(mode) == 1:
  119.                 # default mode, i.e. no hashing
  120.                 input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
  121.                 print(calculate_query_bytes(input, 32))
  122.             else:
  123.                 print('error: hashing mode not available for query operation')
  124.     else:
  125.         # difficulty checking op
  126.         if len(mode) == 1:
  127.             # default mode, i.e. no hashing
  128.             input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
  129.             print('pass' if check(int(op[1]), input, bigendian) else 'fail')
  130.         else:
  131.             # hashing mode
  132.             if 'shake' in mode[1]:
  133.                 input = new(mode[1], bytes(input, 'utf-8')).digest(len(input))
  134.             else:
  135.                 input = new(mode[1], bytes(input, 'utf-8')).digest()
  136.             print('pass' if check(int(op[1]), input, bigendian) else 'fail')
  137.  
  138.  
  139. # cli
  140. if __name__ == '__main__':
  141.     main(argv)
  142.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement