Search code examples
pythonpython-3.xhashpycrypto

Python3 Crypto.Hash - SHA digest always starts with binary 1


I am working on a project where I am trying to create a very simple blockchain-based cryptocurrency. This is an oversimplified version of the way I try to hash a block object (obviously the fields of the Block class are much more complicated, but this is the main idea):

from Crypto.Hash import SHA
import json
from collections import OrderedDict
from random import random

class Block(object):

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def to_dict(self):
        d = OrderedDict({
            'x' : self.x,
            'y' : self.y,
            'z' : self.z
        })
        return d

    def json(self):
        return json.dumps(self.to_dict())

    def hash(self):
        return SHA.new(self.json().encode('utf8')).hexdigest()

# driver code to test hashing
while True:
    x, y, z = [random() for _ in range(3)]
    b = Block(x, y, z)
    if not bin(int(b.hash(), 16)).startswith('0b1'):
        break

The above driver program loops forever. The problem is that (regardless of the number and/or value of the fields) the hash ALWAYS starts with 0b1, which messes with the whole idea of mining difficulty and proof-of-work. More importantly, though, this is not the expected behavior of a hashing function. What do I miss?


Solution

  • Python doesn't zero pad the front of binary numbers by default, so the first digit of any binary number will be one.

    >>> bin(1)
    '0b1'
    >>> bin(2)
    '0b10'
    >>> bin(3)
    '0b11'
    >>> bin(8)
    '0b1000'
    

    If you want fixed with binary strings, use string formatting

    >>> "{:04b}".format(1)
    '0001'
    >>> "{:04b}".format(2)
    '0010'
    >>> "{:04b}".format(8)
    '1000'
    >>> "{:04b}".format(15)
    '1111'
    

    Otherwise, just use a binary and (&) to check if the specific bit is set.

    >>> bool(1 & 0b1000)
    False
    >>> bool(3 & 0b1000)
    False
    >>> bool(8 & 0b1000)
    True
    >>> bool(15 & 0b1000)
    True