diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..73be7d4 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,46 @@ +SHELL = /bin/bash +PY3 ?= $(shell python3 --version >/dev/null 2>&1 && echo python3 || echo python ) +VERSION = $(shell $(PY3) -c 'from hdwallet import __version__; print( __version__.strip("v"))') +WHEEL = dist/hdwallet-$(VERSION)-py3-none-any.whl + +PY3TEST = $(PY3) -m pytest + +.PHONY: all test build build-check wheel install-dev install clean FORCE + +all: build + +test: + $(PY3TEST) + +# Run only tests with a prefix containing the target string, eg test-blah +test-%: + $(PY3TEST) *$*_test.py + +unit-%: + $(PY3TEST) -k $* + +build: clean wheel + +build-check: + @$(PY3) -m build --version \ + || ( \ + echo -e "\n\n!!! Missing Python modules; run:"; \ + echo -e "\n\n $(PY3) -m pip install --upgrade pip setuptools wheel build\n"; \ + false; \ + ) + +wheel: $(WHEEL) + +$(WHEEL): build-check FORCE + $(PY3) -m build + @ls -last dist + +# Install from wheel, including all optional extra dependencies (except dev) +install-dev: $(WHEEL) FORCE + $(PY3) -m pip install --upgrade $<[tests] + +install: $(WHEEL) FORCE + $(PY3) -m pip install --force-reinstall $<[cli,docs] + +clean: + @rm -rf build dist *.egg-info $(shell find . -name '__pycache__' ) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index dd35aca..239dcb2 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -10,7 +10,7 @@ ) # HDWallet Information's -__version__: str = "v2.1.1" +__version__: str = "v2.1.2" __license__: str = "ISCL" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett@zoho.com" diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index daffaec..c9ff5fa 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -39,7 +39,7 @@ import hmac import ecdsa import struct -import sha3 +from Crypto.Hash import keccak import unicodedata import hashlib import base58 @@ -433,7 +433,7 @@ def from_path(self, path: Union[str, Derivation]) -> "HDWallet": if isinstance(path, Derivation): path = str(path) elif str(path)[0:2] != "m/": - raise ValueError("Bad path, please insert like this type of path \"m/0'/0\"! ") + raise ValueError("Bad path, please insert like this type of path \"m/0'/0\"!, not: %r" % ( path )) for index in path.lstrip("m/").split("/"): if "'" in index: @@ -1101,17 +1101,17 @@ def p2pkh_address(self) -> str: """ if self._cryptocurrency.SYMBOL in ["ETH", "ETHTEST"]: - keccak_256 = sha3.keccak_256() + keccak_256 = keccak.new(digest_bits=256) keccak_256.update(unhexlify(self.uncompressed())) address = keccak_256.hexdigest()[24:] return checksum_encode(address, crypto="eth") elif self._cryptocurrency.SYMBOL in ["XDC", "XDCTEST"]: - keccak_256 = sha3.keccak_256() + keccak_256 = keccak.new(digest_bits=256) keccak_256.update(unhexlify(self.uncompressed())) address = keccak_256.hexdigest()[24:] return checksum_encode(address, crypto="xdc") elif self._cryptocurrency.SYMBOL in ["TRX"]: - keccak_256 = sha3.keccak_256() + keccak_256 = keccak.new(digest_bits=256) keccak_256.update(unhexlify(self.uncompressed())) address = keccak_256.hexdigest()[24:] network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + bytearray.fromhex(address) diff --git a/hdwallet/libs/base58.py b/hdwallet/libs/base58.py index c025f8b..cc94f13 100644 --- a/hdwallet/libs/base58.py +++ b/hdwallet/libs/base58.py @@ -2,7 +2,7 @@ from hashlib import sha256 -import sha3 +from Crypto.Hash import keccak import six @@ -13,10 +13,10 @@ def checksum_encode(address, crypto="eth"): out = "" - keccak = sha3.keccak_256() + keccak_256 = keccak.new(digest_bits=256) addr = address.lower().replace("0x", "") if crypto == "eth" else address.lower().replace("xdc", "") - keccak.update(addr.encode("ascii")) - hash_addr = keccak.hexdigest() + keccak_256.update(addr.encode("ascii")) + hash_addr = keccak_256.hexdigest() for i, c in enumerate(addr): if int(hash_addr[i], 16) >= 8: out += c.upper() diff --git a/requirements.txt b/requirements.txt index 477b76d..5887f80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ ecdsa>=0.13,<1 mnemonic>=0.19,<1 -pysha3>=1.0.2,<2 -base58>=2.0.1,<3 \ No newline at end of file +pycryptodome>=3.15,<4 +base58>=2.0.1,<3 diff --git a/setup.py b/setup.py index 9b70d38..74afc4e 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="hdwallet", - version="v2.1.1", + version="v2.1.2", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, diff --git a/tests/test_base58.py b/tests/test_base58.py index f321e76..4c696b5 100644 --- a/tests/test_base58.py +++ b/tests/test_base58.py @@ -7,7 +7,7 @@ import pytest from hdwallet.libs.base58 import ( - check_encode, check_decode, decode, encode, string_to_int + checksum_encode, check_encode, check_decode, decode, encode, string_to_int ) @@ -35,3 +35,54 @@ def test_base58(): assert encode(decode("111233QC4")) == "111233QC4" + + # Ensure ETH address checksums are correct; these are Keccak hash of the lower-case hex address, + # with hash results mapped onto the upper/lower case bits of the address. + eth = "0xfc2077CA7F403cBECA41B1B0F62D91B5EA631B5E" + eth_lower = eth.lower() + eth_check = checksum_encode( eth_lower ) + assert eth_check == eth + + +def test_keccak(): + """Keccak 256 hash is required by several crypto algorithms. Ensure our hash implementations + are correct. + + From: https://cryptobook.nakov.com/cryptographic-hash-functions/hash-functions-examples + + """ + import hashlib, binascii + + text = 'hello' + data = text.encode("utf8") + + sha256hash = hashlib.sha256(data).digest() + print("SHA-256: ", binascii.hexlify(sha256hash)) + assert binascii.hexlify(sha256hash) == b'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' + + sha3_256 = hashlib.sha3_256(data).digest() + print("SHA3-256: ", binascii.hexlify(sha3_256)) + assert binascii.hexlify(sha3_256) == b'3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392' + + blake2s = hashlib.new('blake2s', data).digest() + print("BLAKE2s: ", binascii.hexlify(blake2s)) + assert binascii.hexlify(blake2s) == b'19213bacc58dee6dbde3ceb9a47cbb330b3d86f8cca8997eb00be456f140ca25' + + ripemd160 = hashlib.new('ripemd160', data).digest() + print("RIPEMD-160:", binascii.hexlify(ripemd160)) + assert binascii.hexlify(ripemd160) == b'108f07b8382412612c048d07d13f814118445acd' + + # # Old pysha3 (deprecated) keccak-256 implementation + # from sha3 import keccak_256 + # keccak256 = keccak_256() + # keccak256.update(data) + # keccak256_sha3 = keccak256.digest() + # print("Keccak256:", binascii.hexlify(keccak256_sha3), "(pysha3)") + # assert binascii.hexlify(keccak256_sha3) == b'1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8' + + # New pycryptodome keccak-256 implementation + from Crypto.Hash import keccak + keccak256_pycryptodome = keccak.new(data=data, digest_bits=256).digest() + print("Keccak256:", binascii.hexlify(keccak256_pycryptodome), " (pycryptodome)") + assert binascii.hexlify(keccak256_pycryptodome) == b'1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8' +