diff --git a/README.md b/README.md index 406156c5..7775906d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Coverage Status](https://coveralls.io/repos/github/meherett/python-hdwallet/badge.svg?branch=master)](https://coveralls.io/github/meherett/python-hdwallet?branch=master) Python-based library for the implementation of a hierarchical deterministic wallet generator for more than 140+ multiple cryptocurrencies. -It allows the handling of multiple coins, multiple accounts, external and internal chains per account and millions of addresses per the chain. +It allows the handling of multiple coins, multiple accounts, external and internal chains per account and millions of addresses per chain. For more info see the BIP specs. @@ -19,8 +19,8 @@ For more info see the BIP specs. | [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) | Hierarchical Deterministic Wallets | | [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) | Multi-Account Hierarchy for Deterministic Wallets | | [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) | Derivation scheme for P2WPKH-nested-in-P2SH based accounts | -| [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki) | Derivation scheme for P2WPKH based accounts | -| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki) | Segregated Witness (Consensus layer) | +| [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) | Derivation scheme for P2WPKH based accounts | +| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) | Segregated Witness (Consensus layer) | ## Installation @@ -168,7 +168,7 @@ for address_index in range(10): ```shell script Mnemonic: bright demand olive glance crater key head glory quantum leisure intact age -Base HD Path: m/44'/60'/0'/0/{address_index} +Base HD Path: m/44'/60'/0'/0/{address_index} (0) m/44'/60'/0'/0/0 0x3a149f0c5dc5c0F1E29e573215C23710dE9c4f87 0xa45f9af43912fdd5e88c492226be082029f257681d4b3e73b68be535d2fb0526 (1) m/44'/60'/0'/0/1 0x9e8A4fD9bA74DbB0c7F465EF56b47489793AA102 0x6e5ab2a3ae20c7b3a1c0645b03689e88e8cdff16f6a39d6a420bfebc20e8a941 @@ -185,18 +185,37 @@ Base HD Path: m/44'/60'/0'/0/{address_index} [Click this to see more examples :)](https://github.com/meherett/python-hdwallet/blob/master/examples) +## Development + +To get started, just fork this repo, clone it locally, and run: + +``` +pip install -e .[tests,docs] -r requirements.txt +``` + +## Testing + +You can run the tests with: + +``` +pytest +``` + +Or use `tox` to run the complete suite against the full set of build targets, or pytest to run specific +tests against a specific version of Python. + ## Contributing -Feel free to open an [issue](https://github.com/meherett/hdwallet/issues) if you find a problem, -or a pull request if you've solved an issue. And also any help in testing, development, -documentation and other tasks is highly appreciated and useful to the project. +Feel free to open an [issue](https://github.com/meherett/hdwallet/issues) if you find a problem, +or a pull request if you've solved an issue. And also any help in testing, development, +documentation and other tasks is highly appreciated and useful to the project. There are tasks for contributors of all experience levels. For more information, see the [CONTRIBUTING.md](https://github.com/meherett/hdwallet/blob/master/CONTRIBUTING.md) file. ## Available Cryptocurrencies -This library simplifies the process of creating a new HDWallet's for: +This library simplifies the process of creating a new hierarchical deterministic wallets for: | Cryptocurrencies | Symbols | Mainnet | Testnet | Segwit | Coin Type | Default Paths | | :---------------------------------------------------------------- | :------------------: | :-----: | :-----: | :----: | :-------: | :------------------: | @@ -337,24 +356,17 @@ This library simplifies the process of creating a new HDWallet's for: | XUEZ | `XUEZ` | Yes | No | No | 225 | `m/44'/225'/0'/0/0` | | [XinFin](https://github.com/XinFinOrg/XDPoSChain) | `XDC` | Yes | No | Yes | 550 | `m/44'/550'/0'/0/0` | | ZClassic | `ZCL` | Yes | No | No | 147 | `m/44'/147'/0'/0/0` | -| Zcash | `ZEC` | Yes | No | No | 133 | `m/44'/133'/0'/0/0` | +| [Zcash](https://github.com/zcash/zcash) | `ZEC`, `ZECTEST` | Yes | Yes | No | 133 | `m/44'/133'/0'/0/0` | | Zencash | `ZEN` | Yes | No | No | 121 | `m/44'/121'/0'/0/0` | ## Donations -If You found this tool helpful consider making a donation: - -Ethereum (ETH) or Tether (USDT-ERC20) address: +If You found this tool helpful consider making a donation: -```text -0x342798bbe9731a91e0557fa8ab0bce1eae6d6ae3 -``` - -Bitcoin (BTC) address: - -```text -3GGNPvgbSpMHShcaZJGDXQn5wUJyTz7uoC -``` +| Coins | Addresses | +| ----------------------------- | :----------------------------------------: | +| Bitcoin `BTC` | 3GGNPvgbSpMHShcaZJGDXQn5wUJyTz7uoC | +| Ethereum `ETH`, Tether `USDT` | 0x342798bbe9731a91e0557fa8ab0bce1eae6d6ae3 | ## License diff --git a/docs/conf.py b/docs/conf.py index 187c00e7..0c5a95ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ author = "Meheret Tesfaye" # The full version, including alpha/beta/rc tags -release = "1.3.0" +release = "1.3.2" # The master toctree document. master_doc = "toctree" diff --git a/docs/cryptocurrencies.rst b/docs/cryptocurrencies.rst index 81650354..cad1f47e 100644 --- a/docs/cryptocurrencies.rst +++ b/docs/cryptocurrencies.rst @@ -276,6 +276,13 @@ This library simplifies the process of generating a new HDWallet's for: - No - 116 - m/44'/116'/0'/0/0 + * - `DeSo `_ + - DESO, DESOTEST + - Yes + - Yes + - Yes + - 0 + - m/44'/0'/0'/0/0 * - Diamond - DMD - Yes diff --git a/docs/index.rst b/docs/index.rst index b59b75ce..66289d89 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,5 +26,5 @@ For more info see the BIP specs. - Derivation scheme for P2WPKH-nested-in-P2SH based accounts * - `BIP84 `_ - Derivation scheme for P2WPKH based accounts - * - `BIP141 `_ + * - `BIP141 `_ - Segregated Witness (Consensus layer) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index ce4c736e..68a9e070 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -7,3 +7,5 @@ __all__ = [ "HDWallet", "BIP32HDWallet", "BIP44HDWallet", "BIP49HDWallet", "BIP84HDWallet", "BIP141HDWallet" ] + +__version__ = "1.3.2" diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 1099f4b5..baff6d16 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -62,16 +62,19 @@ class Cryptocurrency(NestedNamespace): SOURCE_CODE: Optional[str] COIN_TYPE: CoinType - SCRIPT_ADDRESS: int - PUBLIC_KEY_ADDRESS: int - SEGWIT_ADDRESS: SegwitAddress + SCRIPT_ADDRESS: Optional[int] = None + PUBLIC_KEY_ADDRESS: int = 0 + PRIVATE_KEY_ADDRESS: int = 0 + SEGWIT_ADDRESS: Optional[SegwitAddress] = None EXTENDED_PRIVATE_KEY: ExtendedPrivateKey EXTENDED_PUBLIC_KEY: ExtendedPublicKey MESSAGE_PREFIX: Optional[str] DEFAULT_PATH: str - WIF_SECRET_KEY: int + WIF_SECRET_KEY: Optional[int] = None + + DEFAULT_SEMANTIC: str = "p2pkh" class AnonMainnet(Cryptocurrency): @@ -712,6 +715,7 @@ class BitcoinMainnet(Cryptocurrency): MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 + DEFAULT_SEMANTIC = "p2wpkh" class BitcoinPlusMainnet(Cryptocurrency): @@ -832,6 +836,7 @@ class BitcoinTestnet(Cryptocurrency): MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef + DEFAULT_SEMANTIC = "p2wpkh" class BitcoinZMainnet(Cryptocurrency): @@ -1674,6 +1679,56 @@ class DenariusMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x9e +class DeSoMainnet(Cryptocurrency): + + NAME = "DeSo" + SYMBOL = "DESO" + NETWORK = "mainnet" + SOURCE_CODE = "https://github.com/deso-protocol" + COIN_TYPE = CoinType({ + "INDEX": 0, + "HARDENED": True + }) + + PUBLIC_KEY_ADDRESS = 0xCD1400 + PRIVATE_KEY_ADDRESS = 0x350000 + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "BASE58CHECK": 0x0488ade4 + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "BASE58CHECK": 0x0488b21e + }) + + MESSAGE_PREFIX = "\x18DeSo Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + DEFAULT_SEMANTIC = "base58check" + + +class DeSoTestnet(Cryptocurrency): + + NAME = "DeSo" + SYMBOL = "DESOTEST" + NETWORK = "testnet" + SOURCE_CODE = "https://github.com/deso-protocol" + COIN_TYPE = CoinType({ + "INDEX": 0, + "HARDENED": True + }) + + PUBLIC_KEY_ADDRESS = 0x11C200 + PRIVATE_KEY_ADDRESS = 0x4F061B + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "BASE58CHECK": 0x04358394 + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "BASE58CHECK": 0x043587cf + }) + + MESSAGE_PREFIX = "\x18DeSo Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + DEFAULT_SEMANTIC = "base58check" + + class DiamondMainnet(Cryptocurrency): NAME = "Diamond" @@ -3213,18 +3268,18 @@ class LitecoinMainnet(Cryptocurrency): }) EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ - "P2PKH": 0x019d9cfe, - "P2SH": 0x019d9cfe, - "P2WPKH": 0x04b2430c, - "P2WPKH_IN_P2SH": 0x01b26792, + "P2PKH": 0x488ade4, + "P2SH": 0x488ade4, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, "P2WSH": None, "P2WSH_IN_P2SH": None }) EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ - "P2PKH": 0x019da462, - "P2SH": 0x019da462, - "P2WPKH": 0x04b24746, - "P2WPKH_IN_P2SH": 0x01b26ef6, + "P2PKH": 0x488b21e, + "P2SH": 0x488b21e, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, "P2WSH": None, "P2WSH_IN_P2SH": None }) @@ -3245,33 +3300,33 @@ class LitecoinTestnet(Cryptocurrency): "HARDENED": True }) - SCRIPT_ADDRESS = 0xc4 + SCRIPT_ADDRESS = 0x3a PUBLIC_KEY_ADDRESS = 0x6f SEGWIT_ADDRESS = SegwitAddress({ - "HRP": "litecointestnet", + "HRP": "tltc", "VERSION": 0x00 }) EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ - "P2PKH": 0x0436ef7d, - "P2SH": 0x0436ef7d, - "P2WPKH": 0x04358394, - "P2WPKH_IN_P2SH": 0x04358394, + "P2PKH": 0x04358394, + "P2SH": 0x04358394, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, "P2WSH": None, "P2WSH_IN_P2SH": None }) EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ - "P2PKH": 0x0436f6e1, - "P2SH": 0x0436f6e1, - "P2WPKH": 0x043587cf, - "P2WPKH_IN_P2SH": 0x043587cf, + "P2PKH": 0x043587cf, + "P2SH": 0x043587cf, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, "P2WSH": None, "P2WSH_IN_P2SH": None }) MESSAGE_PREFIX = "\x19Litecoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" - WIF_SECRET_KEY = 0xb0 + WIF_SECRET_KEY = 0xef class LitecoinZMainnet(Cryptocurrency): @@ -6239,7 +6294,7 @@ class ZcashMainnet(Cryptocurrency): NAME = "Zcash" SYMBOL = "ZEC" NETWORK = "mainnet" - SOURCE_CODE = None + SOURCE_CODE = "https://github.com/zcash/zcash" COIN_TYPE = CoinType({ "INDEX": 133, "HARDENED": True @@ -6274,6 +6329,46 @@ class ZcashMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x80 +class ZcashTestnet(Cryptocurrency): + + NAME = "Zcash" + SYMBOL = "ZECTEST" + NETWORK = "testnet" + SOURCE_CODE = "https://github.com/zcash/zcash" + COIN_TYPE = CoinType({ + "INDEX": 1, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x1cba + PUBLIC_KEY_ADDRESS = 0x1d25 + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": None, + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x4358394, + "P2SH": 0x4358394, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x43587cf, + "P2SH": 0x43587cf, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + + MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0xef + + class ZencashMainnet(Cryptocurrency): NAME = "Zencash" diff --git a/hdwallet/derivations.py b/hdwallet/derivations.py index 9a49013e..db932c2e 100644 --- a/hdwallet/derivations.py +++ b/hdwallet/derivations.py @@ -510,7 +510,7 @@ class BIP84Derivation(BIP32Derivation): >>> BIP84Derivation(cryptocurrency=BitcoinMainnet) >>> str(BIP84Derivation(cryptocurrency=BitcoinMainnet)) - "m/49'/0'/0'/0/0" + "m/84'/0'/0'/0/0" """ PURPOSE: int = 84 @@ -547,7 +547,7 @@ class BIP141Derivation(Derivation): >>> BIP141Derivation(cryptocurrency=BitcoinMainnet) >>> str(BIP141Derivation(cryptocurrency=BitcoinMainnet)) - "m/49'/0'/0'/0/0" + "m/44'/0'/0'/0/0" """ def __init__(self, cryptocurrency: Any, path: Union[str, Derivation] = None, semantic: str = "p2wpkh"): diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 5e226b7f..721714e5 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -67,7 +67,7 @@ class HDWallet: :type symbol: str :param cryptocurrency: Cryptocurrency instance, defaults to ``None``. :type cryptocurrency: Cryptocurrency - :param semantic: Extended semantic, defaults to ``P2PKH``. + :param semantic: Extended semantic :type semantic: str :param use_default_path: Use default derivation path, defaults to ``False``. :type use_default_path: bool @@ -79,7 +79,7 @@ class HDWallet: """ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, - semantic: str = "p2pkh", use_default_path: bool = False): + semantic: str = None, use_default_path: bool = False): self._cryptocurrency: Any = None if cryptocurrency: if not issubclass(cryptocurrency, Cryptocurrency): @@ -88,6 +88,9 @@ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, else: self._cryptocurrency: Any = get_cryptocurrency(symbol=symbol) + if semantic is None: + semantic = self._cryptocurrency.DEFAULT_SEMANTIC + self._strength: Optional[int] = None self._entropy: Optional[str] = None self._mnemonic: Optional[str] = None @@ -112,6 +115,10 @@ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, self._private_key: Optional[bytes] = None self._public_key: Optional[str] = None self._chain_code: Optional[bytes] = None + + self._private_key_base58check: Optional[str] = None + self._public_key_base58check: Optional[str] = None + self._depth: int = 0 self._index: int = 0 @@ -211,6 +218,8 @@ def from_seed(self, seed: str) -> "HDWallet": if self._use_default_path: self.from_path(path=self._cryptocurrency.DEFAULT_PATH) self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() + self._private_key_base58check = self.private_key_base58check() if self._from_class: self.from_path(path=self._path_class) return self @@ -251,6 +260,8 @@ def from_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDW if self._from_class: self.from_path(path=self._path_class) self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() + self._private_key_base58check = self.private_key_base58check() return self def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWallet": @@ -289,6 +300,7 @@ def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWal if self._from_class: self.from_path(path=self._path_class) self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() return self def from_xprivate_key(self, xprivate_key: str) -> "HDWallet": @@ -317,6 +329,8 @@ def from_xprivate_key(self, xprivate_key: str) -> "HDWallet": self._key = ecdsa.SigningKey.from_string(_deserialize_xprivate_key[5], curve=SECP256k1) self._verified_key = self._key.get_verifying_key() self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() + self._private_key_base58check = self.private_key_base58check() return self def from_xpublic_key(self, xpublic_key: str) -> "HDWallet": @@ -346,6 +360,7 @@ def from_xpublic_key(self, xpublic_key: str) -> "HDWallet": _deserialize_xpublic_key[5], curve=SECP256k1 ) self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() return self def from_wif(self, wif: str) -> "HDWallet": @@ -372,6 +387,8 @@ def from_wif(self, wif: str) -> "HDWallet": self._key = ecdsa.SigningKey.from_string(self._private_key, curve=SECP256k1) self._verified_key = self._key.get_verifying_key() self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() + self._private_key_base58check = self.private_key_base58check() return self def from_private_key(self, private_key: str) -> "HDWallet": @@ -394,6 +411,8 @@ def from_private_key(self, private_key: str) -> "HDWallet": self._key = ecdsa.SigningKey.from_string(self._private_key, curve=SECP256k1) self._verified_key = self._key.get_verifying_key() self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() + self._private_key_base58check = self.private_key_base58check() return self def from_public_key(self, public_key: str) -> "HDWallet": @@ -416,6 +435,7 @@ def from_public_key(self, public_key: str) -> "HDWallet": unhexlify(public_key), curve=SECP256k1 ) self._public_key = self.compressed() + self._public_key_base58check = self.public_key_base58check() return self def from_path(self, path: Union[str, Derivation]) -> "HDWallet": @@ -484,8 +504,12 @@ def from_index(self, index: int, hardened: bool = False) -> "HDWallet": def _derive_key_by_index(self, index) -> Optional["HDWallet"]: - if not self._root_private_key and not self._root_public_key: - raise PermissionError("You can't drive this master key.") + if not self._public_key \ + and not self._chain_code \ + and not self._depth \ + and not self._index \ + and not self._parent_fingerprint: + raise ValueError("You can't drive this master key.") i_str = struct.pack(">L", index) if index & BIP32KEY_HARDEN: @@ -517,6 +541,7 @@ def _derive_key_by_index(self, index) -> Optional["HDWallet"]: ) self._key = ecdsa.SigningKey.from_string(self._private_key, curve=SECP256k1) self._verified_key = self._key.get_verifying_key() + self._private_key_base58check = self.private_key_base58check() else: key_point = S256Point.parse(unhexlify(self.public_key())) left_point = il_int * G @@ -719,13 +744,22 @@ def clean_derivation(self) -> "HDWallet": None """ - if self._i: + if self._root_private_key: self._path, self._depth, self._parent_fingerprint, self._index = ( "m", 0, b"\0\0\0\0", 0 ) - self._private_key, self._chain_code = self._i[:32], self._i[32:] + self._private_key, self._chain_code = self._root_private_key self._key = ecdsa.SigningKey.from_string(self._private_key, curve=SECP256k1) self._verified_key = self._key.get_verifying_key() + self._private_key_base58check = self.private_key_base58check() + elif self._root_public_key: + self._path, self._depth, self._parent_fingerprint, self._index = ( + "m", 0, b"\0\0\0\0", 0 + ) + self._chain_code = self._root_public_key[1] + self._verified_key = ecdsa.VerifyingKey.from_string( + self._root_public_key[0], curve=SECP256k1 + ) return self def uncompressed(self, compressed: Optional[str] = None) -> str: @@ -835,6 +869,21 @@ def public_key(self, compressed: bool = True, private_key: Optional[str] = None) return hexlify(ck).decode() if compressed else self.uncompressed(compressed=hexlify(ck).decode()) return self.compressed() if compressed else self.uncompressed() + def public_key_base58check(self) -> str: + return base58.b58encode_check( + _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + + unhexlify(self.public_key()) + ).decode() + + def private_key_base58check(self) -> str: + return base58.b58encode_check( + _unhexlify(self._cryptocurrency.PRIVATE_KEY_ADDRESS) + + unhexlify(self.private_key()) + ).decode() if self.private_key() is not None else None + + def base58check_address(self) -> str: + return self.public_key_base58check() + def strength(self) -> Optional[int]: """ Get Entropy strength. @@ -1099,7 +1148,7 @@ def p2pkh_address(self) -> str: network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + public_key_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) - def p2sh_address(self) -> str: + def p2sh_address(self) -> Optional[str]: """ Get Pay to Script Hash (P2SH) address. @@ -1114,6 +1163,9 @@ def p2sh_address(self) -> str: "3Jp6ad4ErhibQmhSRfavbPRiUyg2xTTT4j" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).hexdigest() public_key_hash_script = unhexlify("76a914" + public_key_hash + "88ac") @@ -1138,6 +1190,8 @@ def p2wpkh_address(self) -> Optional[str]: compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).digest() + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, public_key_hash)) @@ -1157,10 +1211,15 @@ def p2wpkh_in_p2sh_address(self) -> Optional[str]: "3CCrxPrHNa6ePbnB7qjh7S3vaPx9qiLc3e" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).hexdigest() script_hash = hashlib.new('ripemd160', sha256(unhexlify("0014" + public_key_hash)).digest()).digest() network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1182,6 +1241,8 @@ def p2wsh_address(self) -> Optional[str]: compressed_public_key = unhexlify("5121" + self.compressed() + "51ae") script_hash = sha256(compressed_public_key).digest() + if self._cryptocurrency.SEGWIT_ADDRESS is None: + return None if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, script_hash)) @@ -1201,6 +1262,9 @@ def p2wsh_in_p2sh_address(self) -> Optional[str]: "38YMonfh2yLFRViLrM2kdvZx8ctcp1vbbV" """ + if self._cryptocurrency.SCRIPT_ADDRESS is None: + return None + compressed_public_key = unhexlify("5121" + self.compressed() + "51ae") script_hash = unhexlify("0020" + sha256(compressed_public_key).hexdigest()) script_hash = hashlib.new('ripemd160', sha256(script_hash).digest()).digest() @@ -1209,6 +1273,11 @@ def p2wsh_in_p2sh_address(self) -> Optional[str]: return None return ensure_string(base58.b58encode_check(network_hash160_bytes)) + def address(self, semantic=None): + if semantic == None: + semantic = self._cryptocurrency.DEFAULT_SEMANTIC + return getattr(self, f"{semantic}_address")() + def wif(self) -> Optional[str]: """ Get Wallet Important Format. @@ -1224,6 +1293,9 @@ def wif(self) -> Optional[str]: "KzsHWUJsrTWUUhBGPfMMxLLydiH7NhEn6z7mKHXD5qNkUWaC4TEn" """ + if self._cryptocurrency.WIF_SECRET_KEY is None: + return + return check_encode(_unhexlify(self._cryptocurrency.WIF_SECRET_KEY) + self._key.to_string() + b"\x01") if self._key else None def dumps(self) -> dict: @@ -1269,6 +1341,8 @@ def dumps(self) -> dict: semantic=self.semantic(), path=self.path(), hash=self.hash(), + public_key_base58check=self.public_key_base58check(), + private_key_base58check=self.private_key_base58check(), addresses=dict( p2pkh=self.p2pkh_address(), p2sh=self.p2sh_address(), @@ -1276,7 +1350,8 @@ def dumps(self) -> dict: p2wpkh_in_p2sh=self.p2wpkh_in_p2sh_address(), p2wsh=self.p2wsh_address(), p2wsh_in_p2sh=self.p2wsh_in_p2sh_address() - ) + ), + address=self.address() ) diff --git a/hdwallet/libs/base58.py b/hdwallet/libs/base58.py index 98f0a39b..c025f8b5 100644 --- a/hdwallet/libs/base58.py +++ b/hdwallet/libs/base58.py @@ -67,15 +67,18 @@ def decode(data): data = bytes(data, "ascii") val = 0 - for (i, c) in enumerate(data[::-1]): - val += __base58_alphabet_bytes.find(c) * (__base58_radix ** i) + prefix = 0 + for c in data: + val = (val * __base58_radix) + __base58_alphabet_bytes.find(c) + if val == 0: + prefix += 1 dec = bytearray() - while val >= 256: + while val > 0: val, mod = divmod(val, 256) dec.append(mod) - if val: - dec.append(val) + + dec.extend(bytearray(prefix)) return bytes(dec[::-1]) diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index e90c4621..2eae6344 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -74,6 +74,8 @@ DFC = "DFC" # Denarius DNR = "DNR" +# DeSo +DESO, DESOTEST = "DESO", "DESOTEST" # Diamond DMD = "DMD" # Digi Byte @@ -275,7 +277,7 @@ # ZClassic ZCL = "ZCL" # Zcash -ZEC = "ZEC" +ZEC, ZECTEST = "ZEC", "ZECTEST" # Zencash ZEN = "ZEN" @@ -316,6 +318,7 @@ "CRAVE", "DASH", "DASHTEST", "ONION", + "DESO", "DESOTEST", "DFC", "DNR", "DMD", @@ -418,6 +421,6 @@ "XUEZ", "XDC", "ZCL", - "ZEC", + "ZEC", "ZECTEST", "ZEN" ] diff --git a/setup.py b/setup.py index 62190794..8d174557 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="hdwallet", - version="1.3.0", + version="1.3.2", description="Python-based library for the implementation of a " "hierarchical deterministic wallet generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, @@ -23,7 +23,7 @@ author="Meheret Tesfaye", author_email="meherett@zoho.com", url="https://github.com/meherett/python-hdwallet", - keywords=["cryptography", "hd", "bip32", "bip44", "bip39", "wallet", "cryptocurrencies"], + keywords=["cryptography", "hd", "bip32", "bitcoin", "bip44", "bip39", "wallet", "hdwallet", "cryptocurrencies"], python_requires=">=3.6,<4", packages=find_packages(), install_requires=requirements, diff --git a/tests/test_base58.py b/tests/test_base58.py index 2b602c08..f321e765 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, string_to_int + check_encode, check_decode, decode, encode, string_to_int ) @@ -29,3 +29,9 @@ def test_base58(): with pytest.raises(TypeError, match="string argument without an encoding"): assert string_to_int(str("meherett")) + + + assert decode("111233QC4") == b'\x00\x00\x00(\x7f\xb4\xcd' + + + assert encode(decode("111233QC4")) == "111233QC4" diff --git a/tests/test_from_xpublic_key.py b/tests/test_from_xpublic_key.py index d8b90551..b0225958 100644 --- a/tests/test_from_xpublic_key.py +++ b/tests/test_from_xpublic_key.py @@ -4,6 +4,7 @@ import os from hdwallet import HDWallet +from hdwallet.utils import generate_entropy # Test Values base_path: str = os.path.dirname(__file__) @@ -81,3 +82,11 @@ def test_from_xpublic_key(): del dumps["xpublic_key_hex"] assert hdwallet.dumps() == dumps + +def test_derivation_from_xpublic_key(): + hdwallet: HDWallet = HDWallet().from_entropy(generate_entropy()) + wallet1: HDWallet = hdwallet.from_path("m/1'/2'/3'") + xpub: str = wallet1.xpublic_key() + wallet2: HDWallet = HDWallet().from_xpublic_key(xpub) + assert wallet1.xpublic_key() == wallet2.xpublic_key() + assert wallet1.from_path("m/1/2/3").xpublic_key() == wallet2.from_path("m/1/2/3").xpublic_key() diff --git a/tests/test_symbols.py b/tests/test_symbols.py new file mode 100644 index 00000000..86413d6f --- /dev/null +++ b/tests/test_symbols.py @@ -0,0 +1,166 @@ +# !/usr/bin/env python3 + +from hdwallet.symbols import * + + +def test_symbols(): + + assert ANON == "ANON" + assert AGM == "AGM" + assert XAX == "XAX" + assert AYA == "AYA" + assert AC == "AC" + assert ATOM == "ATOM" + assert AUR == "AUR" + assert AXE == "AXE" + assert BTA == "BTA" + assert BEET == "BEET" + assert BELA == "BELA" + assert BTDX == "BTDX" + assert BSD == "BSD" + assert BCH == "BCH" + assert BTG == "BTG" + assert BTC == "BTC" + assert BTCTEST == "BTCTEST" + assert XBC == "XBC" + assert BSV == "BSV" + assert BTCZ == "BTCZ" + assert BTX == "BTX" + assert BLK == "BLK" + assert BST == "BST" + assert BND == "BND" + assert BNDTEST == "BNDTEST" + assert BOLI == "BOLI" + assert BRIT == "BRIT" + assert CPU == "CPU" + assert CDN == "CDN" + assert CCN == "CCN" + assert CLAM == "CLAM" + assert CLUB == "CLUB" + assert CMP == "CMP" + assert CRP == "CRP" + assert CRAVE == "CRAVE" + assert DASH == "DASH" + assert DASHTEST == "DASHTEST" + assert ONION == "ONION" + assert DFC == "DFC" + assert DNR == "DNR" + assert DESO == "DESO" + assert DESOTEST == "DESOTEST" + assert DMD == "DMD" + assert DGB == "DGB" + assert DGC == "DGC" + assert DOGE == "DOGE" + assert DOGETEST == "DOGETEST" + assert EDRC == "EDRC" + assert ECN == "ECN" + assert EMC2 == "EMC2" + assert ELA == "ELA" + assert NRG == "NRG" + assert ETH == "ETH" + assert ERC == "ERC" + assert EXCL == "EXCL" + assert FIX == "FIX" + assert FIXTEST == "FIXTEST" + assert FTC == "FTC" + assert FRST == "FRST" + assert FLASH == "FLASH" + assert FJC == "FJC" + assert GCR == "GCR" + assert GAME == "GAME" + assert GBX == "GBX" + assert GRC == "GRC" + assert GRS == "GRS" + assert GRSTEST == "GRSTEST" + assert NLG == "NLG" + assert HNC == "HNC" + assert THC == "THC" + assert HUSH == "HUSH" + assert IXC == "IXC" + assert INSN == "INSN" + assert IOP == "IOP" + assert JBS == "JBS" + assert KOBO == "KOBO" + assert KMD == "KMD" + assert LBC == "LBC" + assert LINX == "LINX" + assert LCC == "LCC" + assert LTC == "LTC" + assert LTCTEST == "LTCTEST" + assert LTZ == "LTZ" + assert LKR == "LKR" + assert LYNX == "LYNX" + assert MZC == "MZC" + assert MEC == "MEC" + assert MNX == "MNX" + assert MONA == "MONA" + assert MONK == "MONK" + assert XMY == "XMY" + assert NIX == "NIX" + assert NMC == "NMC" + assert NAV == "NAV" + assert NEBL == "NEBL" + assert NEOS == "NEOS" + assert NRO == "NRO" + assert NYC == "NYC" + assert NVC == "NVC" + assert NBT == "NBT" + assert NSR == "NSR" + assert OK == "OK" + assert OMNI == "OMNI" + assert OMNITEST == "OMNITEST" + assert ONX == "ONX" + assert PPC == "PPC" + assert PSB == "PSB" + assert PHR == "PHR" + assert PINK == "PINK" + assert PIVX == "PIVX" + assert PIVXTEST == "PIVXTEST" + assert POSW == "POSW" + assert POT == "POT" + assert PRJ == "PRJ" + assert PUT == "PUT" + assert QTUM == "QTUM" + assert QTUMTEST == "QTUMTEST" + assert RBTC == "RBTC" + assert RBTCTEST == "RBTCTEST" + assert RPD == "RPD" + assert RVN == "RVN" + assert RDD == "RDD" + assert RBY == "RBY" + assert SAFE == "SAFE" + assert SLS == "SLS" + assert SCRIBE == "SCRIBE" + assert SDC == "SDC" + assert SDCTEST == "SDCTEST" + assert SLM == "SLM" + assert SLMTEST == "SLMTEST" + assert SMLY == "SMLY" + assert SLR == "SLR" + assert STASH == "STASH" + assert STRAT == "STRAT" + assert STRATTEST == "STRATTEST" + assert SUGAR == "SUGAR" + assert SUGARTEST == "SUGARTEST" + assert SYS == "SYS" + assert TOA == "TOA" + assert THT == "THT" + assert TWINS == "TWINS" + assert TWINSTEST == "TWINSTEST" + assert USC == "USC" + assert UNO == "UNO" + assert VASH == "VASH" + assert VC == "VC" + assert XVG == "XVG" + assert VTC == "VTC" + assert VIA == "VIA" + assert VIATEST == "VIATEST" + assert VIVO == "VIVO" + assert XWC == "XWC" + assert WC == "WC" + assert XUEZ == "XUEZ" + assert XDC == "XDC" + assert ZCL == "ZCL" + assert ZEC == "ZEC" + assert ZECTEST == "ZECTEST" + assert ZEN == "ZEN"