Skip to content

Commit

Permalink
Replace hashlib.ripemd160 with python-only implemenation
Browse files Browse the repository at this point in the history
The implementation is taken from Bitcoin Core's test framework

Closes #64
  • Loading branch information
dgpv committed Jan 11, 2022
1 parent 1654677 commit 2ff6739
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 10 deletions.
134 changes: 134 additions & 0 deletions bitcointx/core/_ripemd160.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Copyright (c) 2021 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
# Type annotations added by Dmitry Petukhov
#
# The code is not constant-time! This should NOT be used for working with
# secret data, such as, for example building a MAC (message authentication
# code), etc.

"""
Pure Python RIPEMD160 implementation.
The code is taken from Bitcoin Core's test framework.
This is needed because the openssl has deprecated ripemd160 algorithm
and it might not be available from hashlib anymore.
Runtime performance will be slow, but the alternative is having a compiled
dependency, which is too heavy.
IMPORTANT: code is not constant-time! This should NOT be used for working
with secret data, such as, for example building a MAC (message
authentication code), etc.
"""

from typing import Tuple

# Message schedule indexes for the left path.
ML = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
]

# Message schedule indexes for the right path.
MR = [
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
]

# Rotation counts for the left path.
RL = [
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
]

# Rotation counts for the right path.
RR = [
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
]

# K constants for the left path.
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]

# K constants for the right path.
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]


def fi(x: int, y: int, z: int, i: int) -> int:
"""The f1, f2, f3, f4, and f5 functions from the specification."""
if i == 0:
return x ^ y ^ z
elif i == 1:
return (x & y) | (~x & z)
elif i == 2:
return (x | ~y) ^ z
elif i == 3:
return (x & z) | (y & ~z)
elif i == 4:
return x ^ (y | ~z)
else:
assert False


def rol(x: int, i: int) -> int:
"""Rotate the bottom 32 bits of x left by i bits."""
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff


def compress(h0: int, h1: int, h2: int, h3: int, h4: int, block: bytes
) -> Tuple[int, int, int, int, int]:
"""Compress state (h0, h1, h2, h3, h4) with block."""
# Left path variables.
al, bl, cl, dl, el = h0, h1, h2, h3, h4
# Right path variables.
ar, br, cr, dr, er = h0, h1, h2, h3, h4
# Message variables.
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]

# Iterate over the 80 rounds of the compression.
for j in range(80):
rnd = j >> 4
# Perform left side of the transformation.
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
# Perform right side of the transformation.
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr

# Compose old state, left transform, and right transform into new state.
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr


def ripemd160(data: bytes) -> bytes:
"""
Compute the RIPEMD-160 hash of data.
The code is not constant-time! This should NOT be used for working with
secret data, such as, for example building a MAC (message authentication
code), etc.
"""
# Initialize state.
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
# Process full 64-byte blocks in the input.
for b in range(len(data) >> 6):
state = compress(*state, data[64*b:64*(b+1)])
# Construct final blocks (with padding and size).
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
# Process final blocks.
for b in range(len(fin) >> 6):
state = compress(*state, fin[64*b:64*(b+1)])
# Produce output.
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
6 changes: 2 additions & 4 deletions bitcointx/core/scripteval.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import bitcointx.core._bignum
import bitcointx.core.key
import bitcointx.core.serialize
import bitcointx.core._ripemd160

from bitcointx.util import ensure_isinstance

Expand Down Expand Up @@ -1012,10 +1013,7 @@ def check_args(n: int) -> None:

elif sop == OP_RIPEMD160:
check_args(1)

h = hashlib.new('ripemd160')
h.update(stack.pop())
stack.append(h.digest())
stack.append(bitcointx.core._ripemd160.ripemd160(stack.pop()))

elif sop == OP_ROT:
check_args(3)
Expand Down
5 changes: 2 additions & 3 deletions bitcointx/core/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
List, Tuple, Sequence, Union, TypeVar, Type, Generic, Any, Optional, cast
)
from ..util import ensure_isinstance
from ._ripemd160 import ripemd160

from io import BytesIO

Expand All @@ -48,9 +49,7 @@ def Hash(msg: Union[bytes, bytearray]) -> bytes:

def Hash160(msg: Union[bytes, bytearray]) -> bytes:
"""RIPEME160(SHA256(msg)) -> bytes"""
h = hashlib.new('ripemd160')
h.update(hashlib.sha256(msg).digest())
return h.digest()
return ripemd160(hashlib.sha256(msg).digest())


class SerializationError(Exception):
Expand Down
19 changes: 16 additions & 3 deletions bitcointx/core/sha256.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
# Original C++ code was Copyright (c) 2014-2017 The Bitcoin Core developers
# Original C++ code was licensed under MIT software license.

# This is needed for midstate SHA256, that is not available
# from hashlib.sha256. Runtime performance will be slow,
# but oftentimes this is acceptable.
"""
This is needed for midstate SHA256, that is not available
from hashlib.sha256. Runtime performance will be slow, but oftentimes this
is acceptable. IMPORTANT: code is not constant-time! This should NOT be used
for working with # secret data, such as, for example building a MAC (message
authentication code), etc.
"""

# pylama:ignore=E501

Expand Down Expand Up @@ -68,6 +72,15 @@ def ReadBE32(buf: bytes) -> int:


class CSHA256():
"""
This class provides access to SHA256 routines, with access to
SHA256 midstate (which is not available from hashlib.sha256)
The code is not constant-time! This should NOT be used for working with
secret data, such as, for example building a MAC (message authentication
code), etc.
"""

__slots__ = ['s', 'buf', 'bytes_count']

buf: bytes
Expand Down
30 changes: 30 additions & 0 deletions bitcointx/tests/test_ripemd160.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

import unittest
from bitcointx.core._ripemd160 import ripemd160


class Test_RIPEMD160(unittest.TestCase):
def test_ripemd160(self) -> None:
"""RIPEMD-160 test vectors."""
# See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
#
# The following code is Copyright (c) 2021 Pieter Wuille
# (taken from test/functional/test_framework/ripemd160.py
# in Bitcoin Core source)
for msg, hexout in [
(b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
(b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
(b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
(b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
(b"abcdefghijklmnopqrstuvwxyz",
"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"b0e20b6e3116640286ed3a87a5713079b21f5189"),
(b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
(b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
]:
self.assertEqual(ripemd160(msg).hex(), hexout)

0 comments on commit 2ff6739

Please sign in to comment.