sha2.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 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.py: 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. from struct import pack,unpack
  24. class Sha2(object):
  25. 'Implementation based on the pseudocode at https://en.wikipedia.org/wiki/SHA-2'
  26. K = None
  27. @classmethod
  28. def initConstants(cls):
  29. from math import sqrt
  30. def nextPrime(n=2):
  31. while True:
  32. for factor in range(2,int(sqrt(n))+1):
  33. if n % factor == 0:
  34. break
  35. else:
  36. yield n
  37. n += 1
  38. # Find the first nRounds primes
  39. npgen = nextPrime()
  40. primes = [next(npgen) for x in range(cls.nRounds)]
  41. fb_mul = 2 ** cls.wordBits
  42. def getFractionalBits(n):
  43. return int((n - int(n)) * fb_mul)
  44. if cls.use_gmp:
  45. from gmpy2 import context,set_context,sqrt,cbrt
  46. set_context(context(precision=75))
  47. else:
  48. cbrt = lambda n: pow(n, 1 / 3)
  49. # First wordBits bits of the fractional parts of the square roots of the first 8 primes
  50. H = (getFractionalBits(sqrt(n)) for n in primes[:8])
  51. # First wordBits bits of the fractional parts of the cube roots of the first nRounds primes
  52. K = (getFractionalBits(cbrt(n)) for n in primes)
  53. cls.H_init = tuple(H)
  54. cls.K = tuple(K)
  55. def __init__(self,message,preprocess=True):
  56. 'Use preprocess=False for Sha256Compress'
  57. assert type(message) in (bytes,bytearray,list),'message must be of type bytes, bytearray or list'
  58. if self.K == None:
  59. type(self).initConstants()
  60. self.H = list(self.H_init)
  61. self.M = message
  62. self.W = [0] * self.nRounds
  63. if preprocess:
  64. self.padMessage()
  65. self.bytesToWords()
  66. self.compute()
  67. def padMessage(self):
  68. """
  69. Pre-processing (padding) (adapted from the standard, translating bits to bytes)
  70. - Begin with the original message of length MSGLEN bytes
  71. - Create MLPACK := MSGLEN * 8 encoded as a blkSize-bit big-endian integer
  72. - Append 0x80 (0b10000000)
  73. - Append PADLEN null bytes, where PADLEN is the minimum number >= 0 such that
  74. MSGLEN + 1 + PADLEN + len(MLPACK) is a multiple of blkSize
  75. - Append MLPACK
  76. """
  77. mlpack = self.pack_msglen()
  78. padlen = self.blkSize - (len(self.M) % self.blkSize) - len(mlpack) - 1
  79. if padlen < 0: padlen += self.blkSize
  80. self.M = self.M + bytes([0x80] + [0] * padlen) + mlpack
  81. def bytesToWords(self):
  82. ws = self.wordSize
  83. assert len(self.M) % ws == 0
  84. self.M = tuple([unpack(self.word_fmt,self.M[i*ws:ws+(i*ws)])[0] for i in range(len(self.M) // ws)])
  85. def digest(self):
  86. return b''.join((pack(self.word_fmt,w) for w in self.H))
  87. def hexdigest(self):
  88. return self.digest().hex()
  89. def compute(self):
  90. for i in range(0,len(self.M),16):
  91. self.processBlock(i)
  92. def processBlock(self,offset):
  93. 'Process a blkSize-byte chunk of the message'
  94. def rrotate(a,b): return ((a << self.wordBits-b) & self.wordMask) | (a >> b)
  95. def addm(a,b): return (a + b) & self.wordMask
  96. # Copy chunk into first 16 words of message schedule array
  97. for i in range(16):
  98. self.W[i] = self.M[offset + i]
  99. # Extend the first 16 words into the remaining nRounds words of message schedule array
  100. for i in range(16,self.nRounds):
  101. g0 = self.W[i-15]
  102. gamma0 = rrotate(g0,self.g0r1) ^ rrotate(g0,self.g0r2) ^ (g0 >> self.g0r3)
  103. g1 = self.W[i-2]
  104. gamma1 = rrotate(g1,self.g1r1) ^ rrotate(g1,self.g1r2) ^ (g1 >> self.g1r3)
  105. self.W[i] = addm(addm(addm(gamma0,self.W[i-7]),gamma1),self.W[i-16])
  106. # Initialize working variables from current hash state
  107. a,b,c,d,e,f,g,h = self.H
  108. # Compression function main loop
  109. for i in range(self.nRounds):
  110. ch = (e & f) ^ (~e & g)
  111. maj = (a & b) ^ (a & c) ^ (b & c)
  112. sigma0 = rrotate(a,self.s0r1) ^ rrotate(a,self.s0r2) ^ rrotate(a,self.s0r3)
  113. sigma1 = rrotate(e,self.s1r1) ^ rrotate(e,self.s1r2) ^ rrotate(e,self.s1r3)
  114. t1 = addm(addm(addm(addm(h,sigma1),ch),self.K[i]),self.W[i])
  115. t2 = addm(sigma0,maj)
  116. h = g
  117. g = f
  118. f = e
  119. e = addm(d,t1)
  120. d = c
  121. c = b
  122. b = a
  123. a = addm(t1,t2)
  124. # Save hash state
  125. for n,v in enumerate((a,b,c,d,e,f,g,h)):
  126. self.H[n] = addm(self.H[n],v)
  127. class Sha256(Sha2):
  128. use_gmp = False
  129. g0r1,g0r2,g0r3 = (7,18,3)
  130. g1r1,g1r2,g1r3 = (17,19,10)
  131. s0r1,s0r2,s0r3 = (2,13,22)
  132. s1r1,s1r2,s1r3 = (6,11,25)
  133. blkSize = 64
  134. nRounds = 64
  135. wordSize = 4
  136. wordBits = 32
  137. wordMask = 0xffffffff
  138. word_fmt = '>I'
  139. def pack_msglen(self):
  140. return pack('>Q',len(self.M)*8)
  141. class Sha512(Sha2):
  142. """
  143. SHA-512 is identical in structure to SHA-256, but:
  144. - the message is broken into 1024-bit chunks,
  145. - the initial hash values and round constants are extended to 64 bits,
  146. - there are 80 rounds instead of 64,
  147. - the message schedule array W has 80 64-bit words instead of 64 32-bit words,
  148. - to extend the message schedule array W, the loop is from 16 to 79 instead of from 16 to 63,
  149. - the round constants are based on the first 80 primes 2..409,
  150. - the word size used for calculations is 64 bits long,
  151. - the appended length of the message (before pre-processing), in bits, is a 128-bit big-endian integer, and
  152. - the shift and rotate amounts used are different.
  153. """
  154. use_gmp = True
  155. g0r1,g0r2,g0r3 = (1,8,7)
  156. g1r1,g1r2,g1r3 = (19,61,6)
  157. s0r1,s0r2,s0r3 = (28,34,39)
  158. s1r1,s1r2,s1r3 = (14,18,41)
  159. blkSize = 128
  160. nRounds = 80
  161. wordSize = 8
  162. wordBits = 64
  163. wordMask = 0xffffffffffffffff
  164. word_fmt = '>Q'
  165. def pack_msglen(self):
  166. return pack('>Q', (len(self.M)*8) // (2**64)) + \
  167. pack('>Q', (len(self.M)*8) % (2**64))