sha2.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. sha2: A non-optimized but very compact implementation of the SHA2 hash
  20. algorithm for the MMGen suite. Implements SHA256, SHA512 and
  21. SHA256Compress (unpadded SHA256, required for Zcash addresses)
  22. """
  23. # IMPORTANT NOTE: Since GMP precision is platform-dependent, generated constants
  24. # for SHA512 are not guaranteed to be correct! Therefore, the SHA512
  25. # implementation must not be used for anything but testing and study. Test with
  26. # the test/hashfunc.py script in the MMGen repository.
  27. from struct import pack,unpack
  28. class Sha2:
  29. 'Implementation based on the pseudocode at https://en.wikipedia.org/wiki/SHA-2'
  30. K = ()
  31. @classmethod
  32. def initConstants(cls):
  33. from math import sqrt
  34. def nextPrime(n=2):
  35. while True:
  36. for factor in range(2,int(sqrt(n))+1):
  37. if n % factor == 0:
  38. break
  39. else:
  40. yield n
  41. n += 1
  42. # Find the first nRounds primes
  43. npgen = nextPrime()
  44. primes = [next(npgen) for x in range(cls.nRounds)]
  45. fb_mul = 2 ** cls.wordBits
  46. def getFractionalBits(n):
  47. return int((n - int(n)) * fb_mul)
  48. if cls.use_gmp:
  49. from gmpy2 import context,set_context,sqrt,cbrt
  50. # context() parameters are platform-dependent!
  51. set_context(context(precision=75,round=1)) # OK for gmp 6.1.2 / gmpy 2.1.0
  52. else:
  53. cbrt = lambda n: pow(n, 1 / 3)
  54. # First wordBits bits of the fractional parts of the square roots of the first 8 primes
  55. cls.H_init = tuple(getFractionalBits(sqrt(n)) for n in primes[:8])
  56. # First wordBits bits of the fractional parts of the cube roots of the first nRounds primes
  57. cls.K = tuple(getFractionalBits(cbrt(n)) for n in primes)
  58. def __init__(self,message,preprocess=True):
  59. 'Use preprocess=False for Sha256Compress'
  60. assert isinstance(message,(bytes,bytearray,list)),'message must be of type bytes, bytearray or list'
  61. if not self.K:
  62. type(self).initConstants()
  63. self.H = list(self.H_init)
  64. self.M = message
  65. self.W = [0] * self.nRounds
  66. if preprocess:
  67. self.padMessage()
  68. self.bytesToWords()
  69. self.compute()
  70. def padMessage(self):
  71. """
  72. Pre-processing (padding) (adapted from the standard, translating bits to bytes)
  73. - Begin with the original message of length MSGLEN bytes
  74. - Create MLPACK := MSGLEN * 8 encoded as a blkSize-bit big-endian integer
  75. - Append 0x80 (0b10000000)
  76. - Append PADLEN null bytes, where PADLEN is the minimum number >= 0 such that
  77. MSGLEN + 1 + PADLEN + len(MLPACK) is a multiple of blkSize
  78. - Append MLPACK
  79. """
  80. mlpack = self.pack_msglen()
  81. padlen = self.blkSize - (len(self.M) % self.blkSize) - len(mlpack) - 1
  82. if padlen < 0:
  83. padlen += self.blkSize
  84. self.M = self.M + bytes([0x80] + [0] * padlen) + mlpack
  85. def bytesToWords(self):
  86. ws = self.wordSize
  87. assert len(self.M) % ws == 0
  88. self.M = tuple(unpack(self.word_fmt,self.M[i*ws:ws+(i*ws)])[0] for i in range(len(self.M) // ws))
  89. def digest(self):
  90. return b''.join((pack(self.word_fmt,w) for w in self.H))
  91. def hexdigest(self):
  92. return self.digest().hex()
  93. def compute(self):
  94. for i in range(0,len(self.M),16):
  95. self.processBlock(i)
  96. def processBlock(self,offset):
  97. 'Process a blkSize-byte chunk of the message'
  98. def rrotate(a,b):
  99. return ((a << self.wordBits-b) & self.wordMask) | (a >> b)
  100. def addm(a,b):
  101. return (a + b) & self.wordMask
  102. # Copy chunk into first 16 words of message schedule array
  103. for i in range(16):
  104. self.W[i] = self.M[offset + i]
  105. # Extend the first 16 words into the remaining nRounds words of message schedule array
  106. for i in range(16,self.nRounds):
  107. g0 = self.W[i-15]
  108. gamma0 = rrotate(g0,self.g0r1) ^ rrotate(g0,self.g0r2) ^ (g0 >> self.g0r3)
  109. g1 = self.W[i-2]
  110. gamma1 = rrotate(g1,self.g1r1) ^ rrotate(g1,self.g1r2) ^ (g1 >> self.g1r3)
  111. self.W[i] = addm(addm(addm(gamma0,self.W[i-7]),gamma1),self.W[i-16])
  112. # Initialize working variables from current hash state
  113. a,b,c,d,e,f,g,h = self.H
  114. # Compression function main loop
  115. for i in range(self.nRounds):
  116. ch = (e & f) ^ (~e & g)
  117. maj = (a & b) ^ (a & c) ^ (b & c)
  118. sigma0 = rrotate(a,self.s0r1) ^ rrotate(a,self.s0r2) ^ rrotate(a,self.s0r3)
  119. sigma1 = rrotate(e,self.s1r1) ^ rrotate(e,self.s1r2) ^ rrotate(e,self.s1r3)
  120. t1 = addm(addm(addm(addm(h,sigma1),ch),self.K[i]),self.W[i])
  121. t2 = addm(sigma0,maj)
  122. h = g
  123. g = f
  124. f = e
  125. e = addm(d,t1)
  126. d = c
  127. c = b
  128. b = a
  129. a = addm(t1,t2)
  130. # Save hash state
  131. for n,v in enumerate((a,b,c,d,e,f,g,h)):
  132. self.H[n] = addm(self.H[n],v)
  133. class Sha256(Sha2):
  134. use_gmp = False
  135. g0r1,g0r2,g0r3 = (7,18,3)
  136. g1r1,g1r2,g1r3 = (17,19,10)
  137. s0r1,s0r2,s0r3 = (2,13,22)
  138. s1r1,s1r2,s1r3 = (6,11,25)
  139. blkSize = 64
  140. nRounds = 64
  141. wordSize = 4
  142. wordBits = 32
  143. wordMask = 0xffffffff
  144. word_fmt = '>I'
  145. def pack_msglen(self):
  146. return pack('>Q',len(self.M)*8)
  147. class Sha512(Sha2):
  148. """
  149. SHA-512 is identical in structure to SHA-256, but:
  150. - the message is broken into 1024-bit chunks,
  151. - the initial hash values and round constants are extended to 64 bits,
  152. - there are 80 rounds instead of 64,
  153. - the message schedule array W has 80 64-bit words instead of 64 32-bit words,
  154. - to extend the message schedule array W, the loop is from 16 to 79 instead of from 16 to 63,
  155. - the round constants are based on the first 80 primes 2..409,
  156. - the word size used for calculations is 64 bits long,
  157. - the appended length of the message (before pre-processing), in bits, is a 128-bit big-endian integer, and
  158. - the shift and rotate amounts used are different.
  159. """
  160. use_gmp = True
  161. g0r1,g0r2,g0r3 = (1,8,7)
  162. g1r1,g1r2,g1r3 = (19,61,6)
  163. s0r1,s0r2,s0r3 = (28,34,39)
  164. s1r1,s1r2,s1r3 = (14,18,41)
  165. blkSize = 128
  166. nRounds = 80
  167. wordSize = 8
  168. wordBits = 64
  169. wordMask = 0xffffffffffffffff
  170. word_fmt = '>Q'
  171. def pack_msglen(self):
  172. return pack('>Q', (len(self.M)*8) // (2**64)) + \
  173. pack('>Q', (len(self.M)*8) % (2**64))