From a7126ede0375b0e5f244168e7cad36f4916d340d Mon Sep 17 00:00:00 2001 From: MMGen Date: Sat, 23 Mar 2019 14:21:34 +0000 Subject: [PATCH] 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. --- mmgen/addr.py | 71 +++- mmgen/altcoins/eth/contract.py | 14 +- mmgen/altcoins/eth/pyethereum/__init__.py | 0 mmgen/altcoins/eth/pyethereum/transactions.py | 215 ++++++++++ mmgen/altcoins/eth/pyethereum/utils.py | 397 ++++++++++++++++++ mmgen/altcoins/eth/tx.py | 9 +- mmgen/globalvars.py | 19 +- mmgen/keccak.py | 310 ++++++++++++++ mmgen/main_addrgen.py | 3 +- mmgen/main_tool.py | 1 + mmgen/main_txsign.py | 1 + mmgen/opts.py | 13 +- mmgen/protocol.py | 10 +- scripts/test-release.sh | 27 +- setup.py | 4 + test/gentest.py | 3 +- test/{sha2test.py => hashfunc.py} | 60 ++- 17 files changed, 1083 insertions(+), 74 deletions(-) create mode 100755 mmgen/altcoins/eth/pyethereum/__init__.py create mode 100644 mmgen/altcoins/eth/pyethereum/transactions.py create mode 100644 mmgen/altcoins/eth/pyethereum/utils.py create mode 100644 mmgen/keccak.py rename test/{sha2test.py => hashfunc.py} (88%) diff --git a/mmgen/addr.py b/mmgen/addr.py index ae59fc7e..9129e977 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -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') diff --git a/mmgen/altcoins/eth/contract.py b/mmgen/altcoins/eth/contract.py index 88f26b54..b6cbfd4b 100755 --- a/mmgen/altcoins/eth/contract.py +++ b/mmgen/altcoins/eth/contract.py @@ -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) diff --git a/mmgen/altcoins/eth/pyethereum/__init__.py b/mmgen/altcoins/eth/pyethereum/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/mmgen/altcoins/eth/pyethereum/transactions.py b/mmgen/altcoins/eth/pyethereum/transactions.py new file mode 100644 index 00000000..9c412a56 --- /dev/null +++ b/mmgen/altcoins/eth/pyethereum/transactions.py @@ -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 '' % 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, + ) diff --git a/mmgen/altcoins/eth/pyethereum/utils.py b/mmgen/altcoins/eth/pyethereum/utils.py new file mode 100644 index 00000000..628849c3 --- /dev/null +++ b/mmgen/altcoins/eth/pyethereum/utils.py @@ -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) diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index c131288b..1a302e52 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -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') diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index 12eeebff..1603f321 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -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 diff --git a/mmgen/keccak.py b/mmgen/keccak.py new file mode 100644 index 00000000..1d36bdce --- /dev/null +++ b/mmgen/keccak.py @@ -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 '' % 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) diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 185d1ebd..723f5c93 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -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 diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index aa08dff1..ab4c8e05 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -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'. diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 47500f4a..1a2973b3 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -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) diff --git a/mmgen/opts.py b/mmgen/opts.py index 9b541102..455e403d 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -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 diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 9f3002f8..62ef094f 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -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 diff --git a/scripts/test-release.sh b/scripts/test-release.sh index 6dd4d1bc..bf68f664 100755 --- a/scripts/test-release.sh +++ b/scripts/test-release.sh @@ -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)' diff --git a/setup.py b/setup.py index 3b05e104..20671423 100755 --- a/setup.py +++ b/setup.py @@ -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', diff --git a/test/gentest.py b/test/gentest.py index 1584d799..740901a2 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -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 diff --git a/test/sha2test.py b/test/hashfunc.py similarity index 88% rename from test/sha2test.py rename to test/hashfunc.py index e1d8313f..77039292 100755 --- a/test/sha2test.py +++ b/test/hashfunc.py @@ -16,22 +16,21 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -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)