ripemd160.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. #
  5. # Source: https://github.com/bitcoin/bitcoin/blob/master/test/functional/test_framework/ripemd160.py
  6. #
  7. # Adapted for the MMGen Project with the following changes:
  8. # - replace leading spaces with tabs
  9. # - reimplement as class with digest() and hexdigest() methods
  10. # - add custom test output display
  11. #
  12. # SECURITY DISCLAIMER:
  13. # This code has been flagged as ‘test-only’ by its creator because it is not
  14. # constant-time and therefore vulnerable to timing attacks.
  15. #
  16. # The RIPEMD-160 algorithm is used in the MMGen code base in two places: a)
  17. # in hash160(); and b) as a checksum for a config file template. Here
  18. # timing attacks are not meaningful, because in the first case the preimage
  19. # is a secure cryptographic hash and in the second -- public data.
  20. #
  21. # The MMGen Project assumes no responsibility for the use of this code in
  22. # other contexts.
  23. """Test-only pure Python RIPEMD160 implementation."""
  24. # Message schedule indexes for the left path.
  25. ML = [
  26. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
  27. 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
  28. 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
  29. 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
  30. 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
  31. ]
  32. # Message schedule indexes for the right path.
  33. MR = [
  34. 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
  35. 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
  36. 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
  37. 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
  38. 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
  39. ]
  40. # Rotation counts for the left path.
  41. RL = [
  42. 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
  43. 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
  44. 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
  45. 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
  46. 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
  47. ]
  48. # Rotation counts for the right path.
  49. RR = [
  50. 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
  51. 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
  52. 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
  53. 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
  54. 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
  55. ]
  56. # K constants for the left path.
  57. KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]
  58. # K constants for the right path.
  59. KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]
  60. def fi(x, y, z, i):
  61. """The f1, f2, f3, f4, and f5 functions from the specification."""
  62. if i == 0:
  63. return x ^ y ^ z
  64. elif i == 1:
  65. return (x & y) | (~x & z)
  66. elif i == 2:
  67. return (x | ~y) ^ z
  68. elif i == 3:
  69. return (x & z) | (y & ~z)
  70. elif i == 4:
  71. return x ^ (y | ~z)
  72. else:
  73. assert False
  74. def rol(x, i):
  75. """Rotate the bottom 32 bits of x left by i bits."""
  76. return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff
  77. def compress(h0, h1, h2, h3, h4, block):
  78. """Compress state (h0, h1, h2, h3, h4) with block."""
  79. # Left path variables.
  80. al, bl, cl, dl, el = h0, h1, h2, h3, h4
  81. # Right path variables.
  82. ar, br, cr, dr, er = h0, h1, h2, h3, h4
  83. # Message variables.
  84. x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]
  85. # Iterate over the 80 rounds of the compression.
  86. for j in range(80):
  87. rnd = j >> 4
  88. # Perform left side of the transformation.
  89. al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
  90. al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
  91. # Perform right side of the transformation.
  92. ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
  93. ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr
  94. # Compose old state, left transform, and right transform into new state.
  95. return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr
  96. class ripemd160:
  97. def __init__(self,data=b''):
  98. """Compute the RIPEMD-160 hash of data."""
  99. # Initialize state.
  100. state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
  101. # Process full 64-byte blocks in the input.
  102. for b in range(len(data) >> 6):
  103. state = compress(*state, data[64*b:64*(b+1)])
  104. # Construct final blocks (with padding and size).
  105. pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
  106. fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
  107. # Process final blocks.
  108. for b in range(len(fin) >> 6):
  109. state = compress(*state, fin[64*b:64*(b+1)])
  110. # Produce output.
  111. self.res = b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
  112. def hexdigest(self):
  113. return self.res.hex()
  114. def digest(self):
  115. return self.res
  116. def update(self,*args,**kwargs):
  117. raise NotImplementedError('update() method not implemented for pure-Python ripemd160')
  118. def copy(self,*args,**kwargs):
  119. raise NotImplementedError('copy() method not implemented for pure-Python ripemd160')
  120. if __name__ == '__main__':
  121. # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
  122. vectors = [
  123. (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
  124. (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
  125. (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
  126. (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
  127. (b"abcdefghijklmnopqrstuvwxyz",
  128. "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
  129. (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
  130. "12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
  131. (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
  132. "b0e20b6e3116640286ed3a87a5713079b21f5189"),
  133. (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
  134. (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
  135. ]
  136. def test_ripemd160():
  137. import sys
  138. verbose = [s for s in ('verbose','--verbose','-v') if s in sys.argv]
  139. sys.stderr.write("Testing RIPEMD-160 test vectors...")
  140. sys.stderr.flush()
  141. fs = '{:<7} {:62}{:3} {}\n'
  142. col1_w = 62
  143. if verbose:
  144. sys.stderr.write('\n' + fs.format('BYTES','INPUT','','OUTPUT'))
  145. for msg,hexout in vectors:
  146. a = ripemd160(msg).hexdigest()
  147. b = hexout
  148. assert a == b, f'{a} != {b}'
  149. if verbose:
  150. sys.stderr.write(fs.format(
  151. len(msg),
  152. msg.decode()[:col1_w],
  153. "..." if len(msg) > col1_w else "",
  154. hexout ))
  155. sys.stderr.write('OK\n')
  156. test_ripemd160()