diff --git a/README.md b/README.md index 6c73c15..581cb64 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,11 @@ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/200b67d3924c40e38479efdcd9b04aac)](https://www.codacy.com/gh/reverse-hash/understanding-bitcoin/dashboard?utm_source=github.com&utm_medium=referral&utm_content=reverse-hash/understanding-bitcoin&utm_campaign=Badge_Grade) ![test workflow](https://github.com/reverse-hash/understanding-bitcoin/actions/workflows/tests.yml/badge.svg) ![format workflow](https://github.com/reverse-hash/understanding-bitcoin/actions/workflows/format.yml/badge.svg) -![coverage workflow](https://github.com/reverse-hash/understanding-bitcoin/actions/workflows/coverage.yml/badge.svg) \ No newline at end of file +![coverage workflow](https://github.com/reverse-hash/understanding-bitcoin/actions/workflows/coverage.yml/badge.svg) + +## First steps + +```commandline +python -m pip install --upgrade pip +pip install -r requirements.txt +``` \ No newline at end of file diff --git a/understandingbitcoin/common/bit.py b/understandingbitcoin/common/bit.py index 231f87d..6b0c89c 100644 --- a/understandingbitcoin/common/bit.py +++ b/understandingbitcoin/common/bit.py @@ -25,7 +25,8 @@ def from_bytes(cls, byte_value: bytes) -> BitStream: :param byte_value: The array of bytes to convert :return: The binary sequence representation """ - return BitStream.from_unsigned_int(int.from_bytes(byte_value, byteorder='big')) + return BitStream.from_unsigned_int(int.from_bytes(byte_value, + byteorder='big')) @classmethod def from_char(cls, char: str) -> BitStream: @@ -142,9 +143,11 @@ def __add__(self, other: BitStream | str | int) -> BitStream: if isinstance(other, (BitStream, str)): result: int = int(str(self) or self.BIT_0, 2) \ + int(str(other) or self.BIT_0, 2) - return BitStream.from_unsigned_int(result, max(len(self), len(other))) + return BitStream.from_unsigned_int(result, max(len(self), + len(other))) - return BitStream.from_unsigned_int(int(self._value or self.BIT_0, 2) + other) + return BitStream.from_unsigned_int(int(self._value or self.BIT_0, 2) + + other) def __and__(self, other: BitStream): """ diff --git a/understandingbitcoin/hash/sha256.py b/understandingbitcoin/hash/sha256.py index 93ef97c..3803df5 100644 --- a/understandingbitcoin/hash/sha256.py +++ b/understandingbitcoin/hash/sha256.py @@ -66,9 +66,11 @@ def hash(cls, message: bytes) -> bytes: # words words: tuple[64] = cls._expand_block(block) - # words are processed through a series of rounds and hash values - # are updated using the output of each block - cls._compute_hash(hash_values, words) + # words are processed through a series of rounds + block_output: tuple[8] = cls._compress_words(hash_values, words) + + # hash values are updated using the output of each block + cls._update_hash(hash_values, block_output) # final hash output is generated once all blocks of the message have # been processed @@ -125,10 +127,11 @@ def _init_hash(cls) -> list[8]: @classmethod def _expand_block(cls, block: ByteBuffer) -> tuple[64]: - # create a 64 entry list of 64 i + # create a 64 entry list words: list[64] = [None] * 64 # w[0..15] is a copy of the block + i: int for i in range(16): words[i] = block.get_word32() @@ -140,7 +143,7 @@ def _expand_block(cls, block: ByteBuffer) -> tuple[64]: return tuple(words) @classmethod - def _compute_hash(cls, hash_values: list[8], words: tuple[64]) -> None: + def _compress_words(cls, hash_values: list[8], words: tuple[64]) -> tuple: # unpack and copy current hash values (a, b, c, d, e, f, g, h) = hash_values @@ -157,24 +160,7 @@ def _compute_hash(cls, hash_values: list[8], words: tuple[64]) -> None: b = a a = (t1 + t2).mod(cls._WORD_SIZE_BITS) - # update hash values with the compressed chunk - hash_values[0] = (hash_values[0] + a).mod(cls._WORD_SIZE_BITS) - hash_values[1] = (hash_values[1] + b).mod(cls._WORD_SIZE_BITS) - hash_values[2] = (hash_values[2] + c).mod(cls._WORD_SIZE_BITS) - hash_values[3] = (hash_values[3] + d).mod(cls._WORD_SIZE_BITS) - hash_values[4] = (hash_values[4] + e).mod(cls._WORD_SIZE_BITS) - hash_values[5] = (hash_values[5] + f).mod(cls._WORD_SIZE_BITS) - hash_values[6] = (hash_values[6] + g).mod(cls._WORD_SIZE_BITS) - hash_values[7] = (hash_values[7] + h).mod(cls._WORD_SIZE_BITS) - - @staticmethod - def _generate_digest(hash_values: list[8]) -> bytes: - digest: ByteBuffer = ByteBuffer() - value: BitStream - for value in hash_values: - digest.put_word32(value) - - return digest.bytes() + return a, b, c, d, e, f, g, h @staticmethod def _σ0(x: BitStream) -> BitStream: @@ -199,3 +185,26 @@ def _choice(x: BitStream, y: BitStream, z: BitStream) -> BitStream: @staticmethod def _majority(x: BitStream, y: BitStream, z: BitStream) -> BitStream: return (x & y) ^ (x & z) ^ (y & z) + + @classmethod + def _update_hash(cls, hash_values: list[8], block_output: tuple): + (a, b, c, d, e, f, g, h) = block_output + + # update hash values with the compressed chuck + hash_values[0] = (hash_values[0] + a).mod(cls._WORD_SIZE_BITS) + hash_values[1] = (hash_values[1] + b).mod(cls._WORD_SIZE_BITS) + hash_values[2] = (hash_values[2] + c).mod(cls._WORD_SIZE_BITS) + hash_values[3] = (hash_values[3] + d).mod(cls._WORD_SIZE_BITS) + hash_values[4] = (hash_values[4] + e).mod(cls._WORD_SIZE_BITS) + hash_values[5] = (hash_values[5] + f).mod(cls._WORD_SIZE_BITS) + hash_values[6] = (hash_values[6] + g).mod(cls._WORD_SIZE_BITS) + hash_values[7] = (hash_values[7] + h).mod(cls._WORD_SIZE_BITS) + + @staticmethod + def _generate_digest(hash_values: list[8]) -> bytes: + digest: ByteBuffer = ByteBuffer(order=ByteOrder.BIG_ENDIAN) + value: BitStream + for value in hash_values: + digest.put_word32(value) + + return digest.bytes()