Remove dependencies on pysha3 and ethereum modules

- pysha3: Monero and Ethereum use the old pre-SHA3 version of the keccak
  hash function, which is not supported by hashlib.sha3.

  The pysha3 package supports the function but is not portable.

  Therefore, use the pure-Python implementation mmgen.keccak as a fallback,
  making the pysha3 package optional.

  Use of mmgen.keccak may be forced with --use-internal-keccak-module

  mmgen.keccak was ported from the Python 2 implementation at
  https://github.com/ctz/keccak

- ethereum (pyethereum): Installation of this package presents numerous
  problems due to poor maintenance and many superfluous dependencies.

  Therefore, use stripped-down and modified local versions of
  ethereum.transactions and ethereum.utils as fallbacks, making the ethereum
  package optional.

  The local pyethereum.utils uses mmgen.keccak as a fallback for pysha3's
  keccak implementation.
This commit is contained in:
The MMGen Project 2019-03-23 14:21:34 +00:00
commit a7126ede03
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
17 changed files with 1083 additions and 74 deletions

View file

@ -78,14 +78,27 @@ class AddrGeneratorBech32(AddrGenerator):
raise NotImplementedError('Segwit redeem script not supported by this address type')
class AddrGeneratorEthereum(AddrGenerator):
def __init__(self,addr_type):
try:
assert not g.use_internal_keccak_module
from sha3 import keccak_256
except:
from mmgen.keccak import keccak_256
self.keccak_256 = keccak_256
from mmgen.protocol import hash256
self.hash256 = hash256
return AddrGenerator.__init__(addr_type)
def to_addr(self,pubhex):
assert type(pubhex) == PubKey
import sha3
return CoinAddr(sha3.keccak_256(bytes.fromhex(pubhex[2:])).hexdigest()[24:])
return CoinAddr(self.keccak_256(bytes.fromhex(pubhex[2:])).hexdigest()[24:])
def to_wallet_passwd(self,sk_hex):
from mmgen.protocol import hash256
return WalletPassword(hash256(sk_hex)[:32])
return WalletPassword(self.hash256(sk_hex)[:32])
def to_segwit_redeem_script(self,pubhex):
raise NotImplementedError('Segwit redeem script not supported by this address type')
@ -132,6 +145,31 @@ class AddrGeneratorZcashZ(AddrGenerator):
class AddrGeneratorMonero(AddrGenerator):
def __init__(self,addr_type):
try:
assert not g.use_internal_keccak_module
from sha3 import keccak_256
except:
from mmgen.keccak import keccak_256
self.keccak_256 = keccak_256
from mmgen.protocol import hash256
self.hash256 = hash256
if opt.use_old_ed25519:
from mmgen.ed25519 import edwards,encodepoint,B,scalarmult
else:
from mmgen.ed25519ll_djbec import scalarmult
from mmgen.ed25519 import edwards,encodepoint,B
self.edwards = edwards
self.encodepoint = encodepoint
self.scalarmult = scalarmult
self.B = B
return AddrGenerator.__init__(addr_type)
def b58enc(self,addr_bytes):
enc = baseconv.fromhex
l = len(addr_bytes)
@ -141,42 +179,33 @@ class AddrGeneratorMonero(AddrGenerator):
def to_addr(self,sk_hex): # sk_hex instead of pubhex
if opt.use_old_ed25519:
from mmgen.ed25519 import edwards,encodepoint,B,scalarmult
else:
from mmgen.ed25519ll_djbec import scalarmult
from mmgen.ed25519 import edwards,encodepoint,B
# Source and license for scalarmultbase function:
# https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py
# Copyright (c) 2014-2016, The Monero Project
# All rights reserved.
def scalarmultbase(e):
if e == 0: return [0, 1]
Q = scalarmult(B, e//2)
Q = edwards(Q, Q)
if e & 1: Q = edwards(Q, B)
Q = self.scalarmult(self.B, e//2)
Q = self.edwards(Q, Q)
if e & 1: Q = self.edwards(Q, self.B)
return Q
def hex2int_le(hexstr):
return int((bytes.fromhex(hexstr)[::-1]).hex(),16)
vk_hex = self.to_viewkey(sk_hex)
pk_str = encodepoint(scalarmultbase(hex2int_le(sk_hex)))
pvk_str = encodepoint(scalarmultbase(hex2int_le(vk_hex)))
pk_str = self.encodepoint(scalarmultbase(hex2int_le(sk_hex)))
pvk_str = self.encodepoint(scalarmultbase(hex2int_le(vk_hex)))
addr_p1 = bytes.fromhex(g.proto.addr_ver_num['monero'][0]) + pk_str + pvk_str
import sha3
return CoinAddr(self.b58enc(addr_p1 + sha3.keccak_256(addr_p1).digest()[:4]))
return CoinAddr(self.b58enc(addr_p1 + self.keccak_256(addr_p1).digest()[:4]))
def to_wallet_passwd(self,sk_hex):
from mmgen.protocol import hash256
return WalletPassword(hash256(sk_hex)[:32])
return WalletPassword(self.hash256(sk_hex)[:32])
def to_viewkey(self,sk_hex):
assert len(sk_hex) == 64,'{}: incorrect privkey length'.format(len(sk_hex))
import sha3
return MoneroViewKey(g.proto.preprocess_key(sha3.keccak_256(bytes.fromhex(sk_hex)).hexdigest(),None))
return MoneroViewKey(g.proto.preprocess_key(self.keccak_256(bytes.fromhex(sk_hex)).hexdigest(),None))
def to_segwit_redeem_script(self,sk_hex):
raise NotImplementedError('Monero addresses incompatible with Segwit')

View file

@ -20,14 +20,19 @@
altcoins.eth.contract: Ethereum contract and token classes for the MMGen suite
"""
from sha3 import keccak_256
from decimal import Decimal
import rlp
from mmgen.globalvars import g
from mmgen.common import *
from mmgen.obj import MMGenObject,TokenAddr,CoinTxID,ETHAmt
from mmgen.util import msg,msg_r,pmsg,pdie
from mmgen.util import msg,pmsg
try:
assert not g.use_internal_keccak_module
from sha3 import keccak_256
except:
from mmgen.keccak import keccak_256
def parse_abi(s):
return [s[:8]] + [s[8+x*64:8+(x+1)*64] for x in range(len(s[8:])//64)]
@ -103,7 +108,10 @@ class Token(MMGenObject): # ERC20
'data': bytes.fromhex(data) }
def txsign(self,tx_in,key,from_addr,chain_id=None):
from ethereum.transactions import Transaction
try: from ethereum.transactions import Transaction
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
if chain_id is None:
chain_id_method = ('parity_chainId','eth_chainId')['eth_chainId' in g.rpch.caps]
chain_id = int(g.rpch.request(chain_id_method),16)

View file

View file

@ -0,0 +1,215 @@
#
# Adapted from: # https://github.com/ethereum/pyethereum/blob/master/ethereum/transactions.py
#
import rlp
from rlp.sedes import big_endian_int,binary
from mmgen.altcoins.eth.pyethereum.utils import (
str_to_bytes,encode_hex,ascii_chr,big_endian_to_int,TT256,mk_contract_address,
ecsign,ecrecover_to_pub,normalize_key )
import mmgen.altcoins.eth.pyethereum.utils as utils
class InvalidTransaction(Exception): pass
class opcodes(object):
GTXCOST = 21000 # TX BASE GAS COST
GTXDATAZERO = 4 # TX DATA ZERO BYTE GAS COST
GTXDATANONZERO = 68 # TX DATA NON ZERO BYTE GAS COST
# in the yellow paper it is specified that s should be smaller than
# secpk1n (eq.205)
secpk1n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
null_address = b'\xff' * 20
class Transaction(rlp.Serializable):
"""
A transaction is stored as:
[nonce, gasprice, startgas, to, value, data, v, r, s]
nonce is the number of transactions already sent by that account, encoded
in binary form (eg. 0 -> '', 7 -> '\x07', 1000 -> '\x03\xd8').
(v,r,s) is the raw Electrum-style signature of the transaction without the
signature made with the private key corresponding to the sending account,
with 0 <= v <= 3. From an Electrum-style signature (65 bytes) it is
possible to extract the public key, and thereby the address, directly.
A valid transaction is one where:
(i) the signature is well-formed (ie. 0 <= v <= 3, 0 <= r < P, 0 <= s < N,
0 <= r < P - N if v >= 2), and
(ii) the sending account has enough funds to pay the fee and the value.
"""
fields = [
('nonce', big_endian_int),
('gasprice', big_endian_int),
('startgas', big_endian_int),
('to', utils.address),
('value', big_endian_int),
('data', binary),
('v', big_endian_int),
('r', big_endian_int),
('s', big_endian_int),
]
_sender = None
def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0):
# self.data = None
to = utils.normalize_address(to, allow_blank=True)
super(
Transaction,
self).__init__(
nonce,
gasprice,
startgas,
to,
value,
data,
v,
r,
s)
if self.gasprice >= TT256 or self.startgas >= TT256 or \
self.value >= TT256 or self.nonce >= TT256:
raise InvalidTransaction("Values way too high!")
@property
def sender(self):
if not self._sender:
# Determine sender
if self.r == 0 and self.s == 0:
self._sender = null_address
else:
if self.v in (27, 28):
vee = self.v
sighash = utils.sha3(rlp.encode(unsigned_tx_from_tx(self), UnsignedTransaction))
elif self.v >= 37:
vee = self.v - self.network_id * 2 - 8
assert vee in (27, 28)
rlpdata = rlp.encode(rlp.infer_sedes(self).serialize(self)[:-3] + [self.network_id, '', ''])
sighash = utils.sha3(rlpdata)
else:
raise InvalidTransaction("Invalid V value")
if self.r >= secpk1n or self.s >= secpk1n or self.r == 0 or self.s == 0:
raise InvalidTransaction("Invalid signature values!")
pub = ecrecover_to_pub(sighash, vee, self.r, self.s)
if pub == b'\x00' * 64:
raise InvalidTransaction(
"Invalid signature (zero privkey cannot sign)")
self._sender = utils.sha3(pub)[-20:]
return self._sender
@property
def network_id(self):
if self.r == 0 and self.s == 0:
return self.v
elif self.v in (27, 28):
return None
else:
return ((self.v - 1) // 2) - 17
@sender.setter
def sender(self, value):
self._sender = value
def sign(self, key, network_id=None):
"""Sign this transaction with a private key.
A potentially already existing signature would be overridden.
"""
if network_id is None:
rawhash = utils.sha3(rlp.encode(unsigned_tx_from_tx(self), UnsignedTransaction))
else:
assert 1 <= network_id < 2**63 - 18
rlpdata = rlp.encode(rlp.infer_sedes(self).serialize(self)[:-3] + [network_id, b'', b''])
rawhash = utils.sha3(rlpdata)
key = normalize_key(key)
v, r, s = ecsign(rawhash, key)
if network_id is not None:
v += 8 + network_id * 2
ret = self.copy(
v=v, r=r, s=s
)
ret._sender = utils.privtoaddr(key)
return ret
@property
def hash(self):
return utils.sha3(rlp.encode(self))
def to_dict(self):
d = {}
for name, _ in self.__class__._meta.fields:
d[name] = getattr(self, name)
if name in ('to', 'data'):
d[name] = '0x' + encode_hex(d[name])
d['sender'] = '0x' + encode_hex(self.sender)
d['hash'] = '0x' + encode_hex(self.hash)
return d
@property
def intrinsic_gas_used(self):
num_zero_bytes = str_to_bytes(self.data).count(ascii_chr(0))
num_non_zero_bytes = len(self.data) - num_zero_bytes
return (opcodes.GTXCOST
# + (0 if self.to else opcodes.CREATE[3])
+ opcodes.GTXDATAZERO * num_zero_bytes
+ opcodes.GTXDATANONZERO * num_non_zero_bytes)
@property
def creates(self):
"returns the address of a contract created by this tx"
if self.to in (b'', '\0' * 20):
return mk_contract_address(self.sender, self.nonce)
def __eq__(self, other):
return isinstance(other, self.__class__) and self.hash == other.hash
def __lt__(self, other):
return isinstance(other, self.__class__) and self.hash < other.hash
def __hash__(self):
return utils.big_endian_to_int(self.hash)
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return '<Transaction(%s)>' % encode_hex(self.hash)[:4]
def __structlog__(self):
return encode_hex(self.hash)
# This method should be called for block numbers >= HOMESTEAD_FORK_BLKNUM only.
# The >= operator is replaced by > because the integer division N/2 always produces the value
# which is by 0.5 less than the real N/2
def check_low_s_metropolis(self):
if self.s > secpk1n // 2:
raise InvalidTransaction("Invalid signature S value!")
def check_low_s_homestead(self):
if self.s > secpk1n // 2 or self.s == 0:
raise InvalidTransaction("Invalid signature S value!")
class UnsignedTransaction(rlp.Serializable):
fields = [
(field, sedes) for field, sedes in Transaction._meta.fields
if field not in "vrs"
]
def unsigned_tx_from_tx(tx):
return UnsignedTransaction(
nonce=tx.nonce,
gasprice=tx.gasprice,
startgas=tx.startgas,
to=tx.to,
value=tx.value,
data=tx.data,
)

View file

@ -0,0 +1,397 @@
#
# Adapted from: https://github.com/ethereum/pyethereum/blob/master/ethereum/utils.py
#
from py_ecc.secp256k1 import privtopub, ecdsa_raw_sign, ecdsa_raw_recover
import rlp
from rlp.sedes import big_endian_int, BigEndianInt, Binary
from eth_utils import encode_hex as encode_hex_0x
from eth_utils import decode_hex, int_to_big_endian, big_endian_to_int
from rlp.utils import ALL_BYTES
from mmgen.globalvars import g
try:
assert not g.use_internal_keccak_module
import sha3 as _sha3
def sha3_256(x): return _sha3.keccak_256(x).digest()
except:
from mmgen.keccak import keccak_256
def sha3_256(x):
return keccak_256(x).digest()
class Memoize:
def __init__(self, fn):
self.fn = fn
self.memo = {}
def __call__(self, *args):
if args not in self.memo:
self.memo[args] = self.fn(*args)
return self.memo[args]
TT256 = 2 ** 256
TT256M1 = 2 ** 256 - 1
TT255 = 2 ** 255
SECP256K1P = 2**256 - 4294968273
def is_numeric(x): return isinstance(x, int)
def is_string(x): return isinstance(x, bytes)
def to_string(value):
if isinstance(value, bytes):
return value
if isinstance(value, str):
return bytes(value, 'utf-8')
if isinstance(value, int):
return bytes(str(value), 'utf-8')
def int_to_bytes(value):
if isinstance(value, bytes):
return value
return int_to_big_endian(value)
def to_string_for_regexp(value):
return str(to_string(value), 'utf-8')
unicode = str
def bytearray_to_bytestr(value):
return bytes(value)
def encode_int32(v):
return v.to_bytes(32, byteorder='big')
def bytes_to_int(value):
return int.from_bytes(value, byteorder='big')
def str_to_bytes(value):
if isinstance(value, bytearray):
value = bytes(value)
if isinstance(value, bytes):
return value
return bytes(value, 'utf-8')
def ascii_chr(n):
return ALL_BYTES[n]
def encode_hex(n):
if isinstance(n, str):
return encode_hex(n.encode('ascii'))
return encode_hex_0x(n)[2:]
def ecrecover_to_pub(rawhash, v, r, s):
result = ecdsa_raw_recover(rawhash, (v, r, s))
if result:
x, y = result
pub = encode_int32(x) + encode_int32(y)
else:
raise ValueError('Invalid VRS')
assert len(pub) == 64
return pub
def ecsign(rawhash, key):
return ecdsa_raw_sign(rawhash, key)
def mk_contract_address(sender, nonce):
return sha3(rlp.encode([normalize_address(sender), nonce]))[12:]
def mk_metropolis_contract_address(sender, initcode):
return sha3(normalize_address(sender) + initcode)[12:]
def safe_ord(value):
if isinstance(value, int):
return value
else:
return ord(value)
# decorator
def flatten(li):
o = []
for l in li:
o.extend(l)
return o
def bytearray_to_int(arr):
o = 0
for a in arr:
o = (o << 8) + a
return o
def int_to_32bytearray(i):
o = [0] * 32
for x in range(32):
o[31 - x] = i & 0xff
i >>= 8
return o
# sha3_count = [0]
def sha3(seed):
return sha3_256(to_string(seed))
assert encode_hex(
sha3(b'')) == 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
@Memoize
def privtoaddr(k):
k = normalize_key(k)
x, y = privtopub(k)
return sha3(encode_int32(x) + encode_int32(y))[12:]
def normalize_address(x, allow_blank=False):
if is_numeric(x):
return int_to_addr(x)
if allow_blank and x in {'', b''}:
return b''
if len(x) in (42, 50) and x[:2] in {'0x', b'0x'}:
x = x[2:]
if len(x) in (40, 48):
x = decode_hex(x)
if len(x) == 24:
assert len(x) == 24 and sha3(x[:20])[:4] == x[-4:]
x = x[:20]
if len(x) != 20:
raise Exception("Invalid address format: %r" % x)
return x
def normalize_key(key):
if is_numeric(key):
o = encode_int32(key)
elif len(key) == 32:
o = key
elif len(key) == 64:
o = decode_hex(key)
elif len(key) == 66 and key[:2] == '0x':
o = decode_hex(key[2:])
else:
raise Exception("Invalid key format: %r" % key)
if o == b'\x00' * 32:
raise Exception("Zero privkey invalid")
return o
def zpad(x, l):
""" Left zero pad value `x` at least to length `l`.
>>> zpad('', 1)
'\x00'
>>> zpad('\xca\xfe', 4)
'\x00\x00\xca\xfe'
>>> zpad('\xff', 1)
'\xff'
>>> zpad('\xca\xfe', 2)
'\xca\xfe'
"""
return b'\x00' * max(0, l - len(x)) + x
def rzpad(value, total_length):
""" Right zero pad value `x` at least to length `l`.
>>> zpad('', 1)
'\x00'
>>> zpad('\xca\xfe', 4)
'\xca\xfe\x00\x00'
>>> zpad('\xff', 1)
'\xff'
>>> zpad('\xca\xfe', 2)
'\xca\xfe'
"""
return value + b'\x00' * max(0, total_length - len(value))
def int_to_addr(x):
o = [b''] * 20
for i in range(20):
o[19 - i] = ascii_chr(x & 0xff)
x >>= 8
return b''.join(o)
def coerce_addr_to_bin(x):
if is_numeric(x):
return encode_hex(zpad(big_endian_int.serialize(x), 20))
elif len(x) == 40 or len(x) == 0:
return decode_hex(x)
else:
return zpad(x, 20)[-20:]
def coerce_addr_to_hex(x):
if is_numeric(x):
return encode_hex(zpad(big_endian_int.serialize(x), 20))
elif len(x) == 40 or len(x) == 0:
return x
else:
return encode_hex(zpad(x, 20)[-20:])
def coerce_to_int(x):
if is_numeric(x):
return x
elif len(x) == 40:
return big_endian_to_int(decode_hex(x))
else:
return big_endian_to_int(x)
def coerce_to_bytes(x):
if is_numeric(x):
return big_endian_int.serialize(x)
elif len(x) == 40:
return decode_hex(x)
else:
return x
def parse_int_or_hex(s):
if is_numeric(s):
return s
elif s[:2] in (b'0x', '0x'):
s = to_string(s)
tail = (b'0' if len(s) % 2 else b'') + s[2:]
return big_endian_to_int(decode_hex(tail))
else:
return int(s)
def ceil32(x):
return x if x % 32 == 0 else x + 32 - (x % 32)
def to_signed(i):
return i if i < TT255 else i - TT256
def sha3rlp(x):
return sha3(rlp.encode(x))
# Format encoders/decoders for bin, addr, int
def decode_bin(v):
"""decodes a bytearray from serialization"""
if not is_string(v):
raise Exception("Value must be binary, not RLP array")
return v
def decode_addr(v):
"""decodes an address from serialization"""
if len(v) not in [0, 20]:
raise Exception("Serialized addresses must be empty or 20 bytes long!")
return encode_hex(v)
def decode_int(v):
"""decodes and integer from serialization"""
if len(v) > 0 and (v[0] == b'\x00' or v[0] == 0):
raise Exception("No leading zero bytes allowed for integers")
return big_endian_to_int(v)
def decode_int256(v):
return big_endian_to_int(v)
def encode_bin(v):
"""encodes a bytearray into serialization"""
return v
def encode_root(v):
"""encodes a trie root into serialization"""
return v
def encode_int(v):
"""encodes an integer into serialization"""
if not is_numeric(v) or v < 0 or v >= TT256:
raise Exception("Integer invalid or out of range: %r" % v)
return int_to_big_endian(v)
def encode_int256(v):
return zpad(int_to_big_endian(v), 256)
def scan_bin(v):
if v[:2] in ('0x', b'0x'):
return decode_hex(v[2:])
else:
return decode_hex(v)
def scan_int(v):
if v[:2] in ('0x', b'0x'):
return big_endian_to_int(decode_hex(v[2:]))
else:
return int(v)
def int_to_hex(x):
o = encode_hex(encode_int(x))
return '0x' + (o[1:] if (len(o) > 0 and o[0] == b'0') else o)
def remove_0x_head(s):
return s[2:] if s[:2] in (b'0x', '0x') else s
def parse_as_bin(s):
return decode_hex(s[2:] if s[:2] == '0x' else s)
def parse_as_int(s):
return s if is_numeric(s) else int(
'0' + s[2:], 16) if s[:2] == '0x' else int(s)
def dump_state(trie):
res = ''
for k, v in list(trie.to_dict().items()):
res += '%r:%r\n' % (encode_hex(k), encode_hex(v))
return res
class Denoms():
def __init__(self):
self.wei = 1
self.babbage = 10 ** 3
self.ada = 10 ** 3
self.kwei = 10 ** 6
self.lovelace = 10 ** 6
self.mwei = 10 ** 6
self.shannon = 10 ** 9
self.gwei = 10 ** 9
self.szabo = 10 ** 12
self.finney = 10 ** 15
self.mether = 10 ** 15
self.ether = 10 ** 18
self.turing = 2 ** 256 - 1
denoms = Denoms()
address = Binary.fixed_length(20, allow_empty=True)
int20 = BigEndianInt(20)
int32 = BigEndianInt(32)
int256 = BigEndianInt(256)
hash32 = Binary.fixed_length(32)
trie_root = Binary.fixed_length(32, allow_empty=True)

View file

@ -90,7 +90,10 @@ class EthereumMMGenTX(MMGenTX):
# hex data if signed, json if unsigned: see create_raw()
def check_txfile_hex_data(self):
if self.check_sigs():
from ethereum.transactions import Transaction
try: from ethereum.transactions import Transaction
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
import rlp
etx = rlp.decode(bytes.fromhex(self.hex),Transaction)
d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x'
@ -284,7 +287,9 @@ class EthereumMMGenTX(MMGenTX):
'nonce': d['nonce'],
'data': bytes.fromhex(d['data'])}
from ethereum.transactions import Transaction
try: from ethereum.transactions import Transaction
except: from mmgen.altcoins.eth.pyethereum.transactions import Transaction
etx = Transaction(**d_in).sign(wif,d['chainId'])
assert etx.sender.hex() == d['from'],(
'Sender address recovered from signature does not match true sender')

View file

@ -79,8 +79,12 @@ class g(object):
testnet = False
regtest = False
accept_defaults = False
use_internal_keccak_module = False
chain = None # set by first call to rpc_init()
chains = 'mainnet','testnet','regtest'
# rpc:
rpc_host = ''
rpc_port = 0
rpc_user = ''
@ -88,6 +92,7 @@ class g(object):
rpc_fail_on_command = ''
rpch = None # global RPC handle
# regtest:
bob = False
alice = False
@ -117,12 +122,20 @@ class g(object):
data_dir_root,data_dir,cfg_file = None,None,None
daemon_data_dir = '' # set by user or protocol
# User opt sets global var:
# global var sets user opt:
global_sets_opt = ( 'minconf','seed_len','hash_preset','usr_randchars','debug',
'quiet','tx_confs','tx_fee_adj','key_generator' )
# user opt sets global var:
opt_sets_global = ( 'use_internal_keccak_module', )
# 'long' opts - opt sets global var
common_opts = (
'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password',
'daemon_data_dir','force_256_color','regtest','coin','bob','alice',
'accept_defaults','token'
)
# opts initialized to None by opts.init() if not set by user
required_opts = (
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
'show_hash_presets','label','keep_passphrase','keep_hash_preset','yes',
@ -179,10 +192,6 @@ class g(object):
max_tx_file_size = 100000
max_input_size = 1024 * 1024
# Global var sets user opt:
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
'quiet','tx_confs','tx_fee_adj','key_generator']
passwd_max_tries = 5
max_urandchars = 80

310
mmgen/keccak.py Normal file
View file

@ -0,0 +1,310 @@
#!/usr/bin/env python3
#
# Python 2 code from here: https://github.com/ctz/keccak
# Ported to python3 by the MMGen Project
#
# This is the old, pre-SHA3 version of Keccak used by Ethereum, which is not supported
# by hashlib.sha3
from math import log
from operator import xor
from copy import deepcopy
from functools import reduce
# The Keccak-f round constants.
RoundConstants = [
0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000,
0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A,
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008
]
RotationConstants = [
[ 0, 1, 62, 28, 27, ],
[ 36, 44, 6, 55, 20, ],
[ 3, 10, 43, 25, 39, ],
[ 41, 45, 15, 21, 8, ],
[ 18, 2, 61, 56, 14, ]
]
Masks = [(1 << i) - 1 for i in range(65)]
def bits2bytes(x):
return (int(x) + 7) // 8
def rol(value, left, bits):
"""
Circularly rotate 'value' to the left,
treating it as a quantity of the given size in bits.
"""
top = value >> (bits - left)
bot = (value & Masks[bits - left]) << left
return bot | top
def ror(value, right, bits):
"""
Circularly rotate 'value' to the right,
treating it as a quantity of the given size in bits.
"""
top = value >> right
bot = (value & Masks[right]) << (bits - right)
return bot | top
def multirate_padding(used_bytes, align_bytes):
"""
The Keccak padding function.
"""
padlen = align_bytes - used_bytes
if padlen == 0:
padlen = align_bytes
# note: padding done in 'internal bit ordering', wherein LSB is leftmost
if padlen == 1:
return [0x81]
else:
return [0x01] + ([0x00] * (padlen - 2)) + [0x80]
def keccak_f(state):
"""
This is Keccak-f permutation. It operates on and
mutates the passed-in KeccakState. It returns nothing.
"""
def round(A, RC):
W, H = state.W, state.H
rangeW, rangeH = state.rangeW, state.rangeH
lanew = state.lanew
zero = state.zero
# theta
C = [reduce(xor, A[x]) for x in rangeW]
D = [0] * W
for x in rangeW:
D[x] = C[(x - 1) % W] ^ rol(C[(x + 1) % W], 1, lanew)
for y in rangeH:
A[x][y] ^= D[x]
# rho and pi
B = zero()
for x in rangeW:
for y in rangeH:
B[y % W][(2 * x + 3 * y) % H] = rol(A[x][y], RotationConstants[y][x], lanew)
# chi
for x in rangeW:
for y in rangeH:
A[x][y] = B[x][y] ^ ((~ B[(x + 1) % W][y]) & B[(x + 2) % W][y])
# iota
A[0][0] ^= RC
l = int(log(state.lanew, 2))
nr = 12 + 2 * l
for ir in range(nr):
round(state.s, RoundConstants[ir])
class KeccakState(object):
"""
A keccak state container.
The state is stored as a 5x5 table of integers.
"""
W = 5
H = 5
rangeW = range(W)
rangeH = range(H)
@staticmethod
def zero():
"""
Returns an zero state table.
"""
return [[0] * KeccakState.W for x in KeccakState.rangeH]
@staticmethod
def format(st):
"""
Formats the given state as hex, in natural byte order.
"""
rows = []
def fmt(x): return '%016x' % x
for y in KeccakState.rangeH:
row = []
for x in rangeW:
row.append(fmt(st[x][y]))
rows.append(' '.join(row))
return '\n'.join(rows)
@staticmethod
def lane2bytes(s, w):
"""
Converts the lane s to a sequence of byte values,
assuming a lane is w bits.
"""
return [(s >> b) & 0xff for b in range(0, w, 8)]
@staticmethod
def bytes2lane(bb):
"""
Converts a sequence of byte values to a lane.
"""
r = 0
for b in reversed(bb):
r = r << 8 | b
return r
def __init__(self, bitrate, b):
self.bitrate = bitrate
self.b = b
# only byte-aligned
assert self.bitrate % 8 == 0
self.bitrate_bytes = bits2bytes(self.bitrate)
assert self.b % 25 == 0
self.lanew = self.b // 25
self.s = KeccakState.zero()
def __str__(self):
return KeccakState.format(self.s)
def absorb(self, bb):
"""
Mixes in the given bitrate-length string to the state.
"""
assert len(bb) == self.bitrate_bytes
bb += [0] * bits2bytes(self.b - self.bitrate)
i = 0
for y in self.rangeH:
for x in self.rangeW:
self.s[x][y] ^= KeccakState.bytes2lane(bb[i:i + 8])
i += 8
def squeeze(self):
"""
Returns the bitrate-length prefix of the state to be output.
"""
return self.get_bytes()[:self.bitrate_bytes]
def get_bytes(self):
"""
Convert whole state to a byte string.
"""
out = [0] * bits2bytes(self.b)
i = 0
for y in self.rangeH:
for x in self.rangeW:
v = KeccakState.lane2bytes(self.s[x][y], self.lanew)
out[i:i+8] = v
i += 8
return out
def set_bytes(self, bb):
"""
Set whole state from byte string, which is assumed
to be the correct length.
"""
i = 0
for y in self.rangeH:
for x in self.rangeW:
self.s[x][y] = KeccakState.bytes2lane(bb[i:i+8])
i += 8
class KeccakSponge(object):
def __init__(self, bitrate, width, padfn, permfn):
self.state = KeccakState(bitrate, width)
self.padfn = padfn
self.permfn = permfn
self.buffer = []
def copy(self):
return deepcopy(self)
def absorb_block(self, bb):
assert len(bb) == self.state.bitrate_bytes
self.state.absorb(bb)
self.permfn(self.state)
def absorb(self, s):
self.buffer += bytes(s)
while len(self.buffer) >= self.state.bitrate_bytes:
self.absorb_block(self.buffer[:self.state.bitrate_bytes])
self.buffer = self.buffer[self.state.bitrate_bytes:]
def absorb_final(self):
padded = self.buffer + self.padfn(len(self.buffer), self.state.bitrate_bytes)
self.absorb_block(padded)
self.buffer = []
def squeeze_once(self):
rc = self.state.squeeze()
self.permfn(self.state)
return rc
def squeeze(self, l):
Z = self.squeeze_once()
while len(Z) < l:
Z += self.squeeze_once()
return Z[:l]
class KeccakHash(object):
"""
The Keccak hash function, with a hashlib-compatible interface.
"""
def __init__(self, bitrate_bits, capacity_bits, output_bits):
# our in-absorption sponge. this is never given padding
assert bitrate_bits + capacity_bits in (25, 50, 100, 200, 400, 800, 1600)
self.sponge = KeccakSponge(bitrate_bits, bitrate_bits + capacity_bits,
multirate_padding,
keccak_f)
# hashlib interface members
assert output_bits % 8 == 0
self.digest_size = bits2bytes(output_bits)
self.block_size = bits2bytes(bitrate_bits)
def __repr__(self):
inf = (self.sponge.state.bitrate,
self.sponge.state.b - self.sponge.state.bitrate,
self.digest_size * 8)
return '<KeccakHash with r=%d, c=%d, image=%d>' % inf
def copy(self):
return deepcopy(self)
def update(self, s):
self.sponge.absorb(s)
def digest(self):
finalised = self.sponge.copy()
finalised.absorb_final()
digest = finalised.squeeze(self.digest_size)
return bytes(digest)
def hexdigest(self):
return self.digest().hex()
@staticmethod
def preset(bitrate_bits, capacity_bits, output_bits):
"""
Returns a factory function for the given bitrate, sponge capacity and output length.
The function accepts an optional initial input, ala hashlib.
"""
def create(initial_input = None):
h = KeccakHash(bitrate_bits, capacity_bits, output_bits)
if initial_input is not None:
h.update(initial_input)
return h
return create
# SHA3 parameter presets
keccak_224 = KeccakHash.preset(1152, 448, 224)
keccak_256 = KeccakHash.preset(1088, 512, 256)
keccak_384 = KeccakHash.preset(832, 768, 384)
keccak_512 = KeccakHash.preset(576, 1024, 512)

View file

@ -35,7 +35,7 @@ if sys.argv[0].split('-')[-1] == 'keygen':
else:
gen_what = 'addresses'
gen_desc = 'addresses'
opt_filter = 'hbcdeEiHOKlpzPqrStv-'
opt_filter = 'hbcdeEiHOkKlpzPqrStv-'
note_addrkey = ''
note_secp256k1 = """
If available, the secp256k1 library will be used for address generation.
@ -59,6 +59,7 @@ opts_data = lambda: {
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated)
-O, --old-incog-fmt Specify old-format incognito input
-k, --use-internal-keccak-module Force use of the internal keccak module
-K, --key-generator=m Use method 'm' for public key generation
Options: {kgs} (default: {kg})
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option

View file

@ -60,6 +60,7 @@ opts_data = lambda: {
-d, --outdir= d Specify an alternate directory 'd' for output
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-k, --use-internal-keccak-module Force use of the internal keccak module
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'.

View file

@ -35,6 +35,7 @@ opts_data = lambda: {
-d, --outdir= d Specify an alternate directory 'd' for output
-D, --tx-id Display transaction ID and exit
-e, --echo-passphrase Print passphrase to screen when typing it
-E, --use-internal-keccak-module Force use of the internal keccak module
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated)

View file

@ -228,8 +228,9 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
usage_txt = opts_data['usage']
# Transfer uopts into opt, setting program's opts + required opts to None if not set by user
for o in tuple([s.rstrip('=') for s in long_opts] + add_opts + skipped_opts) + \
g.required_opts + g.common_opts:
for o in ( tuple([s.rstrip('=') for s in long_opts] + add_opts + skipped_opts)
+ g.required_opts
+ g.common_opts ):
setattr(opt,o,uopts[o] if o in uopts else None)
if opt.version: Die(0,"""
@ -255,9 +256,11 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
override_from_env()
# User opt sets global var - do these here, before opt is set from g.global_sets_opt
for k in g.common_opts:
val = getattr(opt,k)
if val != None: setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
for k in (g.common_opts + g.opt_sets_global):
if hasattr(opt,k):
val = getattr(opt,k)
if val != None:
setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
if g.regtest: g.testnet = True # These are equivalent for now

View file

@ -415,8 +415,14 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
assert len(addr) == cls.addr_width,'Incorrect width'
ret = b58dec(addr)
import sha3
chk = sha3.keccak_256(bytes.fromhex(ret)[:-4]).hexdigest()[:8]
try:
assert not g.use_internal_keccak_module
from sha3 import keccak_256
except:
from mmgen.keccak import keccak_256
chk = keccak_256(bytes.fromhex(ret)[:-4]).hexdigest()[:8]
assert chk == ret[-8:],'{}: incorrect checksum. Correct value: {}'.format(ret[-8:],chk)
return { 'hex': ret, 'format': 'monero' } if return_dict else True

View file

@ -19,10 +19,10 @@ scrambletest_py='test/scrambletest.py'
mmgen_tool='cmds/mmgen-tool'
mmgen_keygen='cmds/mmgen-keygen'
python='python3'
rounds=100 rounds_mid=250 rounds_max=500
rounds=100 rounds_min=20 rounds_mid=250 rounds_max=500
monero_addrs='3,99,2,22-24,101-104'
dfl_tests='obj unit sha2 alts monero eth autosign btc btc_tn btc_rt bch bch_rt ltc ltc_tn ltc_rt tool tool2 gen'
dfl_tests='obj unit hash 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)
@ -47,7 +47,7 @@ do
echo " AVAILABLE TESTS:"
echo " obj - data objects"
echo " unit - unit tests"
echo " sha2 - MMGen sha2 implementation"
echo " hash - internal hash function implementations"
echo " alts - operations for all supported gen-only altcoins"
echo " monero - operations for Monero"
echo " eth - operations for Ethereum"
@ -78,7 +78,7 @@ do
gentest_py="$python $gentest_py"
mmgen_tool="$python $mmgen_tool"
mmgen_keygen="$python $mmgen_keygen" ;&
f) rounds=10 rounds_mid=25 rounds_max=50 monero_addrs='3,23' ;;
f) rounds=10 rounds_min=3 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"
@ -169,12 +169,13 @@ s_unit='Running unit'
t_unit=("$unit_tests_py")
f_unit='Unit tests run 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_hash='Internal hash function implementations'
s_hash='Testing internal hash function implementations'
t_hash=(
"$python test/hashfunc.py sha256 $rounds_max"
"$python test/hashfunc.py sha512 $rounds_max"
"$python test/hashfunc.py keccak $rounds_max")
f_hash='Hash function tests complete'
i_alts='Gen-only altcoin'
s_alts='The following tests will test generation operations for all supported altcoins'
@ -190,7 +191,11 @@ t_alts=(
"$gentest_py --coin=ltc --type=segwit 2 $rounds"
"$gentest_py --coin=ltc --type=bech32 2 $rounds"
"$gentest_py --coin=etc 2 $rounds"
"$gentest_py --coin=etc --use-internal-keccak-module 2 $rounds_min"
"$gentest_py --coin=eth 2 $rounds"
"$gentest_py --coin=eth --use-internal-keccak-module 2 $rounds_min"
"$gentest_py --coin=xmr 2 $rounds"
"$gentest_py --coin=xmr --use-internal-keccak-module 2 $rounds_min"
"$gentest_py --coin=zec 2 $rounds"
"$gentest_py --coin=zec --type=zcash_z 2 $rounds_mid"
@ -228,7 +233,7 @@ s_monero='Testing key-address file generation and wallet creation and sync opera
s_monero='The monerod (mainnet) daemon must be running for the following tests'
t_monero=(
"mmgen-walletgen -q -r0 -p1 -Llabel --outdir $TMPDIR -o words"
"$mmgen_keygen -q --accept-defaults --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs"
"$mmgen_keygen -q --accept-defaults --use-internal-keccak-module --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs"
'cs1=$(mmgen-tool -q --accept-defaults --coin=xmr keyaddrfile_chksum $TMPDIR/*-XMR*.akeys)'
"$mmgen_keygen -q --use-old-ed25519 --accept-defaults --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs"
'cs2=$(mmgen-tool -q --accept-defaults --coin=xmr keyaddrfile_chksum $TMPDIR/*-XMR*.akeys)'

View file

@ -128,6 +128,7 @@ setup(
'mmgen.exception',
'mmgen.filename',
'mmgen.globalvars',
'mmgen.keccak',
'mmgen.license',
'mmgen.mn_electrum',
'mmgen.mn_tirosh',
@ -151,6 +152,9 @@ setup(
'mmgen.altcoins.eth.obj',
'mmgen.altcoins.eth.tx',
'mmgen.altcoins.eth.tw',
'mmgen.altcoins.eth.pyethereum.__init__',
'mmgen.altcoins.eth.pyethereum.transactions',
'mmgen.altcoins.eth.pyethereum.utils',
'mmgen.main',
'mmgen.main_addrgen',

View file

@ -37,6 +37,7 @@ opts_data = lambda: {
'options': """
-h, --help Print this help message
-a, --all Test all supported coins for external generator 'ext'
-k, --use-internal-keccak-module Force use of the internal keccak module
--, --longhelp Print help message for long options (common options)
-q, --quiet Produce quieter output
-t, --type=t Specify address type (valid options: 'compressed','segwit','zcash_z')
@ -122,7 +123,7 @@ def init_external_prog():
addr_type = MMGenAddrType('Z')
elif test_support('pyethereum'):
try:
import ethereum.utils as eth
import mmgen.altcoins.eth.pyethereum.utils as eth
except:
raise ImportError("Unable to import 'ethereum' module. Is pyethereum installed?")
ext_sec2addr = pyethereum_sec2addr

View file

@ -16,22 +16,21 @@
# 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
test/hashfunc.py: Test internal implementations of SHA256, SHA512 and Keccak256
"""
import sys,os
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'"
assert test in ('Sha256','Sha512','Keccak'), "Valid choices for test are 'sha256','sha512' or 'keccak'"
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):
class TestHashFunc(object):
def test_constants(self):
msg('Testing generated constants: ')
@ -45,8 +44,7 @@ class TestSha2(object):
msg('OK\n')
def compare_hashes(self,dlen,data):
import hashlib
sha2_ref = getattr(hashlib,self.desc)(data).hexdigest()
sha2_ref = getattr(self.hashlib,self.desc)(data).hexdigest()
ret = self.t_cls(data).hexdigest()
if ret != sha2_ref:
m ='\nHashes do not match!\nReference {d}: {}\nMMGen {d}: {}'
@ -81,9 +79,40 @@ class TestSha2(object):
self.compare_hashes(dlen,os.urandom(dlen))
msg('OK\n')
class TestKeccak(TestHashFunc):
desc = 'keccak_256'
def __init__(self):
from mmgen.keccak import keccak_256
import sha3
self.t_cls = keccak_256
self.hashlib = sha3
def test_constants(self): pass
class TestSha2(TestHashFunc):
def __init__(self):
from mmgen.sha2 import Sha256,Sha512
import hashlib
self.t_cls = { 'sha256':Sha256, 'sha512':Sha512 }[self.desc]
self.hashlib = hashlib
class TestSha256(TestSha2):
desc = '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 )
class TestSha512(TestSha2):
desc = 'sha512'
t_cls = Sha512
H_ref = (
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 )
@ -105,23 +134,8 @@ class TestSha512(TestSha2):
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]()
msg(green('Testing internal implementation of {}\n'.format(t.desc)))
t.test_constants()
t.test_ref()
t.test_random(random_rounds)