forked from petertodd/python-bitcoinlib
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace hashlib.ripemd160 with python-only implemenation
The implementation is taken from Bitcoin Core's test framework Closes #64
- Loading branch information
Showing
5 changed files
with
184 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |