rewrite SHA2 implementation, support SHA512; update tests accordingly

- Note: the sha2 module is used by MMGen solely for generating Zcash
  addresses and viewkeys
This commit is contained in:
The MMGen Project 2019-03-15 09:09:38 +00:00
commit 2b6dc95970
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
7 changed files with 354 additions and 232 deletions

View file

@ -101,7 +101,7 @@ class AddrGeneratorZcashZ(AddrGenerator):
s = bytearray(s + bytes(32))
s[0] |= 0xc0
s[32] = t
from mmgen.sha256 import Sha256
from mmgen.sha2 import Sha256
return Sha256(s,preprocess=False).digest()
def to_addr(self,pubhex): # pubhex is really privhex

200
mmgen/sha2.py Executable file
View file

@ -0,0 +1,200 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
sha2.py: A non-optimized but very compact implementation of the SHA2 hash
algorithm for the MMGen suite. Implements SHA256, SHA512 and
SHA256Compress (unpadded SHA256, required for Zcash addresses)
"""
from binascii import hexlify
from struct import pack,unpack
class Sha2(object):
'Implementation based on the pseudocode at https://en.wikipedia.org/wiki/SHA-2'
K = None
@classmethod
def initConstants(cls):
from math import sqrt
def nextPrime(n=2):
while True:
for factor in range(2,int(sqrt(n))+1):
if n % factor == 0:
break
else:
yield n
n += 1
# Find the first nRounds primes
npgen = nextPrime()
primes = [next(npgen) for x in range(cls.nRounds)]
fb_mul = 2 ** cls.wordBits
def getFractionalBits(n):
return int((n - int(n)) * fb_mul)
if cls.use_gmp:
from gmpy2 import context,set_context,sqrt,cbrt
set_context(context(precision=75))
else:
cbrt = lambda n: pow(n, 1 / 3)
# First wordBits bits of the fractional parts of the square roots of the first 8 primes
H = (getFractionalBits(sqrt(n)) for n in primes[:8])
# First wordBits bits of the fractional parts of the cube roots of the first nRounds primes
K = (getFractionalBits(cbrt(n)) for n in primes)
cls.H_init = tuple(H)
cls.K = tuple(K)
def __init__(self,message,preprocess=True):
'Use preprocess=False for Sha256Compress'
assert type(message) in (bytes,bytearray,list),'message must be of type bytes, bytearray or list'
if self.K == None:
type(self).initConstants()
self.H = list(self.H_init)
self.M = message
self.W = [0] * self.nRounds
if preprocess:
self.padMessage()
self.bytesToWords()
self.compute()
def padMessage(self):
"""
Pre-processing (padding) (adapted from the standard, translating bits to bytes)
- Begin with the original message of length MSGLEN bytes
- Create MLPACK := MSGLEN * 8 encoded as a blkSize-bit big-endian integer
- Append 0x80 (0b10000000)
- Append PADLEN null bytes, where PADLEN is the minimum number >= 0 such that
MSGLEN + 1 + PADLEN + len(MLPACK) is a multiple of blkSize
- Append MLPACK
"""
mlpack = self.pack_msglen()
padlen = self.blkSize - (len(self.M) % self.blkSize) - len(mlpack) - 1
if padlen < 0: padlen += self.blkSize
self.M = self.M + bytes([0x80] + [0] * padlen) + mlpack
def bytesToWords(self):
ws = self.wordSize
assert len(self.M) % ws == 0
self.M = tuple([unpack(self.word_fmt,self.M[i*ws:ws+(i*ws)])[0] for i in range(len(self.M) // ws)])
def digest(self):
return b''.join((pack(self.word_fmt,w) for w in self.H))
def hexdigest(self):
return hexlify(self.digest())
def compute(self):
for i in range(0,len(self.M),16):
self.processBlock(i)
def processBlock(self,offset):
'Process a blkSize-byte chunk of the message'
def rrotate(a,b): return ((a << self.wordBits-b) & self.wordMask) | (a >> b)
def addm(a,b): return (a + b) & self.wordMask
# Copy chunk into first 16 words of message schedule array
for i in range(16):
self.W[i] = self.M[offset + i]
# Extend the first 16 words into the remaining nRounds words of message schedule array
for i in range(16,self.nRounds):
g0 = self.W[i-15]
gamma0 = rrotate(g0,self.g0r1) ^ rrotate(g0,self.g0r2) ^ (g0 >> self.g0r3)
g1 = self.W[i-2]
gamma1 = rrotate(g1,self.g1r1) ^ rrotate(g1,self.g1r2) ^ (g1 >> self.g1r3)
self.W[i] = addm(addm(addm(gamma0,self.W[i-7]),gamma1),self.W[i-16])
# Initialize working variables from current hash state
a,b,c,d,e,f,g,h = self.H
# Compression function main loop
for i in range(self.nRounds):
ch = (e & f) ^ (~e & g)
maj = (a & b) ^ (a & c) ^ (b & c)
sigma0 = rrotate(a,self.s0r1) ^ rrotate(a,self.s0r2) ^ rrotate(a,self.s0r3)
sigma1 = rrotate(e,self.s1r1) ^ rrotate(e,self.s1r2) ^ rrotate(e,self.s1r3)
t1 = addm(addm(addm(addm(h,sigma1),ch),self.K[i]),self.W[i])
t2 = addm(sigma0,maj)
h = g
g = f
f = e
e = addm(d,t1)
d = c
c = b
b = a
a = addm(t1,t2)
# Save hash state
for n,v in enumerate((a,b,c,d,e,f,g,h)):
self.H[n] = addm(self.H[n],v)
class Sha256(Sha2):
use_gmp = False
g0r1,g0r2,g0r3 = (7,18,3)
g1r1,g1r2,g1r3 = (17,19,10)
s0r1,s0r2,s0r3 = (2,13,22)
s1r1,s1r2,s1r3 = (6,11,25)
blkSize = 64
nRounds = 64
wordSize = 4
wordBits = 32
wordMask = 0xffffffff
word_fmt = '>I'
def pack_msglen(self):
return pack('>Q',len(self.M)*8)
class Sha512(Sha2):
"""
SHA-512 is identical in structure to SHA-256, but:
- the message is broken into 1024-bit chunks,
- the initial hash values and round constants are extended to 64 bits,
- there are 80 rounds instead of 64,
- the message schedule array W has 80 64-bit words instead of 64 32-bit words,
- to extend the message schedule array W, the loop is from 16 to 79 instead of from 16 to 63,
- the round constants are based on the first 80 primes 2..409,
- the word size used for calculations is 64 bits long,
- the appended length of the message (before pre-processing), in bits, is a 128-bit big-endian integer, and
- the shift and rotate amounts used are different.
"""
use_gmp = True
g0r1,g0r2,g0r3 = (1,8,7)
g1r1,g1r2,g1r3 = (19,61,6)
s0r1,s0r2,s0r3 = (28,34,39)
s1r1,s1r2,s1r3 = (14,18,41)
blkSize = 128
nRounds = 80
wordSize = 8
wordBits = 64
wordMask = 0xffffffffffffffff
word_fmt = '>Q'
def pack_msglen(self):
return pack('>Q', (len(self.M)*8) // (2**64)) + \
pack('>Q', (len(self.M)*8) % (2**64))

View file

@ -1,142 +0,0 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
sha256.py: custom sha256 implementation for Zcash
"""
# Sha256 code ported from JavaScript, originally here:
# CryptoJS v3.1.2 code.google.com/p/crypto-js
# (c) 2009-2013 by Jeff Mott. All rights reserved.
# code.google.com/p/crypto-js/wiki/License
# via here:
# https://www.npmjs.com/package/sha256
# and here:
# https://github.com/howardwu/zaddr
from binascii import hexlify
class Sha256(object):
def initConstants():
import math
def isPrime(n):
for factor in range(2,int(math.sqrt(n))+1):
if not (n % factor): return False
return True
def getFractionalBits(n):
return int((n - int(n)) * 0x100000000)
k = [0] * 64
n,nPrime = 2,0
while nPrime < 64:
if isPrime(n):
k[nPrime] = getFractionalBits(math.pow(n, 1 / 3))
nPrime += 1
n += 1
return k
K = initConstants()
# preprocess: True=Sha256(), False=Sha256Compress()
def __init__(self,message,preprocess=True):
self.H = [0x6A09E667,0xBB67AE85,0x3C6EF372,0xA54FF53A,0x510E527F,0x9B05688C,0x1F83D9AB,0x5BE0CD19]
self.M = message
self.W = [0] * 64
(self.bytesToWords,self.preprocessBlock)[preprocess]()
# self.initConstants()
self.compute()
def digest(self):
self.M = self.H
self.wordsToBytes()
return self.M
def hexdigest(self):
return hexlify(self.digest())
def bytesToWords(self):
assert type(self.M) in (bytes,bytearray,list)
words = [0] * (len(self.M) // 4 + len(self.M) % 4)
b = 0
for i in range(len(self.M)):
words[b >> 5] |= self.M[i] << (24 - b % 32)
b += 8
self.M = words
def wordsToBytes(self):
assert type(self.M) == list and len(self.M) == 8
self.M = bytes([(self.M[b >> 5] >> (24 - b % 32) & 0xff) for b in range(0,len(self.M)*32,8)])
def preprocessBlock(self):
def lshift(a,b): return (a << b) & 0xffffffff
assert type(self.M) in (bytes,bytearray)
l = len(self.M) * 8
self.bytesToWords()
last_idx = lshift((l + 64 >> 9),4) + 15
self.M.extend([0] * (last_idx - len(self.M) + 1))
self.M[l >> 5] |= lshift(0x80, (24 - l % 32))
self.M[last_idx] = l
def compute(self):
assert type(self.M) == list
for i in range(0,len(self.M),16):
self.processBlock(i)
def processBlock(self,offset):
def lshift(a,b): return (a << b) & 0xffffffff
def sumr(a,b): return (a + b) & 0xffffffff
a,b,c,d,e,f,g,h = [self.H[i] for i in range(8)] # Working variables
for i in range(64):
if i < 16:
self.W[i] = self.M[offset + i]
else:
gamma0x = self.W[i - 15]
gamma0 = ( (lshift(gamma0x,25) | (gamma0x >> 7)) ^
(lshift(gamma0x,14) | (gamma0x >> 18)) ^
(gamma0x >> 3) )
gamma1x = self.W[i - 2]
gamma1 = ( (lshift(gamma1x,15) | (gamma1x >> 17)) ^
(lshift(gamma1x,13) | (gamma1x >> 19)) ^
(gamma1x >> 10) )
self.W[i] = sumr(sumr(sumr(gamma0,self.W[i - 7]),gamma1),self.W[i - 16])
ch = (e & f) ^ (~e & g)
maj = (a & b) ^ (a & c) ^ (b & c)
sigma0 = (lshift(a,30) | (a >> 2)) ^ (lshift(a,19) | (a >> 13)) ^ (lshift(a,10) | (a >> 22))
sigma1 = (lshift(e,26) | (e >> 6)) ^ (lshift(e,21) | (e >> 11)) ^ (lshift(e,7) | (e >> 25))
t1 = sumr(sumr(sumr(sumr(h,sigma1),ch),self.K[i]),self.W[i])
t2 = sumr(sigma0,maj)
h = g
g = f
f = e
e = sumr(d,t1)
d = c
c = b
b = a
a = sumr(t1,t2)
# Intermediate hash value
for n,v in enumerate([a,b,c,d,e,f,g,h]):
self.H[n] = sumr(self.H[n],v)

View file

@ -18,10 +18,10 @@ scrambletest_py='test/scrambletest.py'
mmgen_tool='cmds/mmgen-tool'
mmgen_keygen='cmds/mmgen-keygen'
python='python3'
rounds=100 rounds_low=20 rounds_spec=500 sha_rounds=500 gen_rounds=10
rounds=100 rounds_mid=250 rounds_max=500
monero_addrs='3,99,2,22-24,101-104'
dfl_tests='obj sha256 alts monero eth autosign btc btc_tn btc_rt bch bch_rt ltc ltc_tn ltc_rt tool tool2 gen'
dfl_tests='obj sha2 alts monero eth autosign btc btc_tn btc_rt bch bch_rt ltc ltc_tn ltc_rt tool tool2 gen'
add_tests='autosign_minimal autosign_live'
PROGNAME=$(basename $0)
@ -45,7 +45,7 @@ do
echo " '-V' Run test/test.py and other commands with '--verbose' switch"
echo " AVAILABLE TESTS:"
echo " obj - data objects"
echo " sha256 - MMGen sha256 implementation"
echo " sha2 - MMGen sha2 implementation"
echo " alts - operations for all supported gen-only altcoins"
echo " monero - operations for Monero"
echo " eth - operations for Ethereum"
@ -75,7 +75,7 @@ do
gentest_py="$python $gentest_py"
mmgen_tool="$python $mmgen_tool"
mmgen_keygen="$python $mmgen_keygen" ;&
f) rounds=2 rounds_low=2 rounds_spec=10 sha_rounds=40 gen_rounds=2 monero_addrs='3,23' ;;
f) rounds=10 rounds_mid=25 rounds_max=50 monero_addrs='3,23' ;;
i) INSTALL=1 ;;
I) INSTALL_ONLY=1 ;;
l) echo -e "Default tests:\n $dfl_tests"
@ -162,10 +162,12 @@ t_obj=(
"$objtest_py --coin=ltc --testnet=1")
f_obj='Data object test complete'
i_sha256='MMGen sha256 implementation'
s_sha256='Testing sha256 implementation'
t_sha256=("$python test/sha256test.py $sha_rounds")
f_sha256='Sha256 test complete'
i_sha2='MMGen SHA2 implementation'
s_sha2='Testing SHA2 implementation'
t_sha2=(
"$python test/sha2test.py sha256 $rounds_max"
"$python test/sha2test.py sha512 $rounds_max")
f_sha2='SHA2 test complete'
i_alts='Gen-only altcoin'
s_alts='The following tests will test generation operations for all supported altcoins'
@ -183,7 +185,7 @@ t_alts=(
"$gentest_py --coin=etc 2 $rounds"
"$gentest_py --coin=eth 2 $rounds"
"$gentest_py --coin=zec 2 $rounds"
"$gentest_py --coin=zec --type=zcash_z 2 $rounds_spec"
"$gentest_py --coin=zec --type=zcash_z 2 $rounds_mid"
"$gentest_py --coin=btc 2:ext $rounds"
"$gentest_py --coin=btc --type=compressed 2:ext $rounds"
@ -194,12 +196,12 @@ t_alts=(
"$gentest_py --coin=etc 2:ext $rounds"
"$gentest_py --coin=eth 2:ext $rounds"
"$gentest_py --coin=zec 2:ext $rounds"
"$gentest_py --coin=zec --type=zcash_z 2:ext $rounds_spec"
"$gentest_py --coin=zec --type=zcash_z 2:ext $rounds_mid"
"$gentest_py --all 2:pycoin $rounds_low"
"$gentest_py --all 2:pyethereum $rounds_low"
"$gentest_py --all 2:keyconv $rounds_low"
"$gentest_py --all 2:zcash_mini $rounds_low")
"$gentest_py --all 2:pycoin $rounds"
"$gentest_py --all 2:pyethereum $rounds"
"$gentest_py --all 2:keyconv $rounds_mid"
"$gentest_py --all 2:zcash_mini $rounds_mid")
if [ "$MINGW" ]; then
t_alts[13]="# MSWin platform: skipping zcash z-addr generation and altcoin verification with third-party tools"
i=14 end=${#t_alts[*]}
@ -374,20 +376,20 @@ t_gen=(
"$gentest_py -q 2 $REFDIR/btcwallet.dump"
"$gentest_py -q --type=segwit 2 $REFDIR/btcwallet-segwit.dump"
"$gentest_py -q --type=bech32 2 $REFDIR/btcwallet-bech32.dump"
"$gentest_py -q 1:2 $gen_rounds"
"$gentest_py -q --type=segwit 1:2 $gen_rounds"
"$gentest_py -q --type=bech32 1:2 $gen_rounds"
"$gentest_py -q 1:2 $rounds"
"$gentest_py -q --type=segwit 1:2 $rounds"
"$gentest_py -q --type=bech32 1:2 $rounds"
"$gentest_py -q --testnet=1 2 $REFDIR/btcwallet-testnet.dump"
"$gentest_py -q --testnet=1 1:2 $gen_rounds"
"$gentest_py -q --testnet=1 --type=segwit 1:2 $gen_rounds"
"$gentest_py -q --testnet=1 1:2 $rounds"
"$gentest_py -q --testnet=1 --type=segwit 1:2 $rounds"
"$gentest_py -q --coin=ltc 2 $REFDIR/litecoin/ltcwallet.dump"
"$gentest_py -q --coin=ltc --type=segwit 2 $REFDIR/litecoin/ltcwallet-segwit.dump"
"$gentest_py -q --coin=ltc --type=bech32 2 $REFDIR/litecoin/ltcwallet-bech32.dump"
"$gentest_py -q --coin=ltc 1:2 $gen_rounds"
"$gentest_py -q --coin=ltc --type=segwit 1:2 $gen_rounds"
"$gentest_py -q --coin=ltc 1:2 $rounds"
"$gentest_py -q --coin=ltc --type=segwit 1:2 $rounds"
"$gentest_py -q --coin=ltc --testnet=1 2 $REFDIR/litecoin/ltcwallet-testnet.dump"
"$gentest_py -q --coin=ltc --testnet=1 1:2 $gen_rounds"
"$gentest_py -q --coin=ltc --testnet=1 --type=segwit 1:2 $gen_rounds")
"$gentest_py -q --coin=ltc --testnet=1 1:2 $rounds"
"$gentest_py -q --coin=ltc --testnet=1 --type=segwit 1:2 $rounds")
f_gen='gentest tests completed'
[ -d .git -a -n "$INSTALL" -a -z "$TESTING" ] && {

View file

@ -136,7 +136,7 @@ setup(
'mmgen.regtest',
'mmgen.rpc',
'mmgen.seed',
'mmgen.sha256',
'mmgen.sha2',
'mmgen.term',
'mmgen.tool',
'mmgen.tw',

View file

@ -1,65 +0,0 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys,os,hashlib
from binascii import hexlify
from mmgen.sha256 import Sha256
random_rounds = int(sys.argv[1]) if len(sys.argv) == 2 else 500
def msg(s): sys.stderr.write(s)
def green(s): return '\033[32;1m' + s + '\033[0m'
def compare_hashes(dlen,data):
sha2 = hashlib.sha256(data).hexdigest().encode()
# msg('Dlen {:<5} {}\r'.format(dlen,sha2))
my_sha2 = Sha256(data).hexdigest()
assert my_sha2 == sha2,'Hashes do not match!'
def test_K():
msg('Testing generated constants: ')
K_ref = [1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,-778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998]
def toSigned32(n): return ((n & 0xffffffff) ^ 0x80000000) - 0x80000000
K_sig = [toSigned32(n) for n in Sha256.K]
assert K_sig == K_ref,'Generated constants in K[] differ from reference value'
msg('OK\n')
def test_ref():
inputs = (
'','x','xa','the','the quick','the quick brown fox',
'\x00','\x00\x00','\x00'*256,'\x00'*512,'\x00'*511,'\x00'*513,
'\x0f','\x0f\x0f','\x0f'*256,'\x0f'*512,'\x0f'*511,'\x0f'*513,
'\x0f\x0d','\x0e\x0e'*256,'\x00\x0f'*512,'\x0e\x0f'*511,'\x0a\x0d'*513
)
for i,data in enumerate(inputs):
msg('\rTesting reference input data: {:4}/{} '.format(i+1,len(inputs)))
compare_hashes(len(data),data.encode())
msg('OK\n')
def test_random(rounds):
for i in range(rounds):
if i+1 in (1,rounds) or not (i+1) % 10:
msg('\rTesting random input data: {:4}/{} '.format(i+1,rounds))
dlen = int(hexlify(os.urandom(4)),16) >> 18
compare_hashes(dlen,os.urandom(dlen))
msg('OK\n')
msg(green('Testing MMGen implementation of Sha256()\n'))
test_K()
test_ref()
test_random(random_rounds)

127
test/sha2test.py Executable file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
test/sha2test.py: Test MMGen's SHA256 and SHA512 implementations
"""
import sys,os
from binascii import hexlify
from mmgen.sha2 import Sha256,Sha512
from mmgen.util import die
assert len(sys.argv) in (2,3),"Test takes 1 or 2 arguments: test name, plus optional rounds count"
test = sys.argv[1].capitalize()
assert test in ('Sha256','Sha512'), "Valid choices for test are 'sha256' or 'sha512'"
random_rounds = int(sys.argv[2]) if len(sys.argv) == 3 else 500
def msg(s): sys.stderr.write(s)
def green(s): return '\033[32;1m' + s + '\033[0m'
class TestSha2(object):
def test_constants(self):
msg('Testing generated constants: ')
h = self.t_cls(b'foo')
if h.H_init != self.H_ref:
m = 'Generated constants H[] differ from reference value:\nReference:\n{}\nGenerated:\n{}'
die(3,m.format([hex(n) for n in self.H_ref],[hex(n) for n in h.H_init]))
if h.K != self.K_ref:
m = 'Generated constants K[] differ from reference value:\nReference:\n{}\nGenerated:\n{}'
die(3,m.format([hex(n) for n in self.K_ref],[hex(n) for n in h.K]))
msg('OK\n')
def compare_hashes(self,dlen,data):
import hashlib
sha2 = getattr(hashlib,self.desc)(data).hexdigest().encode()
my_sha2 = self.t_cls(data).hexdigest()
if my_sha2 != sha2:
die(3,'Hashes do not match!\nmy_sha2: {}\nsha2: {}\n'.format(my_sha2,sha2))
def test_ref(self,input_n=None):
inputs = (
'','x','xa','the','7_chars''8charmsg' '9_charmsg','10_charmsg'
'8charmsg' * 8,
'8charmsg' * 7 + '7_chars',
'8charmsg' * 7 + '9_charmsg',
'\x00','\x00\x00','\x00'*256,
'\x0f','\x0f\x0f','\x0f'*256,
'\x0f\x0d','\x0e\x0e'*256,
'\x00'*511,'\x00'*512,'\x00'*513,
'\x0f'*512,'\x0f'*511,'\x0f'*513,
'\x00\x0f'*512,'\x0e\x0f'*511,'\x0a\x0d'*513,
'\x00\x0f'*1024,'\x0e\x0f'*1023,'\x0a\x0d'*1025 )
for i,data in enumerate([inputs[input_n]] if input_n is not None else inputs):
msg('\rTesting reference input data: {:4}/{} '.format(i+1,len(inputs)))
self.compare_hashes(len(data),data.encode())
msg('OK\n')
def test_random(self,rounds):
for i in range(rounds):
if i+1 in (1,rounds) or not (i+1) % 10:
msg('\rTesting random input data: {:4}/{} '.format(i+1,rounds))
dlen = int(hexlify(os.urandom(4)),16) >> 18
self.compare_hashes(dlen,os.urandom(dlen))
msg('OK\n')
class TestSha512(TestSha2):
desc = 'sha512'
t_cls = Sha512
H_ref = (
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 )
K_ref = (
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538,
0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe,
0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab,
0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed,
0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c,
0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 )
class TestSha256(TestSha2):
desc = 'sha256'
t_cls = Sha256
H_ref = (
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 )
K_ref = (
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 )
msg(green('Testing MMGen implementation of {}\n'.format(test)))
t = globals()['Test'+test]()
t.test_constants()
t.test_ref()
t.test_random(random_rounds)