Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """Difficulty: calculate or check the PoW difficulty (preceding null bits) of an input"""
- from hashlib import new
- from sys import argv
- def calculate(hash: bytes, bigendian: bool = True) -> int:
- """Calculate the difficulty (number of preceding null bits)."""
- # prepare variables
- number = int.from_bytes(hash, byteorder = 'big' if bigendian else 'little')
- length = len(hash)
- trailing_bits = 0
- # shift off bits until a null result is achieved
- while number != 0 and trailing_bits < length * 8:
- number = number >> 1
- trailing_bits += 1
- # difficulty achieved is the preceeding null bits
- return length * 8 - trailing_bits
- def calculate_linear(hash: bytes, bigendian: bool = True) -> int:
- """Calculates the difficulty as the expected average number of tries."""
- return 2**calculate(hash, bigendian)
- def calculate_query_bytes(target: bytes, hash_len: int) -> int:
- """Calculates the expected number of tries to find a hash containing the
- target sequence of bytes.
- """
- target_len = len(target)
- permutations = hash_len - target_len + 1
- return 2**(target_len*8)/permutations
- def check(diff: int, hash: bytes, bigendian: bool = True) -> bool:
- """Checks if a hash meets the minimum difficulty threshold (preceeding null bits)."""
- # prepare variables
- number = int.from_bytes(hash, byteorder='big' if bigendian else 'little')
- length = len(hash)
- # raise exception if difficulty is impossible to achieve
- if diff > length * 8:
- raise Exception('Difficulty cannot exceed bit length of input')
- # shift off all but {diff} preceding bits
- preceding_bits = number >> (length * 8 - diff)
- # it meets the difficulty if preceding bits are 0
- return preceding_bits == 0
- def license():
- """Copyleft (c) 2022 k98kurz
- Permission to use, copy, modify, and/or distribute this software
- for any purpose with or without fee is hereby granted, provided
- that the above copyleft notice and this permission notice appear in
- all copies.
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- """
- return license.__doc__
- def usage(name):
- print('Calculate difficulty of a given hash/value or check if it reaches a threshold.')
- print(f'usage: {name} [check:{{number}}|calc|linear|query] [hash:{{hashlib_algo}}|hex|str] [input_string] [bigendian=True]')
- print(f'\toperators:')
- print(f'\t\tcalc\t\tcalculate the number of preceding null bits of the given hash')
- print(f'\t\tlinear\t\tcalculate the average expected number of tries of the given hash')
- print(f'\t\tquery\t\tcalculate the average expected number of tries for a 32 byte hash containing the input_string')
- print(f'\tmodes:')
- print(f'\t\thash:{{hashlib_algo}}\thash the input_string before running the operation')
- print(f'\t\t\thash mode is not available for the query operation')
- print(f'\t\thex\t\tinterpret the input_string as hexadecimal')
- print(f'\t\tstr\t\tinterpret the input_string as a utf-8 string')
- print(f'\tbigendian\t(bool) the endianess for byte interpretation is big or little')
- print(f'\texamples:')
- print(f'\t\t`{name} check:8 hex 0001` checks if the hex string 0001 has 8 preceding null bits; should return `pass`')
- print(f'\t\t`{name} check:1 hash:sha256 1234` checks if the hex string 0001 has 8 preceding null bits; should return `pass`')
- print(f'\t\t`{name} calc hex 0f` calculates the number of preceding null bits in the hex string 0f; should return 4')
- 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')
- 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')
- def main(args):
- """Main function for invoking as cli tool."""
- if len(args) < 4:
- return usage(args[0])
- # parse arguments
- op = args[1].split(':')
- mode = args[2].split(':')
- input = args[3]
- bigendian = True if len(args) < 5 or args[4] in ('True', 'true', '1') else False
- if len(op) == 1:
- # calculate or query
- if op[0] in ('calc', 'linear'):
- if len(mode) == 1:
- # default mode, i.e. no hashing
- input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
- print(calculate_linear(input, bigendian) if op[0] == 'linear' else calculate(input, bigendian))
- else:
- # hashing mode
- if 'shake' in mode[1]:
- input = new(mode[1], bytes(input, 'utf-8')).digest(len(input))
- else:
- input = new(mode[1], bytes(input, 'utf-8')).digest()
- print(calculate_linear(input, bigendian) if op[0] == 'linear' else calculate(input, bigendian))
- elif op[0] == 'query':
- if len(mode) == 1:
- # default mode, i.e. no hashing
- input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
- print(calculate_query_bytes(input, 32))
- else:
- print('error: hashing mode not available for query operation')
- else:
- # difficulty checking op
- if len(mode) == 1:
- # default mode, i.e. no hashing
- input = bytearray.fromhex(input) if mode[0] == 'hex' else bytes(input, 'utf-8')
- print('pass' if check(int(op[1]), input, bigendian) else 'fail')
- else:
- # hashing mode
- if 'shake' in mode[1]:
- input = new(mode[1], bytes(input, 'utf-8')).digest(len(input))
- else:
- input = new(mode[1], bytes(input, 'utf-8')).digest()
- print('pass' if check(int(op[1]), input, bigendian) else 'fail')
- # cli
- if __name__ == '__main__':
- main(argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement