ripemd160.py
1 # Copyright (c) 2021 Pieter Wuille 2 # Distributed under the MIT software license, see the accompanying 3 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 """Test-only pure Python RIPEMD160 implementation.""" 5 6 import unittest 7 8 # Message schedule indexes for the left path. 9 ML = [ 10 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 11 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 12 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 13 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 14 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 15 ] 16 17 # Message schedule indexes for the right path. 18 MR = [ 19 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 20 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 21 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 22 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 23 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 24 ] 25 26 # Rotation counts for the left path. 27 RL = [ 28 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 29 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 30 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 31 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 32 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 33 ] 34 35 # Rotation counts for the right path. 36 RR = [ 37 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 38 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 39 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 40 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 41 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 42 ] 43 44 # K constants for the left path. 45 KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] 46 47 # K constants for the right path. 48 KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] 49 50 51 def fi(x, y, z, i): 52 """The f1, f2, f3, f4, and f5 functions from the specification.""" 53 if i == 0: 54 return x ^ y ^ z 55 elif i == 1: 56 return (x & y) | (~x & z) 57 elif i == 2: 58 return (x | ~y) ^ z 59 elif i == 3: 60 return (x & z) | (y & ~z) 61 elif i == 4: 62 return x ^ (y | ~z) 63 else: 64 assert False 65 66 67 def rol(x, i): 68 """Rotate the bottom 32 bits of x left by i bits.""" 69 return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff 70 71 72 def compress(h0, h1, h2, h3, h4, block): 73 """Compress state (h0, h1, h2, h3, h4) with block.""" 74 # Left path variables. 75 al, bl, cl, dl, el = h0, h1, h2, h3, h4 76 # Right path variables. 77 ar, br, cr, dr, er = h0, h1, h2, h3, h4 78 # Message variables. 79 x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] 80 81 # Iterate over the 80 rounds of the compression. 82 for j in range(80): 83 rnd = j >> 4 84 # Perform left side of the transformation. 85 al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el 86 al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl 87 # Perform right side of the transformation. 88 ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er 89 ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr 90 91 # Compose old state, left transform, and right transform into new state. 92 return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr 93 94 95 def ripemd160(data): 96 """Compute the RIPEMD-160 hash of data.""" 97 # Initialize state. 98 state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) 99 # Process full 64-byte blocks in the input. 100 for b in range(len(data) >> 6): 101 state = compress(*state, data[64*b:64*(b+1)]) 102 # Construct final blocks (with padding and size). 103 pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) 104 fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') 105 # Process final blocks. 106 for b in range(len(fin) >> 6): 107 state = compress(*state, fin[64*b:64*(b+1)]) 108 # Produce output. 109 return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) 110 111 112 class TestFrameworkKey(unittest.TestCase): 113 def test_ripemd160(self): 114 """RIPEMD-160 test vectors.""" 115 # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html 116 for msg, hexout in [ 117 (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), 118 (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), 119 (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), 120 (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), 121 (b"abcdefghijklmnopqrstuvwxyz", 122 "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), 123 (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 124 "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), 125 (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 126 "b0e20b6e3116640286ed3a87a5713079b21f5189"), 127 (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), 128 (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") 129 ]: 130 self.assertEqual(ripemd160(msg).hex(), hexout)