From 3869c07711c8dfa03d100e3abfe2cce65809429b Mon Sep 17 00:00:00 2001 From: meherett Date: Fri, 11 Jun 2021 22:52:06 +0300 Subject: [PATCH 01/92] Update: Testing doc and added development doc. --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6893e2e..0d7aed3 100644 --- a/README.md +++ b/README.md @@ -185,15 +185,24 @@ 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) -## Running test cases -Install the requirements -```python -pip install -e .[tests] -r requirements.txt +## Development + +To get started, just fork this repo, clone it locally, and run: + ``` -Run -```python -pytest tests/ +$ 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 From e630169666c012fc40cb15290711efdeadbff0ca Mon Sep 17 00:00:00 2001 From: meherett Date: Fri, 18 Jun 2021 17:11:01 +0300 Subject: [PATCH 02/92] Add: Root xpublic key derivation cleaner. --- hdwallet/hdwallet.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 5e226b7..369d213 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -719,13 +719,21 @@ 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() + 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: From 98b15fd9acba32877869b854f809c8ce01afcc97 Mon Sep 17 00:00:00 2001 From: meherett Date: Fri, 18 Jun 2021 17:15:07 +0300 Subject: [PATCH 03/92] Bump: HDWallet from v1.3.0 to v1.3.1 package. --- docs/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 187c00e..6b42da8 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.1" # The master toctree document. master_doc = "toctree" diff --git a/setup.py b/setup.py index 6219079..29fde43 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="hdwallet", - version="1.3.0", + version="1.3.1", description="Python-based library for the implementation of a " "hierarchical deterministic wallet generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, From 749bf8c702dacb3b4e8d0cf78164aeb1f1ae5d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Hol=C3=BD?= Date: Tue, 22 Jun 2021 12:31:26 +0200 Subject: [PATCH 04/92] Fix: Incorrect output of Base58 decoding. --- hdwallet/libs/base58.py | 13 ++++++++----- tests/test_base58.py | 8 +++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/hdwallet/libs/base58.py b/hdwallet/libs/base58.py index 98f0a39..c025f8b 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/tests/test_base58.py b/tests/test_base58.py index 2b602c0..f321e76 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" From fcb16c730d62decb50d6d006487db3520d1cf9ea Mon Sep 17 00:00:00 2001 From: saloppe73 Date: Thu, 22 Jul 2021 03:19:35 +0200 Subject: [PATCH 05/92] add ZECTEST --- hdwallet/cryptocurrencies.py | 40 ++++++++++++++++++++++++++++++++++++ hdwallet/symbols.py | 4 ++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 1099f4b..cadcc6b 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -6274,6 +6274,46 @@ class ZcashMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x80 +class ZcashTestnet(Cryptocurrency): + + NAME = "Zcash" + SYMBOL = "ZECTEST" + NETWORK = "testnet" + SOURCE_CODE = "https://github.com/zcash/zcashn" + 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/symbols.py b/hdwallet/symbols.py index e90c462..be3a03d 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -275,7 +275,7 @@ # ZClassic ZCL = "ZCL" # Zcash -ZEC = "ZEC" +ZEC, ZECTEST = "ZEC", "ZECTEST" # Zencash ZEN = "ZEN" @@ -418,6 +418,6 @@ "XUEZ", "XDC", "ZCL", - "ZEC", + "ZEC", "ZECTEST" "ZEN" ] From 697e9e72b2ca79cd8723807d947040a7fef3fe0a Mon Sep 17 00:00:00 2001 From: saloppe73 Date: Thu, 22 Jul 2021 03:33:12 +0200 Subject: [PATCH 06/92] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d7aed3..48cca63 100644 --- a/README.md +++ b/README.md @@ -356,7 +356,7 @@ 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 From b876ad42b4606cb911ef2506ce8eb8ef7d0f81bf Mon Sep 17 00:00:00 2001 From: saloppe73 Date: Thu, 22 Jul 2021 05:34:53 +0200 Subject: [PATCH 07/92] fix LTC/LTCTEST --- README.md | 2 +- hdwallet/cryptocurrencies.py | 42 ++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 48cca63..9cb0ca9 100644 --- a/README.md +++ b/README.md @@ -356,7 +356,7 @@ 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](https://github.com/zcash/zcash) | `ZEC`, `ZECTEST` | Yes | YES | 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 diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index cadcc6b..d54f644 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -3213,18 +3213,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 +3245,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 +6239,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 @@ -6279,7 +6279,7 @@ class ZcashTestnet(Cryptocurrency): NAME = "Zcash" SYMBOL = "ZECTEST" NETWORK = "testnet" - SOURCE_CODE = "https://github.com/zcash/zcashn" + SOURCE_CODE = "https://github.com/zcash/zcash" COIN_TYPE = CoinType({ "INDEX": 1, "HARDENED": True From a480596a58993ccd869a73f72978eb3e0869ba9c Mon Sep 17 00:00:00 2001 From: Meheret Tesfaye Date: Mon, 26 Jul 2021 21:05:52 +0300 Subject: [PATCH 08/92] Update README.md --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9cb0ca9..b23f6eb 100644 --- a/README.md +++ b/README.md @@ -363,17 +363,11 @@ This library simplifies the process of creating a new HDWallet's for: If You found this tool helpful consider making a donation: -Ethereum (ETH) or Tether (USDT-ERC20) address: - -```text -0x342798bbe9731a91e0557fa8ab0bce1eae6d6ae3 -``` - -Bitcoin (BTC) address: - -```text -3GGNPvgbSpMHShcaZJGDXQn5wUJyTz7uoC -``` +| Coins | Addresses | +| ----------------------------- | :----------------------------------------: | +| Bitcoin `BTC` | 3GGNPvgbSpMHShcaZJGDXQn5wUJyTz7uoC | +| Ethereum `ETH`, Tether `USDT` | 0x342798bbe9731a91e0557fa8ab0bce1eae6d6ae3 | +| Bytom `BTM` | bm1qhpzc42ahrsahmpranv6xddc74tk6wlrvxrw68c | ## License From e79db28a094126d058ac4662935e8e3b96084223 Mon Sep 17 00:00:00 2001 From: sokripon Date: Thu, 12 Aug 2021 16:58:57 +0200 Subject: [PATCH 09/92] Fixed BIP141 link Corrected the spec link for BIP141. --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index b59b75c..66289d8 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) From 78517c5e1e57114ed6652c0fe845b2e4a61492e1 Mon Sep 17 00:00:00 2001 From: sokripon Date: Thu, 12 Aug 2021 17:08:29 +0200 Subject: [PATCH 10/92] Fixed link for BIP84 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b23f6eb..623f586 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ 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 | +| [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-0014.mediawiki) | Segregated Witness (Consensus layer) | ## Installation From 8da78de5d139a98599628064920102332a208d01 Mon Sep 17 00:00:00 2001 From: sokripon Date: Thu, 12 Aug 2021 17:08:52 +0200 Subject: [PATCH 11/92] Fixed link for BIP141 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 623f586..8bd138f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For more info see the BIP specs. | [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-0084.mediawiki) | Derivation scheme for P2WPKH based accounts | -| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki) | Segregated Witness (Consensus layer) | +| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) | Segregated Witness (Consensus layer) | ## Installation From 71620d2ee9e693f24a9bc4d12f50396a4d9b1b12 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 2 Sep 2021 07:36:33 +0300 Subject: [PATCH 12/92] Bump: HDWallet from v1.3.1 to v1.3.2 package. --- README.md | 9 ++++----- setup.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8bd138f..7775906 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. @@ -190,7 +190,7 @@ Base HD Path: m/44'/60'/0'/0/{address_index} To get started, just fork this repo, clone it locally, and run: ``` -$ pip install -e .[tests,docs] -r requirements.txt +pip install -e .[tests,docs] -r requirements.txt ``` ## Testing @@ -198,7 +198,7 @@ $ pip install -e .[tests,docs] -r requirements.txt You can run the tests with: ``` -$ pytest +pytest ``` Or use `tox` to run the complete suite against the full set of build targets, or pytest to run specific @@ -215,7 +215,7 @@ For more information, see the [CONTRIBUTING.md](https://github.com/meherett/hdwa ## 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 | | :---------------------------------------------------------------- | :------------------: | :-----: | :-----: | :----: | :-------: | :------------------: | @@ -367,7 +367,6 @@ If You found this tool helpful consider making a donation: | ----------------------------- | :----------------------------------------: | | Bitcoin `BTC` | 3GGNPvgbSpMHShcaZJGDXQn5wUJyTz7uoC | | Ethereum `ETH`, Tether `USDT` | 0x342798bbe9731a91e0557fa8ab0bce1eae6d6ae3 | -| Bytom `BTM` | bm1qhpzc42ahrsahmpranv6xddc74tk6wlrvxrw68c | ## License diff --git a/setup.py b/setup.py index 29fde43..8d17455 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="hdwallet", - version="1.3.1", + 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, From 0fa4a5288d624b1466ef7d91c282e721c23f1f89 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 2 Sep 2021 08:05:43 +0300 Subject: [PATCH 13/92] Fix: Zcash symbol ticker and added symbols tester. --- hdwallet/symbols.py | 2 +- tests/test_symbols.py | 164 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 tests/test_symbols.py diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index be3a03d..0f3dcbf 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -418,6 +418,6 @@ "XUEZ", "XDC", "ZCL", - "ZEC", "ZECTEST" + "ZEC", "ZECTEST", "ZEN" ] diff --git a/tests/test_symbols.py b/tests/test_symbols.py new file mode 100644 index 0000000..9177211 --- /dev/null +++ b/tests/test_symbols.py @@ -0,0 +1,164 @@ +# !/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 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" From d7488efe50d697adc37422d2ff7371bcb4a17114 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 2 Sep 2021 08:07:10 +0300 Subject: [PATCH 14/92] Bump: HDWallet from v1.3.1 to v1.3.2 package. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 6b42da8..0c5a95b 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.1" +release = "1.3.2" # The master toctree document. master_doc = "toctree" From 500d840fa7223ebe869a5375a0181461f060754d Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 2 Sep 2021 08:42:30 +0300 Subject: [PATCH 15/92] Change: Coveralls runner python package version to 3.8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 32023d4..c22e731 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,4 @@ matrix: - name: "3.9" python: 3.9 script: pytest -after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.9" ]; then coveralls; fi; \ No newline at end of file +after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.8" ]; then coveralls; fi; \ No newline at end of file From 3b76caf6021be0dcf416498e5e2828ad783c0e37 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 2 Sep 2021 08:46:02 +0300 Subject: [PATCH 16/92] Change: Coveralls runner python form v3.8 to v3.9 package. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c22e731..32023d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,4 @@ matrix: - name: "3.9" python: 3.9 script: pytest -after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.8" ]; then coveralls; fi; \ No newline at end of file +after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.9" ]; then coveralls; fi; \ No newline at end of file From 41b6a01531b69c9507424e3a75023ab2f60a2565 Mon Sep 17 00:00:00 2001 From: Beomsoo Kim Date: Mon, 13 Sep 2021 13:12:15 +0900 Subject: [PATCH 17/92] Added version info --- hdwallet/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index ce4c736..68a9e07 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -7,3 +7,5 @@ __all__ = [ "HDWallet", "BIP32HDWallet", "BIP44HDWallet", "BIP49HDWallet", "BIP84HDWallet", "BIP141HDWallet" ] + +__version__ = "1.3.2" From eb1c806f8644bf774c55495f1b36d817ae04db87 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 9 Oct 2021 21:04:01 +0300 Subject: [PATCH 18/92] Upgrade: Now, from root xprivate and xpublic kes merged into from xprivate and xpublic functions. --- hdwallet/__init__.py | 13 +++- hdwallet/hdwallet.py | 154 ++++++++++++++++++------------------------- 2 files changed, 75 insertions(+), 92 deletions(-) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index 68a9e07..353c1e0 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -4,8 +4,15 @@ HDWallet, BIP32HDWallet, BIP44HDWallet, BIP49HDWallet, BIP84HDWallet, BIP141HDWallet ) -__all__ = [ +# HDWallet Information's +__version__: str = "v2.0.0a1" +__license__: str = "ISCL" +__author__: str = "Meheret Tesfaye Batu" +__email__: str = "meherett@zoho.com" +__description__: str = "Python-based library for the implementation of a hierarchical deterministic wallet " \ + "generator for more than 140+ multiple cryptocurrencies." + +__all__: list = [ + "__version__", "__license__", "__author__", "__email__", "__description__", "HDWallet", "BIP32HDWallet", "BIP44HDWallet", "BIP49HDWallet", "BIP84HDWallet", "BIP141HDWallet" ] - -__version__ = "1.3.2" diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 369d213..9bdb3e0 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -39,8 +39,7 @@ Cryptocurrency, get_cryptocurrency, SegwitAddress ) from .derivations import ( - Derivation, BIP32Derivation, BIP44Derivation, - BIP49Derivation, BIP84Derivation, BIP141Derivation + Derivation, BIP32Derivation, BIP44Derivation, BIP49Derivation, BIP84Derivation, BIP141Derivation ) from .exceptions import ( SemanticError, DerivationError @@ -115,6 +114,10 @@ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, self._depth: int = 0 self._index: int = 0 + self._root_depth: int = 0 + self._root_parent_fingerprint: bytes = b"\0\0\0\0" + self._root_index: int = 0 + def from_entropy(self, entropy: str, language: str = "english", passphrase: str = None) -> "HDWallet": """ Master from Entropy hex string. @@ -215,32 +218,46 @@ def from_seed(self, seed: str) -> "HDWallet": self.from_path(path=self._path_class) return self - def from_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDWallet": + def from_xprivate_key(self, xprivate_key: str, strict: bool = False, change_to_root: bool = False) -> "HDWallet": """ - Master from Root XPrivate Key. + Master from XPrivate Key. - :param xprivate_key: Root xprivate key. + :param xprivate_key: Root or Non-Root XPrivate key. :type xprivate_key: str - :param strict: Strict for must be root xprivate key, default to ``True``. + :param strict: Strict for must be root xprivate key, default to ``False``. :type strict: bool + :param change_to_root: Non root xprivate key change to root xprivate key, default to ``False``. + :type change_to_root: bool :returns: HDWallet -- Hierarchical Deterministic Wallet instance. >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_root_xprivate_key(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") """ if not is_root_xprivate_key(xprivate_key=xprivate_key, symbol=self._cryptocurrency.SYMBOL): if strict: raise ValueError("Invalid root xprivate key.") - else: - print("Warning: The xprivate key is not root xprivate key.") _deserialize_xprivate_key = self._deserialize_xprivate_key(xprivate_key=xprivate_key) - self._depth, self._parent_fingerprint, self._index = (0, b"\0\0\0\0", 0) + if change_to_root: + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + 0, b"\0\0\0\0", 0 + ) + else: + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + int.from_bytes(_deserialize_xprivate_key[1], "big"), + _deserialize_xprivate_key[2], + struct.unpack(">L", _deserialize_xprivate_key[3])[0] + ) + self._depth, self._parent_fingerprint, self._index = ( + int.from_bytes(_deserialize_xprivate_key[1], "big"), + _deserialize_xprivate_key[2], + struct.unpack(">L", _deserialize_xprivate_key[3])[0] + ) self._i = _deserialize_xprivate_key[5] + _deserialize_xprivate_key[4] self._root_private_key = (_deserialize_xprivate_key[5], _deserialize_xprivate_key[4]) self._private_key, self._chain_code = self._i[:32], self._i[32:] @@ -253,21 +270,23 @@ def from_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDW self._public_key = self.compressed() return self - def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWallet": + def from_xpublic_key(self, xpublic_key: str, strict: bool = False, change_to_root: bool = False) -> "HDWallet": """ - Master from Root XPublic Key. + Master from XPublic Key. - :param xpublic_key: Root xpublic key. + :param xpublic_key: Root or Non-Root XPublic key. :type xpublic_key: str - :param strict: Strict for must be root xpublic key, default to ``True``. + :param strict: Strict for must be root xpublic key, default to ``False``. :type strict: bool + :param change_to_root: Non root xprivate key change to root xprivate key, default to ``False``. + :type change_to_root: bool :returns: HDWallet -- Hierarchical Deterministic Wallet instance. >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_root_xpublic_key(xpublic_key="xpub661MyMwAqRbcGSTjb2Mp3Sb4STUDhD2x986ubXKjQa2QsFTCVqzdA98qeZjcncHT1AaZcMSjiP1HJ16jH97q72RwyFfiNhmG8zQ6KBB5PaQ") + >>> hdwallet.from_xpublic_key(xpublic_key="xpub661MyMwAqRbcGSTjb2Mp3Sb4STUDhD2x986ubXKjQa2QsFTCVqzdA98qeZjcncHT1AaZcMSjiP1HJ16jH97q72RwyFfiNhmG8zQ6KBB5PaQ") """ @@ -276,7 +295,21 @@ def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWal raise ValueError("Invalid root xpublic key.") _deserialize_xpublic_key = self._deserialize_xpublic_key(xpublic_key=xpublic_key) - self._depth, self._parent_fingerprint, self._index = (0, b"\0\0\0\0", 0) + if change_to_root: + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + 0, b"\0\0\0\0", 0 + ) + else: + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + int.from_bytes(_deserialize_xpublic_key[1], "big"), + _deserialize_xpublic_key[2], + struct.unpack(">L", _deserialize_xpublic_key[3])[0] + ) + self._depth, self._parent_fingerprint, self._index = ( + int.from_bytes(_deserialize_xpublic_key[1], "big"), + _deserialize_xpublic_key[2], + struct.unpack(">L", _deserialize_xpublic_key[3])[0] + ) self._chain_code = _deserialize_xpublic_key[4] self._verified_key = ecdsa.VerifyingKey.from_string( _deserialize_xpublic_key[5], curve=SECP256k1 @@ -291,63 +324,6 @@ def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWal self._public_key = self.compressed() return self - def from_xprivate_key(self, xprivate_key: str) -> "HDWallet": - """ - Master from XPrivate Key. - - :param xprivate_key: XPrivate key. - :type xprivate_key: str - - :returns: HDWallet -- Hierarchical Deterministic Wallet instance. - - >>> from hdwallet import HDWallet - >>> from hdwallet.symbols import BTC - >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_xprivate_key(xprivate_key="xprvA3BYGWQ9FmhyaNRRXB2f1LphNPnaY9T6gngw4BaTbkFtscSH4RCuJhgWUSKs9S6ciGioHd4TX4UeyUg53MkfN9Xh38xkS1j2Wb9YKsYpJHQ") - - """ - - _deserialize_xprivate_key = self._deserialize_xprivate_key(xprivate_key=xprivate_key) - self._depth, self._parent_fingerprint, self._index = ( - int.from_bytes(_deserialize_xprivate_key[1], "big"), - _deserialize_xprivate_key[2], - struct.unpack(">L", _deserialize_xprivate_key[3])[0] - ) - self._private_key, self._chain_code = _deserialize_xprivate_key[5], _deserialize_xprivate_key[4] - 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() - return self - - def from_xpublic_key(self, xpublic_key: str) -> "HDWallet": - """ - Master from XPublic Key. - - :param xpublic_key: XPublic key. - :type xpublic_key: str - - :returns: HDWallet -- Hierarchical Deterministic Wallet instance. - - >>> from hdwallet import HDWallet - >>> from hdwallet.symbols import BTC - >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_xpublic_key(xprivate_key="xpub661MyMwAqRbcGSTjb2Mp3Sb4STUDhD2x986ubXKjQa2QsFTCVqzdA98qeZjcncHT1AaZcMSjiP1HJ16jH97q72RwyFfiNhmG8zQ6KBB5PaQ") - - """ - - _deserialize_xpublic_key = self._deserialize_xpublic_key(xpublic_key=xpublic_key) - self._depth, self._parent_fingerprint, self._index = ( - int.from_bytes(_deserialize_xpublic_key[1], "big"), - _deserialize_xpublic_key[2], - struct.unpack(">L", _deserialize_xpublic_key[3])[0] - ) - self._chain_code = _deserialize_xpublic_key[4] - self._verified_key = ecdsa.VerifyingKey.from_string( - _deserialize_xpublic_key[5], curve=SECP256k1 - ) - self._public_key = self.compressed() - return self - def from_wif(self, wif: str) -> "HDWallet": """ Master from Wallet Important Format (WIF). @@ -430,7 +406,7 @@ def from_path(self, path: Union[str, Derivation]) -> "HDWallet": >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_root_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_path(path="m/44'/0'/'0/0/0") """ @@ -463,7 +439,7 @@ def from_index(self, index: int, hardened: bool = False) -> "HDWallet": >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_root_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_index(index=44, hardened=True) >>> hdwallet.from_index(index=0, hardened=True) >>> hdwallet.from_index(index=0, hardened=True) @@ -485,18 +461,18 @@ 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.") + raise ValueError("You can't drive this master key.") i_str = struct.pack(">L", index) if index & BIP32KEY_HARDEN: if self._key is None: - raise DerivationError("Hardened derivation path is invalid with root xpublic key") + raise DerivationError("Hardened derivation path is invalid for xpublic key.") data = b"\0" + self._key.to_string() + i_str else: data = unhexlify(self.public_key()) + i_str if not self._chain_code: - raise PermissionError("You can't drive xprivate_key and private_key.") + raise ValueError("You can't drive xprivate_key and private_key.") i = hmac.new(self._chain_code, data, hashlib.sha512).digest() il, ir = i[:32], i[32:] @@ -587,9 +563,9 @@ def root_xprivate_key(self, encoded: bool = True) -> Optional[str]: if not self._i: return None secret_key, chain_code = self._i[:32], self._i[32:] - depth = bytes(bytearray([0])) - parent_fingerprint = b"\0\0\0\0" - index = struct.pack(">L", 0) + depth = bytes(bytearray([self._root_depth])) + parent_fingerprint = self._root_parent_fingerprint + index = struct.pack(">L", self._root_index) data = b"\x00" + secret_key return self._serialize_xkeys( _unhexlify(version), depth, parent_fingerprint, index, chain_code, data, encoded @@ -627,9 +603,9 @@ def root_xpublic_key(self, encoded: bool = True) -> Optional[str]: else: secret_key, chain_code = self._i[:32], self._i[32:] data = unhexlify(self.public_key(private_key=secret_key.hex())) - depth = bytes(bytearray([0])) - parent_fingerprint = b"\0\0\0\0" - index = struct.pack(">L", 0) + depth = bytes(bytearray([self._root_depth])) + parent_fingerprint = self._root_parent_fingerprint + index = struct.pack(">L", self._root_index) return self._serialize_xkeys( _unhexlify(version), depth, parent_fingerprint, index, chain_code, data, encoded ) @@ -709,7 +685,7 @@ def clean_derivation(self) -> "HDWallet": >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_root_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_path(path="m/44'/0'/'0/0/0") >>> hdwallet.path() "m/44'/0'/'0/0/0" @@ -1103,7 +1079,7 @@ def p2pkh_address(self) -> str: return checksum_encode(address, crypto="xdc") compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).digest() + public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).digest() network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + public_key_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1123,9 +1099,9 @@ def p2sh_address(self) -> str: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).hexdigest() + public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).hexdigest() public_key_hash_script = unhexlify("76a914" + public_key_hash + "88ac") - script_hash = hashlib.new('ripemd160', sha256(public_key_hash_script).digest()).digest() + script_hash = hashlib.new("ripemd160", sha256(public_key_hash_script).digest()).digest() network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1145,7 +1121,7 @@ def p2wpkh_address(self) -> Optional[str]: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new('ripemd160', sha256(compressed_public_key).digest()).digest() + public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).digest() if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, public_key_hash)) From abd7210972c1c1ff0f95e6ecab7579d0224e9206 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 9 Oct 2021 21:05:35 +0300 Subject: [PATCH 19/92] Update: Removed root key testers and add more tests on from xprv and xpub keys. --- tests/test_from_root_xprivate_key.py | 77 ---------------------------- tests/test_from_root_xpublic_key.py | 62 ---------------------- tests/test_from_xprivate_key.py | 77 +++++++++++++++++++++++++--- tests/test_from_xpublic_key.py | 56 ++++++++++++++++++-- 4 files changed, 120 insertions(+), 152 deletions(-) delete mode 100644 tests/test_from_root_xprivate_key.py delete mode 100644 tests/test_from_root_xpublic_key.py diff --git a/tests/test_from_root_xprivate_key.py b/tests/test_from_root_xprivate_key.py deleted file mode 100644 index 043a051..0000000 --- a/tests/test_from_root_xprivate_key.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 - -import json -import os - -from hdwallet import HDWallet - -# Test Values -base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) -values = open(file_path, "r", encoding="utf-8") -_: dict = json.loads(values.read()) -values.close() - - -def test_from_root_xprivate_key(): - - hdwallet: HDWallet = HDWallet( - symbol=_["bitcoin"]["mainnet"]["symbol"] - ) - hdwallet.from_root_xprivate_key( - xprivate_key=_["bitcoin"]["mainnet"]["root_xprivate_key"], strict=True - ) - hdwallet.from_path( - path=_["bitcoin"]["mainnet"]["path"] - ) - - assert hdwallet.cryptocurrency() == _["bitcoin"]["mainnet"]["cryptocurrency"] - assert hdwallet.symbol() == _["bitcoin"]["mainnet"]["symbol"] - assert hdwallet.network() == _["bitcoin"]["mainnet"]["network"] - assert hdwallet.strength() is None - assert hdwallet.entropy() is None - assert hdwallet.mnemonic() is None - assert hdwallet.language() is None - assert hdwallet.passphrase() is None - assert hdwallet.seed() is None - assert hdwallet.root_xprivate_key(encoded=False) == _["bitcoin"]["mainnet"]["root_xprivate_key_hex"] - assert hdwallet.root_xprivate_key() == _["bitcoin"]["mainnet"]["root_xprivate_key"] - assert hdwallet.root_xpublic_key(encoded=False) == _["bitcoin"]["mainnet"]["root_xpublic_key_hex"] - assert hdwallet.root_xpublic_key() == _["bitcoin"]["mainnet"]["root_xpublic_key"] - assert hdwallet.xprivate_key(encoded=False) == _["bitcoin"]["mainnet"]["xprivate_key_hex"] - assert hdwallet.xprivate_key() == _["bitcoin"]["mainnet"]["xprivate_key"] - assert hdwallet.xpublic_key(encoded=False) == _["bitcoin"]["mainnet"]["xpublic_key_hex"] - assert hdwallet.xpublic_key() == _["bitcoin"]["mainnet"]["xpublic_key"] - assert hdwallet.uncompressed() == _["bitcoin"]["mainnet"]["uncompressed"] - assert hdwallet.compressed() == _["bitcoin"]["mainnet"]["compressed"] - assert hdwallet.chain_code() == _["bitcoin"]["mainnet"]["chain_code"] - assert hdwallet.private_key() == _["bitcoin"]["mainnet"]["private_key"] - assert hdwallet.public_key() == _["bitcoin"]["mainnet"]["public_key"] - assert hdwallet.wif() == _["bitcoin"]["mainnet"]["wif"] - assert hdwallet.finger_print() == _["bitcoin"]["mainnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["mainnet"]["semantic"] - assert hdwallet.path() == _["bitcoin"]["mainnet"]["path"] - assert hdwallet.hash() == _["bitcoin"]["mainnet"]["hash"] - assert hdwallet.p2pkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2pkh"] - assert hdwallet.p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2sh"] - assert hdwallet.p2wpkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wpkh"] - assert hdwallet.p2wpkh_in_p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wpkh_in_p2sh"] - assert hdwallet.p2wsh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wsh"] - assert hdwallet.p2wsh_in_p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wsh_in_p2sh"] - - assert isinstance(hdwallet.dumps(), dict) - - dumps: dict = _["bitcoin"]["mainnet"] - - dumps["strength"] = None - dumps["entropy"] = None - dumps["mnemonic"] = None - dumps["language"] = None - dumps["passphrase"] = None - dumps["seed"] = None - del dumps["root_xprivate_key_hex"] - del dumps["root_xpublic_key_hex"] - del dumps["xprivate_key_hex"] - del dumps["xpublic_key_hex"] - - assert hdwallet.dumps() == dumps diff --git a/tests/test_from_root_xpublic_key.py b/tests/test_from_root_xpublic_key.py deleted file mode 100644 index 6d5892c..0000000 --- a/tests/test_from_root_xpublic_key.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 - -import json -import os - -from hdwallet import HDWallet - -# Test Values -base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) -values = open(file_path, "r", encoding="utf-8") -_: dict = json.loads(values.read()) -values.close() - - -def test_from_root_xpublic_key(): - - hdwallet: HDWallet = HDWallet( - symbol=_["bitcoin"]["mainnet"]["symbol"] - ) - hdwallet.from_root_xpublic_key( - xpublic_key=_["bitcoin"]["mainnet"]["root_xpublic_key"], strict=True - ) - hdwallet.from_path( - path="m/44/0/0/0/0" - ) - - assert hdwallet.cryptocurrency() == _["bitcoin"]["mainnet"]["cryptocurrency"] - assert hdwallet.symbol() == _["bitcoin"]["mainnet"]["symbol"] - assert hdwallet.network() == _["bitcoin"]["mainnet"]["network"] - assert hdwallet.strength() is None - assert hdwallet.entropy() is None - assert hdwallet.mnemonic() is None - assert hdwallet.language() is None - assert hdwallet.passphrase() is None - assert hdwallet.seed() is None - assert hdwallet.root_xprivate_key(encoded=False) is None - assert hdwallet.root_xprivate_key() is None - assert hdwallet.root_xpublic_key(encoded=False) == "0488b21e000000000000000000ad41ef910cdcae932cb4060777b4284ee38f5b29c5fb60fda8416f298a14702c02949b9f64223e124eb9a8383fba0b21b5845fcfbdc84dec7692d21c716410eab0" - assert hdwallet.root_xpublic_key() == "xpub661MyMwAqRbcGGUtsoFw2d6ARvD2ABd7z327zxt2XiBBwMx9GAuNrrE7tbRuWF5MjjZ1BzDsRdaSHc9nVKAgHzQrv6pwYW3Hd7LSzbh8sWS" - assert hdwallet.xprivate_key(encoded=False) is None - assert hdwallet.xprivate_key() is None - assert hdwallet.xpublic_key(encoded=False) == "0488b21e052c0269af000000006c95c19e932b9e8f3d834e874526768ca1b3d89933ad71fd8253bcca67ac283d038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" - assert hdwallet.xpublic_key() == "xpub6FjoSaU1JaG6fC6wTYmb1mJzaZxSunxASN7nTRHhFynh33gKRfmmNrtQ82s8YouLCrEniskjumfACiiTyVmi4aXyLL8HvLdZc8mjKsbzT9z" - assert hdwallet.uncompressed() == "8f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad8750a64d9e0ee3555225e4130c7e36a443ec20330bf0be1e4de913e31e00202993" - assert hdwallet.compressed() == "038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" - assert hdwallet.chain_code() == "6c95c19e932b9e8f3d834e874526768ca1b3d89933ad71fd8253bcca67ac283d" - assert hdwallet.private_key() is None - assert hdwallet.public_key() == "038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" - assert hdwallet.wif() is None - assert hdwallet.finger_print() == "4e749a26" - assert hdwallet.semantic() == "p2pkh" - assert hdwallet.path() == "m/44/0/0/0/0" - assert hdwallet.hash() == "4e749a26934bca5091a05ee6f55e7d0e21482647" - assert hdwallet.p2pkh_address() == "189qPd6J81ns9LEGx6kun7Xtg1bJV8GJXh" - assert hdwallet.p2sh_address() == "3C71bNRojv3Gc7zHvWygas4AFt34rKezcF" - assert hdwallet.p2wpkh_address() == "bc1qfe6f5f5nf099pydqtmn02hnapcs5sfj86dpqjm" - assert hdwallet.p2wpkh_in_p2sh_address() == "3NykoodgJ7Li43JPt5xsezQz8xfwwwFZUs" - assert hdwallet.p2wsh_address() == "bc1qazm6kznlgs06exh4cq2qxh567xrffppwujje5zg84upnng4essusd08nhz" - assert hdwallet.p2wsh_in_p2sh_address() == "32yGj8ncXBBTjXqg188ZHxd1xffoQDcjin" - - assert isinstance(hdwallet.dumps(), dict) diff --git a/tests/test_from_xprivate_key.py b/tests/test_from_xprivate_key.py index 939c717..c93eee8 100644 --- a/tests/test_from_xprivate_key.py +++ b/tests/test_from_xprivate_key.py @@ -15,12 +15,73 @@ def test_from_xprivate_key(): + hdwallet: HDWallet = HDWallet( + symbol=_["bitcoin"]["mainnet"]["symbol"] + ) + hdwallet.from_xprivate_key( + xprivate_key=_["bitcoin"]["mainnet"]["root_xprivate_key"], strict=True + ) + hdwallet.from_path( + path=_["bitcoin"]["mainnet"]["path"] + ) + + assert hdwallet.cryptocurrency() == _["bitcoin"]["mainnet"]["cryptocurrency"] + assert hdwallet.symbol() == _["bitcoin"]["mainnet"]["symbol"] + assert hdwallet.network() == _["bitcoin"]["mainnet"]["network"] + assert hdwallet.strength() is None + assert hdwallet.entropy() is None + assert hdwallet.mnemonic() is None + assert hdwallet.language() is None + assert hdwallet.passphrase() is None + assert hdwallet.seed() is None + assert hdwallet.root_xprivate_key(encoded=False) == _["bitcoin"]["mainnet"]["root_xprivate_key_hex"] + assert hdwallet.root_xprivate_key() == _["bitcoin"]["mainnet"]["root_xprivate_key"] + assert hdwallet.root_xpublic_key(encoded=False) == _["bitcoin"]["mainnet"]["root_xpublic_key_hex"] + assert hdwallet.root_xpublic_key() == _["bitcoin"]["mainnet"]["root_xpublic_key"] + assert hdwallet.xprivate_key(encoded=False) == _["bitcoin"]["mainnet"]["xprivate_key_hex"] + assert hdwallet.xprivate_key() == _["bitcoin"]["mainnet"]["xprivate_key"] + assert hdwallet.xpublic_key(encoded=False) == _["bitcoin"]["mainnet"]["xpublic_key_hex"] + assert hdwallet.xpublic_key() == _["bitcoin"]["mainnet"]["xpublic_key"] + assert hdwallet.uncompressed() == _["bitcoin"]["mainnet"]["uncompressed"] + assert hdwallet.compressed() == _["bitcoin"]["mainnet"]["compressed"] + assert hdwallet.chain_code() == _["bitcoin"]["mainnet"]["chain_code"] + assert hdwallet.private_key() == _["bitcoin"]["mainnet"]["private_key"] + assert hdwallet.public_key() == _["bitcoin"]["mainnet"]["public_key"] + assert hdwallet.wif() == _["bitcoin"]["mainnet"]["wif"] + assert hdwallet.finger_print() == _["bitcoin"]["mainnet"]["finger_print"] + assert hdwallet.semantic() == _["bitcoin"]["mainnet"]["semantic"] + assert hdwallet.path() == _["bitcoin"]["mainnet"]["path"] + assert hdwallet.hash() == _["bitcoin"]["mainnet"]["hash"] + assert hdwallet.p2pkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2pkh"] + assert hdwallet.p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2sh"] + assert hdwallet.p2wpkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wpkh"] + assert hdwallet.p2wpkh_in_p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wpkh_in_p2sh"] + assert hdwallet.p2wsh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wsh"] + assert hdwallet.p2wsh_in_p2sh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2wsh_in_p2sh"] + + assert isinstance(hdwallet.dumps(), dict) + + dumps: dict = _["bitcoin"]["mainnet"] + + dumps["strength"] = None + dumps["entropy"] = None + dumps["mnemonic"] = None + dumps["language"] = None + dumps["passphrase"] = None + dumps["seed"] = None + del dumps["root_xprivate_key_hex"] + del dumps["root_xpublic_key_hex"] + del dumps["xprivate_key_hex"] + del dumps["xpublic_key_hex"] + + assert hdwallet.dumps() == dumps + hdwallet: HDWallet = HDWallet( symbol=_["bitcoin"]["testnet"]["symbol"] ) - + hdwallet.from_xprivate_key( - xprivate_key=_["bitcoin"]["testnet"]["xprivate_key"] + xprivate_key=_["bitcoin"]["testnet"]["xprivate_key"], strict=False ) assert hdwallet.cryptocurrency() == _["bitcoin"]["testnet"]["cryptocurrency"] @@ -32,10 +93,10 @@ def test_from_xprivate_key(): assert hdwallet.language() is None assert hdwallet.passphrase() is None assert hdwallet.seed() is None - assert hdwallet.root_xprivate_key(encoded=False) is None - assert hdwallet.root_xprivate_key() is None - assert hdwallet.root_xpublic_key(encoded=False) is None - assert hdwallet.root_xpublic_key() is None + assert hdwallet.root_xprivate_key(encoded=False) == _["bitcoin"]["testnet"]["xprivate_key_hex"] + assert hdwallet.root_xprivate_key() == _["bitcoin"]["testnet"]["xprivate_key"] + assert hdwallet.root_xpublic_key(encoded=False) == _["bitcoin"]["testnet"]["xpublic_key_hex"] + assert hdwallet.root_xpublic_key() == _["bitcoin"]["testnet"]["xpublic_key"] assert hdwallet.xprivate_key(encoded=False) == _["bitcoin"]["testnet"]["xprivate_key_hex"] assert hdwallet.xprivate_key() == _["bitcoin"]["testnet"]["xprivate_key"] assert hdwallet.xpublic_key(encoded=False) == _["bitcoin"]["testnet"]["xpublic_key_hex"] @@ -67,9 +128,9 @@ def test_from_xprivate_key(): dumps["language"] = None dumps["passphrase"] = None dumps["seed"] = None - dumps["root_xprivate_key"] = None - dumps["root_xpublic_key"] = None dumps["path"] = None + dumps["root_xprivate_key"] = _["bitcoin"]["testnet"]["xprivate_key"] + dumps["root_xpublic_key"] = _["bitcoin"]["testnet"]["xpublic_key"] del dumps["root_xprivate_key_hex"] del dumps["root_xpublic_key_hex"] del dumps["xprivate_key_hex"] diff --git a/tests/test_from_xpublic_key.py b/tests/test_from_xpublic_key.py index d8b9055..34b870f 100644 --- a/tests/test_from_xpublic_key.py +++ b/tests/test_from_xpublic_key.py @@ -15,12 +15,58 @@ def test_from_xpublic_key(): + hdwallet: HDWallet = HDWallet( + symbol=_["bitcoin"]["mainnet"]["symbol"] + ) + hdwallet.from_xpublic_key( + xpublic_key=_["bitcoin"]["mainnet"]["root_xpublic_key"], strict=True + ) + hdwallet.from_path( + path="m/44/0/0/0/0" + ) + + assert hdwallet.cryptocurrency() == _["bitcoin"]["mainnet"]["cryptocurrency"] + assert hdwallet.symbol() == _["bitcoin"]["mainnet"]["symbol"] + assert hdwallet.network() == _["bitcoin"]["mainnet"]["network"] + assert hdwallet.strength() is None + assert hdwallet.entropy() is None + assert hdwallet.mnemonic() is None + assert hdwallet.language() is None + assert hdwallet.passphrase() is None + assert hdwallet.seed() is None + assert hdwallet.root_xprivate_key(encoded=False) is None + assert hdwallet.root_xprivate_key() is None + assert hdwallet.root_xpublic_key(encoded=False) == "0488b21e000000000000000000ad41ef910cdcae932cb4060777b4284ee38f5b29c5fb60fda8416f298a14702c02949b9f64223e124eb9a8383fba0b21b5845fcfbdc84dec7692d21c716410eab0" + assert hdwallet.root_xpublic_key() == "xpub661MyMwAqRbcGGUtsoFw2d6ARvD2ABd7z327zxt2XiBBwMx9GAuNrrE7tbRuWF5MjjZ1BzDsRdaSHc9nVKAgHzQrv6pwYW3Hd7LSzbh8sWS" + assert hdwallet.xprivate_key(encoded=False) is None + assert hdwallet.xprivate_key() is None + assert hdwallet.xpublic_key(encoded=False) == "0488b21e052c0269af000000006c95c19e932b9e8f3d834e874526768ca1b3d89933ad71fd8253bcca67ac283d038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" + assert hdwallet.xpublic_key() == "xpub6FjoSaU1JaG6fC6wTYmb1mJzaZxSunxASN7nTRHhFynh33gKRfmmNrtQ82s8YouLCrEniskjumfACiiTyVmi4aXyLL8HvLdZc8mjKsbzT9z" + assert hdwallet.uncompressed() == "8f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad8750a64d9e0ee3555225e4130c7e36a443ec20330bf0be1e4de913e31e00202993" + assert hdwallet.compressed() == "038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" + assert hdwallet.chain_code() == "6c95c19e932b9e8f3d834e874526768ca1b3d89933ad71fd8253bcca67ac283d" + assert hdwallet.private_key() is None + assert hdwallet.public_key() == "038f24175db513b40c75503c25040e5f0ea4d38e912d1f83daf5fd8c4b9512ad87" + assert hdwallet.wif() is None + assert hdwallet.finger_print() == "4e749a26" + assert hdwallet.semantic() == "p2pkh" + assert hdwallet.path() == "m/44/0/0/0/0" + assert hdwallet.hash() == "4e749a26934bca5091a05ee6f55e7d0e21482647" + assert hdwallet.p2pkh_address() == "189qPd6J81ns9LEGx6kun7Xtg1bJV8GJXh" + assert hdwallet.p2sh_address() == "3C71bNRojv3Gc7zHvWygas4AFt34rKezcF" + assert hdwallet.p2wpkh_address() == "bc1qfe6f5f5nf099pydqtmn02hnapcs5sfj86dpqjm" + assert hdwallet.p2wpkh_in_p2sh_address() == "3NykoodgJ7Li43JPt5xsezQz8xfwwwFZUs" + assert hdwallet.p2wsh_address() == "bc1qazm6kznlgs06exh4cq2qxh567xrffppwujje5zg84upnng4essusd08nhz" + assert hdwallet.p2wsh_in_p2sh_address() == "32yGj8ncXBBTjXqg188ZHxd1xffoQDcjin" + + assert isinstance(hdwallet.dumps(), dict) + hdwallet: HDWallet = HDWallet( symbol=_["bitcoin"]["testnet"]["symbol"] ) - + hdwallet.from_xpublic_key( - xpublic_key=_["bitcoin"]["testnet"]["xpublic_key"] + xpublic_key=_["bitcoin"]["testnet"]["xpublic_key"], strict=False ) assert hdwallet.cryptocurrency() == _["bitcoin"]["testnet"]["cryptocurrency"] @@ -34,8 +80,8 @@ def test_from_xpublic_key(): assert hdwallet.seed() is None assert hdwallet.root_xprivate_key(encoded=False) is None assert hdwallet.root_xprivate_key() is None - assert hdwallet.root_xpublic_key(encoded=False) is None - assert hdwallet.root_xpublic_key() is None + assert hdwallet.root_xpublic_key(encoded=False) == _["bitcoin"]["testnet"]["xpublic_key_hex"] + assert hdwallet.root_xpublic_key() == _["bitcoin"]["testnet"]["xpublic_key"] assert hdwallet.xprivate_key(encoded=False) is None assert hdwallet.xprivate_key() is None assert hdwallet.xpublic_key(encoded=False) == _["bitcoin"]["testnet"]["xpublic_key_hex"] @@ -70,11 +116,11 @@ def test_from_xpublic_key(): dumps["passphrase"] = None dumps["seed"] = None dumps["root_xprivate_key"] = None - dumps["root_xpublic_key"] = None dumps["xprivate_key"] = None dumps["private_key"] = None dumps["wif"] = None dumps["path"] = None + dumps["root_xpublic_key"] = _["bitcoin"]["testnet"]["xpublic_key"] del dumps["root_xprivate_key_hex"] del dumps["root_xpublic_key_hex"] del dumps["xprivate_key_hex"] From cdf5d89a15ea79a79d8af0199e0ddcccc55caf2b Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 9 Oct 2021 21:23:55 +0300 Subject: [PATCH 20/92] Update: from_xprivate_key and from_xpublic_key examples. --- examples/from_root_xprivate_key.py | 61 ------------------------------ examples/from_root_xpublic_key.py | 57 ---------------------------- examples/from_xprivate_key.py | 41 +++++++++++++++----- examples/from_xpublic_key.py | 41 +++++++++++++++----- 4 files changed, 62 insertions(+), 138 deletions(-) delete mode 100644 examples/from_root_xprivate_key.py delete mode 100644 examples/from_root_xpublic_key.py diff --git a/examples/from_root_xprivate_key.py b/examples/from_root_xprivate_key.py deleted file mode 100644 index dc6258f..0000000 --- a/examples/from_root_xprivate_key.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 - -from hdwallet import HDWallet as HDWallet -from hdwallet.utils import is_root_xprivate_key -from hdwallet.symbols import BTC - -import json - -# Strict for root xpublic key -STRICT: bool = True -# Bitcoin root xprivate key -XPRIVATE_KEY: str = "xprv9s21ZrQH143K24t96gCaezzt1QQmnqiEGm8m6TP8yb8e3TmGfkCgcLEVss" \ - "kufMW9R4KH27pD1kyyEfJkYz1eiPwjhFzB4gtabH3PzMSmXSM" -# Bitcoin not root xprivate key -# XPRIVATE_KEY: str = "xprvA3KRgVDh45mbQT1VmWPx73YeAWM4629Q2D9pMuqjFMnjTqDGhKiww6H532rg" \ -# "YRNj37fngd4Mvp7GfUD8rKeQzUZjCWeisT92tX8FfjWx3BL" - -if STRICT: - # Check root xprivate key - assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xprivate key." - -# Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) -# Get Bitcoin HDWallet from root xprivate key -hdwallet.from_root_xprivate_key(xprivate_key=XPRIVATE_KEY, strict=STRICT) - -# Derivation from path -# hdwallet.from_path("m/44'/0'/0'/0/0") -# Or derivation from index -hdwallet.from_index(44, hardened=True) -hdwallet.from_index(0, hardened=True) -hdwallet.from_index(0, hardened=True) -hdwallet.from_index(0) -hdwallet.from_index(0) - -# Print all Bitcoin HDWallet information's -# print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) - -print("Cryptocurrency:", hdwallet.cryptocurrency()) -print("Symbol:", hdwallet.symbol()) -print("Network:", hdwallet.network()) -print("Root XPrivate Key:", hdwallet.root_xprivate_key()) -print("Root XPublic Key:", hdwallet.root_xpublic_key()) -print("XPrivate Key:", hdwallet.xprivate_key()) -print("XPublic Key:", hdwallet.xpublic_key()) -print("Uncompressed:", hdwallet.uncompressed()) -print("Compressed:", hdwallet.compressed()) -print("Chain Code:", hdwallet.chain_code()) -print("Private Key:", hdwallet.private_key()) -print("Public Key:", hdwallet.public_key()) -print("Wallet Important Format:", hdwallet.wif()) -print("Finger Print:", hdwallet.finger_print()) -print("Semantic:", hdwallet.semantic()) -print("Path:", hdwallet.path()) -print("Hash:", hdwallet.hash()) -print("P2PKH Address:", hdwallet.p2pkh_address()) -print("P2SH Address:", hdwallet.p2sh_address()) -print("P2WPKH Address:", hdwallet.p2wpkh_address()) -print("P2WPKH In P2SH Address:", hdwallet.p2wpkh_in_p2sh_address()) -print("P2WSH Address:", hdwallet.p2wsh_address()) -print("P2WSH In P2SH Address:", hdwallet.p2wsh_in_p2sh_address()) diff --git a/examples/from_root_xpublic_key.py b/examples/from_root_xpublic_key.py deleted file mode 100644 index ad2ba34..0000000 --- a/examples/from_root_xpublic_key.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 - -from hdwallet import HDWallet as HDWallet -from hdwallet.utils import is_root_xpublic_key -from hdwallet.symbols import BTC - -import json - -# Strict for root xpublic key -STRICT: bool = True -# Bitcoin root xpublic key -XPUBLIC_KEY: str = "xpub661MyMwAqRbcEqD3v24ZWHGDMqqAfbDbmnUFJXfbpxGZaAshq7evA7fB75CHFbNHSot" \ - "LadDZw6M6ic4ZkdN6jQ2KMGR66Z2EybgdLFjNrpf" -# Bitcoin not root xpublic key -# XPUBLIC_KEY: str = "xpub6FbWJtnc3eJHBwfTqhaE9yQNkmi56UDy9Rm1pbhvuSSigr6xKihuFpnnf4jz8G9ba2m3wFaF" \ -# "Gj7eH7FE451Jo5hPJhbaCdmxoBwWbFzk1Sn" - -if STRICT: - # Check root xpublic key - assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xpublic key." - -# Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) -# Get Bitcoin HDWallet from root xpublic key -hdwallet.from_root_xpublic_key(xpublic_key=XPUBLIC_KEY, strict=STRICT) - -# Derivation from path -# hdwallet.from_path("m/44/0/0/0/0") -# Or derivation from index -hdwallet.from_index(44, hardened=False) -hdwallet.from_index(0, hardened=False) -hdwallet.from_index(0, hardened=False) -hdwallet.from_index(0) -hdwallet.from_index(0) - -# Print all Bitcoin HDWallet information's -# print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) - -print("Cryptocurrency:", hdwallet.cryptocurrency()) -print("Symbol:", hdwallet.symbol()) -print("Network:", hdwallet.network()) -print("Root XPublic Key:", hdwallet.root_xpublic_key()) -print("XPublic Key:", hdwallet.xpublic_key()) -print("Uncompressed:", hdwallet.uncompressed()) -print("Compressed:", hdwallet.compressed()) -print("Chain Code:", hdwallet.chain_code()) -print("Public Key:", hdwallet.public_key()) -print("Finger Print:", hdwallet.finger_print()) -print("Semantic:", hdwallet.semantic()) -print("Path:", hdwallet.path()) -print("Hash:", hdwallet.hash()) -print("P2PKH Address:", hdwallet.p2pkh_address()) -print("P2SH Address:", hdwallet.p2sh_address()) -print("P2WPKH Address:", hdwallet.p2wpkh_address()) -print("P2WPKH In P2SH Address:", hdwallet.p2wpkh_in_p2sh_address()) -print("P2WSH Address:", hdwallet.p2wsh_address()) -print("P2WSH In P2SH Address:", hdwallet.p2wsh_in_p2sh_address()) diff --git a/examples/from_xprivate_key.py b/examples/from_xprivate_key.py index 5eb1913..fe9e65c 100644 --- a/examples/from_xprivate_key.py +++ b/examples/from_xprivate_key.py @@ -1,25 +1,46 @@ #!/usr/bin/env python3 -from hdwallet import HDWallet -from hdwallet.symbols import ETH +from hdwallet import HDWallet as HDWallet +from hdwallet.utils import is_root_xprivate_key +from hdwallet.symbols import BTC import json -# Ethereum xprivate key -XPRIVATE_KEY = "xprvA3KRgVDh45mbQT1VmWPx73YeAWM4629Q2D9pMuqjFMnjTqDGhKiww6H" \ - "532rgYRNj37fngd4Mvp7GfUD8rKeQzUZjCWeisT92tX8FfjWx3BL" +# Strict for root xpublic key +STRICT: bool = True +# Bitcoin root xprivate key +XPRIVATE_KEY: str = "xprv9s21ZrQH143K24t96gCaezzt1QQmnqiEGm8m6TP8yb8e3TmGfkCgcLEVss" \ + "kufMW9R4KH27pD1kyyEfJkYz1eiPwjhFzB4gtabH3PzMSmXSM" +# Bitcoin non-root xprivate key +# XPRIVATE_KEY: str = "xprvA3KRgVDh45mbQT1VmWPx73YeAWM4629Q2D9pMuqjFMnjTqDGhKiww6H532rg" \ +# "YRNj37fngd4Mvp7GfUD8rKeQzUZjCWeisT92tX8FfjWx3BL" -# Initialize Ethereum mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=ETH) -# Get Ethereum HDWallet from xprivate key -hdwallet.from_xprivate_key(xprivate_key=XPRIVATE_KEY) +if STRICT: + # Check root xprivate key + assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xprivate key." -# Print all Ethereum HDWallet information's +# Initialize Bitcoin mainnet HDWallet +hdwallet: HDWallet = HDWallet(symbol=BTC) +# Get Bitcoin HDWallet from xprivate key +hdwallet.from_xprivate_key(xprivate_key=XPRIVATE_KEY, strict=STRICT) + +# Derivation from path +# hdwallet.from_path("m/44'/0'/0'/0/0") +# Or derivation from index +hdwallet.from_index(44, hardened=True) +hdwallet.from_index(0, hardened=True) +hdwallet.from_index(0, hardened=True) +hdwallet.from_index(0) +hdwallet.from_index(0) + +# Print all Bitcoin HDWallet information's # print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) print("Cryptocurrency:", hdwallet.cryptocurrency()) print("Symbol:", hdwallet.symbol()) print("Network:", hdwallet.network()) +print("Root XPrivate Key:", hdwallet.root_xprivate_key()) +print("Root XPublic Key:", hdwallet.root_xpublic_key()) print("XPrivate Key:", hdwallet.xprivate_key()) print("XPublic Key:", hdwallet.xpublic_key()) print("Uncompressed:", hdwallet.uncompressed()) diff --git a/examples/from_xpublic_key.py b/examples/from_xpublic_key.py index c12d4e0..7197b55 100644 --- a/examples/from_xpublic_key.py +++ b/examples/from_xpublic_key.py @@ -1,25 +1,45 @@ #!/usr/bin/env python3 -from hdwallet import HDWallet -from hdwallet.symbols import ETH +from hdwallet import HDWallet as HDWallet +from hdwallet.utils import is_root_xpublic_key +from hdwallet.symbols import BTC import json -# Ethereum xpublic key -XPUBLIC_KEY = "xpub6GYVAAuBNfDxWKfSoNPR262M6uW7wWTuxE6LLRtgdBZkvmgzWFGhk41NKHydAxa" \ - "6RMZP3pY2318KG4iUfZa22nUA4q8hfrqhrDpBUJcfvWu" +# Strict for root xpublic key +STRICT: bool = True +# Bitcoin root xpublic key +XPUBLIC_KEY: str = "xpub661MyMwAqRbcEqD3v24ZWHGDMqqAfbDbmnUFJXfbpxGZaAshq7evA7fB75CHFbNHSot" \ + "LadDZw6M6ic4ZkdN6jQ2KMGR66Z2EybgdLFjNrpf" +# Bitcoin non-root xpublic key +# XPUBLIC_KEY: str = "xpub6FbWJtnc3eJHBwfTqhaE9yQNkmi56UDy9Rm1pbhvuSSigr6xKihuFpnnf4jz8G9ba2m3wFaF" \ +# "Gj7eH7FE451Jo5hPJhbaCdmxoBwWbFzk1Sn" -# Initialize Ethereum mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=ETH) -# Get Ethereum HDWallet from xpublic key -hdwallet.from_xpublic_key(xpublic_key=XPUBLIC_KEY) +if STRICT: + # Check root xpublic key + assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xpublic key." -# Print all Ethereum HDWallet information's +# Initialize Bitcoin mainnet HDWallet +hdwallet: HDWallet = HDWallet(symbol=BTC) +# Get Bitcoin HDWallet from xpublic key +hdwallet.from_xpublic_key(xpublic_key=XPUBLIC_KEY, strict=STRICT) + +# Derivation from path +# hdwallet.from_path("m/44/0/0/0/0") +# Or derivation from index +hdwallet.from_index(44, hardened=False) +hdwallet.from_index(0, hardened=False) +hdwallet.from_index(0, hardened=False) +hdwallet.from_index(0) +hdwallet.from_index(0) + +# Print all Bitcoin HDWallet information's # print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) print("Cryptocurrency:", hdwallet.cryptocurrency()) print("Symbol:", hdwallet.symbol()) print("Network:", hdwallet.network()) +print("Root XPublic Key:", hdwallet.root_xpublic_key()) print("XPublic Key:", hdwallet.xpublic_key()) print("Uncompressed:", hdwallet.uncompressed()) print("Compressed:", hdwallet.compressed()) @@ -27,6 +47,7 @@ print("Public Key:", hdwallet.public_key()) print("Finger Print:", hdwallet.finger_print()) print("Semantic:", hdwallet.semantic()) +print("Path:", hdwallet.path()) print("Hash:", hdwallet.hash()) print("P2PKH Address:", hdwallet.p2pkh_address()) print("P2SH Address:", hdwallet.p2sh_address()) From 766a75cbe9e902a802452b25c9bf87f33edab25f Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 10 Oct 2021 21:56:50 +0300 Subject: [PATCH 21/92] Add: CLI for hdwallet generator and for millions of addresses generator. --- hdwallet/cli/__init__.py | 12 +++ hdwallet/cli/__main__.py | 43 ++++++++ hdwallet/cli/generate/__init__.py | 94 ++++++++++++++++ hdwallet/cli/generate/addresses/__init__.py | 87 +++++++++++++++ hdwallet/cli/generate/addresses/addresses.py | 102 ++++++++++++++++++ hdwallet/cli/generate/hdwallet.py | 108 +++++++++++++++++++ 6 files changed, 446 insertions(+) create mode 100644 hdwallet/cli/__init__.py create mode 100644 hdwallet/cli/__main__.py create mode 100644 hdwallet/cli/generate/__init__.py create mode 100644 hdwallet/cli/generate/addresses/__init__.py create mode 100644 hdwallet/cli/generate/addresses/addresses.py create mode 100644 hdwallet/cli/generate/hdwallet.py diff --git a/hdwallet/cli/__init__.py b/hdwallet/cli/__init__.py new file mode 100644 index 0000000..9a7b09f --- /dev/null +++ b/hdwallet/cli/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +import textwrap +import click +import sys + + +__all__ = [ + "textwrap", + "click", + "sys" +] diff --git a/hdwallet/cli/__main__.py b/hdwallet/cli/__main__.py new file mode 100644 index 0000000..8cba88d --- /dev/null +++ b/hdwallet/cli/__main__.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# coding=utf-8 + +from hdwallet import __version__ +from hdwallet.cli.generate import generate +from hdwallet.cli import click + +CONTEXT_SETTINGS = dict( + help_option_names=["-h", "--help"], +) + + +class AliasedGroup(click.Group): + + def get_command(self, ctx, cmd_name): + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + matches = [x for x in self.list_commands(ctx) + if x.startswith(cmd_name)] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail("Too many matches: %s" % ", ".join(sorted(matches))) + + +def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + click.echo(__version__) + ctx.exit() + + +@click.group(cls=AliasedGroup, + options_metavar="[OPTIONS]", context_settings=CONTEXT_SETTINGS) +@click.option("-v", "--version", is_flag=True, callback=print_version, + expose_value=False, help="Show HDWallet version and exit.") +def main(): + pass + + +main.add_command(generate) diff --git a/hdwallet/cli/generate/__init__.py b/hdwallet/cli/generate/__init__.py new file mode 100644 index 0000000..34dcb0b --- /dev/null +++ b/hdwallet/cli/generate/__init__.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +from typing import Optional + +from hdwallet.cli import click +from hdwallet.cli.generate.hdwallet import generate_hdwallet +from hdwallet.cli.generate.addresses import addresses + + +@click.group("generate", options_metavar="[OPTIONS]", + short_help="Select Generate for HDWallet.", invoke_without_command=True) +@click.option("-s", "--symbol", type=str, default="BTC", + help="Set Cryptocurrency ticker symbol.") +@click.option("-sg", "--strength", type=int, default=128, + help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) +@click.option("-e", "--entropy", type=str, default=None, + help="Set Master key from entropy hex string.", show_default=True) +@click.option("-m", "--mnemonic", type=str, default=None, + help="Set Master key from mnemonic words.", show_default=True) +@click.option("-l", "--language", type=str, default="english", + help="Set Language for mnemonic, choose language english, french, italian, spanish, " + "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) +@click.option("-pa", "--passphrase", type=str, default=None, + help="Set Passphrase for mnemonic.", show_default=True) +@click.option("-sd", "--seed", type=str, default=None, + help="Set Master key from seed hex string.", show_default=True) +@click.option("-xprv", "--xprivate-key", type=str, default=None, + help="Set Master key from xprivate key.", show_default=True) +@click.option("-xpub", "--xpublic-key", type=str, default=None, + help="Set Master key from xpublic key.", show_default=True) +@click.option("-st", "--strict", type=bool, default=False, + help="Set Strict for root keys.", show_default=True) +@click.option("-ac", "--account", type=int, default=0, + help="Set derivation from account.", show_default=True) +@click.option("-ch", "--change", type=bool, default=False, + help="Set Derivation from change.", show_default=True) +@click.option("-ad", "--address", type=int, default=0, + help="Set Derivation from address.", show_default=True) +@click.option("-p", "--path", type=str, default=None, + help="Set Master key derivation path.", show_default=True) +@click.option("-prv", "--private-key", type=str, default=None, + help="Set Master key from private key.", show_default=True) +@click.option("-pub", "--public-key", type=str, default=None, + help="Set Master key from public key.", show_default=True) +@click.option("-w", "--wif", type=str, default=None, + help="Set Master key from wallet important format.", show_default=True) +@click.option("-sm", "--semantic", type=str, default="p2pkh", + help="Set Semantic for xprivate and xpublic keys.", show_default=True) +@click.pass_context +def generate( + context: click.core.Context, + symbol: str, + strength: int, + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: bool, + account: int, + change: bool, + address: int, + path: Optional[str], + private_key: Optional[str], + public_key: Optional[str], + wif: Optional[str], + semantic: str +): + if context.invoked_subcommand is None: + return generate_hdwallet( + symbol=symbol, + strength=strength, + entropy=entropy, + mnemonic=mnemonic, + language=language, + passphrase=passphrase, + seed=seed, + xprivate_key=xprivate_key, + xpublic_key=xpublic_key, + strict=strict, + account=account, + change=change, + address=address, + path=path, + private_key=private_key, + public_key=public_key, + wif=wif, + semantic=semantic + ) + + +generate.add_command(addresses) diff --git a/hdwallet/cli/generate/addresses/__init__.py b/hdwallet/cli/generate/addresses/__init__.py new file mode 100644 index 0000000..af1bcd0 --- /dev/null +++ b/hdwallet/cli/generate/addresses/__init__.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +from typing import Optional + +from hdwallet.cli.generate.addresses.addresses import generate_hdwallet_addresses +from hdwallet.cli import click + + +@click.command("addresses", options_metavar="[OPTIONS]", + short_help="Select Addresses for generation HDWallet addresses.") +@click.option("-s", "--symbol", type=str, required=True, + help="Set Cryptocurrency ticker symbol.") +@click.option("-sg", "--strength", type=int, default=128, + help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) +@click.option("-e", "--entropy", type=str, default=None, + help="Set Master key from entropy hex string.", show_default=True) +@click.option("-m", "--mnemonic", type=str, default=None, + help="Set Master key from mnemonic words.", show_default=True) +@click.option("-l", "--language", type=str, default="english", + help="Set Language for mnemonic, choose language english, french, italian, spanish, " + "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) +@click.option("-pa", "--passphrase", type=str, default=None, + help="Set Passphrase for mnemonic.", show_default=True) +@click.option("-sd", "--seed", type=str, default=None, + help="Set Master key from seed hex string.", show_default=True) +@click.option("-xprv", "--xprivate-key", type=str, default=None, + help="Set Master key from xprivate key.", show_default=True) +@click.option("-xpub", "--xpublic-key", type=str, default=None, + help="Set Master key from xpublic key.", show_default=True) +@click.option("-st", "--strict", type=bool, default=False, + help="Set Strict for root keys.", show_default=True) +@click.option("-ac", "--account", type=int, default=0, + help="Set derivation from account.", show_default=True) +@click.option("-ch", "--change", type=bool, default=False, + help="Set Derivation from change.", show_default=True) +@click.option("-p", "--path", type=str, default=None, + help="Set Master key derivation path.", show_default=True) +@click.option("-se", "--semantic", type=str, default="p2pkh", + help="Set Semantic for xprivate and xpublic keys.", show_default=True) +@click.option("-h", "--hardened", type=bool, default=False, + help="Set Hardened for addresses.", show_default=True) +@click.option("-si", "--start-index", type=int, default=0, + help="Set Start from address index.", show_default=True) +@click.option("-ei", "--end-index", type=int, default=20, + help="Set End to address index.", show_default=True) +@click.option("-sh", "--show", type=str, default="path,addresses:p2pkh,public_key,wif", + help="Set Value key of generated HDWallet data to show.", show_default=True) +def addresses( + symbol: str, + strength: int, + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: bool, + account: int, + change: bool, + path: Optional[str], + semantic: str, + start_index: int, + end_index: int, + hardened: bool, + show: str +): + return generate_hdwallet_addresses( + symbol=symbol, + strength=strength, + entropy=entropy, + mnemonic=mnemonic, + language=language, + passphrase=passphrase, + seed=seed, + xprivate_key=xprivate_key, + xpublic_key=xpublic_key, + strict=strict, + account=account, + change=change, + path=path, + semantic=semantic, + start_index=start_index, + end_index=end_index, + hardened=hardened, + show=show + ) diff --git a/hdwallet/cli/generate/addresses/addresses.py b/hdwallet/cli/generate/addresses/addresses.py new file mode 100644 index 0000000..998c4ab --- /dev/null +++ b/hdwallet/cli/generate/addresses/addresses.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +from typing import Optional + +from hdwallet import HDWallet +from hdwallet.derivations import ( + Derivation, BIP32Derivation +) +from hdwallet.cryptocurrencies import ( + Cryptocurrency, get_cryptocurrency +) +from hdwallet.utils import generate_mnemonic +from hdwallet.cli import ( + click, sys +) + + +def generate_hdwallet_addresses( + symbol: str, + strength: Optional[int], + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: Optional[bool], + account: int, + change: bool, + path: Optional[str], + semantic: str, + start_index: int, + end_index: int, + hardened: bool, + show: str +): + try: + + hdwallet: HDWallet = HDWallet( + symbol=symbol, semantic=semantic + ) + if entropy: + hdwallet.from_entropy( + entropy=entropy, language=language, passphrase=passphrase + ) + elif mnemonic: + hdwallet.from_mnemonic( + mnemonic=mnemonic, language=language, passphrase=passphrase + ) + elif seed: + hdwallet.from_seed( + seed=seed + ) + elif xprivate_key: + hdwallet.from_xprivate_key( + xprivate_key=xprivate_key, strict=strict + ) + elif xpublic_key: + hdwallet.from_xpublic_key( + xpublic_key=xpublic_key, strict=strict + ) + else: + mnemonic = generate_mnemonic(language=language, strength=strength) + hdwallet.from_mnemonic( + mnemonic=mnemonic, language=language, passphrase=passphrase + ) + + for index in range(start_index, end_index): + if path: + derivation: Derivation = Derivation(path=path) + derivation.from_index(index=index, hardened=hardened) + hdwallet.from_path(path=derivation) + else: + cryptocurrency: Cryptocurrency = get_cryptocurrency(symbol=symbol) + bip32_derivation: BIP32Derivation = BIP32Derivation( + purpose=( + 44, False if xpublic_key else True + ), + coin_type=( + cryptocurrency.COIN_TYPE.INDEX, + cryptocurrency.COIN_TYPE.HARDENED + ), + account=( + account, False if xpublic_key else True + ), + change=change, + address=index + ) + hdwallet.from_path(path=bip32_derivation) + + rows: str = "" + dumps = hdwallet.dumps() + for key in [keys.split(":") for keys in show.split(",")]: + rows += f" {dumps[key[0]][key[1]] if len(key) == 2 else dumps[key[0]]}" + click.echo(rows) + + hdwallet.clean_derivation() + + except TimeoutError as exception: + click.echo(click.style(f"Error: {str(exception)}"), err=True) + sys.exit() diff --git a/hdwallet/cli/generate/hdwallet.py b/hdwallet/cli/generate/hdwallet.py new file mode 100644 index 0000000..9480a75 --- /dev/null +++ b/hdwallet/cli/generate/hdwallet.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +from typing import Optional + +import json + +from hdwallet import HDWallet +from hdwallet.derivations import ( + Derivation, BIP32Derivation +) +from hdwallet.cryptocurrencies import ( + Cryptocurrency, get_cryptocurrency +) +from hdwallet.utils import generate_mnemonic +from hdwallet.cli import ( + click, sys +) + + +def generate_hdwallet( + symbol: str, + strength: Optional[int], + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: Optional[bool], + account: int, + change: bool, + address: int, + path: Optional[str], + private_key: Optional[str], + public_key: Optional[str], + wif: Optional[str], + semantic: str +): + try: + + hdwallet: HDWallet = HDWallet( + symbol=symbol, semantic=semantic + ) + if entropy: + hdwallet.from_entropy( + entropy=entropy, language=language, passphrase=passphrase + ) + elif mnemonic: + hdwallet.from_mnemonic( + mnemonic=mnemonic, language=language, passphrase=passphrase + ) + elif seed: + hdwallet.from_seed( + seed=seed + ) + elif xprivate_key: + hdwallet.from_xprivate_key( + xprivate_key=xprivate_key, strict=strict + ) + elif xpublic_key: + hdwallet.from_xpublic_key( + xpublic_key=xpublic_key, strict=strict + ) + elif private_key: + hdwallet.from_private_key( + private_key=private_key + ) + elif public_key: + hdwallet.from_public_key( + public_key=public_key + ) + elif wif: + hdwallet.from_wif( + wif=wif + ) + else: + mnemonic = generate_mnemonic(language=language, strength=strength) + hdwallet.from_mnemonic( + mnemonic=mnemonic, language=language, passphrase=passphrase + ) + + if path: + derivation: Derivation = Derivation(path=path) + hdwallet.from_path(path=derivation) + else: + cryptocurrency: Cryptocurrency = get_cryptocurrency(symbol=symbol) + bip32_derivation: BIP32Derivation = BIP32Derivation( + purpose=( + 44, False if xpublic_key else True + ), + coin_type=( + cryptocurrency.COIN_TYPE.INDEX, + cryptocurrency.COIN_TYPE.HARDENED + ), + account=( + account, False if xpublic_key else True + ), + change=change, + address=address + ) + hdwallet.from_path(path=bip32_derivation) + + click.echo(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) + + except Exception as exception: + click.echo(click.style(f"Error: {str(exception)}"), err=True) + sys.exit() From 25ea8e7c830f4728979d16e7fd78e104d8b8108b Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 10 Oct 2021 22:03:07 +0300 Subject: [PATCH 22/92] Drop: Removed first index of space from rows. --- hdwallet/cli/generate/addresses/addresses.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hdwallet/cli/generate/addresses/addresses.py b/hdwallet/cli/generate/addresses/addresses.py index 998c4ab..91f7c18 100644 --- a/hdwallet/cli/generate/addresses/addresses.py +++ b/hdwallet/cli/generate/addresses/addresses.py @@ -91,8 +91,12 @@ def generate_hdwallet_addresses( rows: str = "" dumps = hdwallet.dumps() - for key in [keys.split(":") for keys in show.split(",")]: - rows += f" {dumps[key[0]][key[1]] if len(key) == 2 else dumps[key[0]]}" + for i, key in enumerate([keys.split(":") for keys in show.split(",")]): + rows += ( + f"{dumps[key[0]][key[1]] if len(key) == 2 else dumps[key[0]]}" + if i == 0 else + f" {dumps[key[0]][key[1]] if len(key) == 2 else dumps[key[0]]}" + ) click.echo(rows) hdwallet.clean_derivation() From 4fe98629b35571d4299f5d14045d56da3bd5ccd6 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 10 Oct 2021 22:09:31 +0300 Subject: [PATCH 23/92] Fix: Hardened key for XPublic key on COIN_TYPE keys. --- hdwallet/cli/generate/addresses/addresses.py | 2 +- hdwallet/cli/generate/hdwallet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hdwallet/cli/generate/addresses/addresses.py b/hdwallet/cli/generate/addresses/addresses.py index 91f7c18..c1161c2 100644 --- a/hdwallet/cli/generate/addresses/addresses.py +++ b/hdwallet/cli/generate/addresses/addresses.py @@ -79,7 +79,7 @@ def generate_hdwallet_addresses( ), coin_type=( cryptocurrency.COIN_TYPE.INDEX, - cryptocurrency.COIN_TYPE.HARDENED + False if xpublic_key else cryptocurrency.COIN_TYPE.HARDENED ), account=( account, False if xpublic_key else True diff --git a/hdwallet/cli/generate/hdwallet.py b/hdwallet/cli/generate/hdwallet.py index 9480a75..14084e7 100644 --- a/hdwallet/cli/generate/hdwallet.py +++ b/hdwallet/cli/generate/hdwallet.py @@ -91,7 +91,7 @@ def generate_hdwallet( ), coin_type=( cryptocurrency.COIN_TYPE.INDEX, - cryptocurrency.COIN_TYPE.HARDENED + False if xpublic_key else cryptocurrency.COIN_TYPE.HARDENED ), account=( account, False if xpublic_key else True From 253c9294d2a67c0abc0e4db83745cd22e12cc7f1 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 14 Oct 2021 20:20:46 +0300 Subject: [PATCH 24/92] Update: Docs on xprivate and xpublic keys. --- hdwallet/hdwallet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 9bdb3e0..6813201 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -406,7 +406,7 @@ def from_path(self, path: Union[str, Derivation]) -> "HDWallet": >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_path(path="m/44'/0'/'0/0/0") """ @@ -439,7 +439,7 @@ def from_index(self, index: int, hardened: bool = False) -> "HDWallet": >>> from hdwallet import HDWallet >>> from hdwallet.symbols import BTC >>> hdwallet = HDWallet(symbol=BTC) - >>> hdwallet.from_xprivate_key(root_xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") + >>> hdwallet.from_xprivate_key(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_index(index=44, hardened=True) >>> hdwallet.from_index(index=0, hardened=True) >>> hdwallet.from_index(index=0, hardened=True) From 8540759862ac8426571170e9b2e312276700f120 Mon Sep 17 00:00:00 2001 From: meherett Date: Fri, 15 Oct 2021 13:48:36 +0300 Subject: [PATCH 25/92] Fix: is_entropy function, added try except. --- hdwallet/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hdwallet/utils.py b/hdwallet/utils.py index 16edefa..652e6c0 100644 --- a/hdwallet/utils.py +++ b/hdwallet/utils.py @@ -119,7 +119,10 @@ def is_entropy(entropy: str) -> bool: True """ - return len(unhexlify(entropy)) in [16, 20, 24, 28, 32] + try: + return len(unhexlify(entropy)) in [16, 20, 24, 28, 32] + except: + return False def is_mnemonic(mnemonic: str, language: Optional[str] = None) -> bool: From c2eef7fd9853c88609df1298073d60dd8426e6bb Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 16 Oct 2021 23:00:12 +0300 Subject: [PATCH 26/92] Add: List command for cryptocurrency, languages and strengths. --- hdwallet/cli/list/__init__.py | 1 + hdwallet/cli/list/cryptocurrencies.py | 53 +++++++++++++++++++++++++++ hdwallet/cli/list/languages.py | 25 +++++++++++++ hdwallet/cli/list/strengths.py | 23 ++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 hdwallet/cli/list/__init__.py create mode 100644 hdwallet/cli/list/cryptocurrencies.py create mode 100644 hdwallet/cli/list/languages.py create mode 100644 hdwallet/cli/list/strengths.py diff --git a/hdwallet/cli/list/__init__.py b/hdwallet/cli/list/__init__.py new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/hdwallet/cli/list/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/hdwallet/cli/list/cryptocurrencies.py b/hdwallet/cli/list/cryptocurrencies.py new file mode 100644 index 0000000..d55a60e --- /dev/null +++ b/hdwallet/cli/list/cryptocurrencies.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +from tabulate import tabulate + +import inspect + +from hdwallet import cryptocurrencies +from hdwallet.cli import click + + +def list_cryptocurrencies(): + + documents, table, headers = [], [], [ + "Cryptocurrency", "Symbol", "Mainnet", "Testnet", "Segwit", "Coin Type", "Default Path" + ] + + for name, cryptocurrency in inspect.getmembers(cryptocurrencies): + if inspect.isclass(cryptocurrency): + if issubclass(cryptocurrency, cryptocurrencies.Cryptocurrency) \ + and cryptocurrency != cryptocurrencies.Cryptocurrency: + + if cryptocurrency.NETWORK == "mainnet": + document: dict = { + "name": cryptocurrency.NAME, + "symbol": cryptocurrency.SYMBOL, + "source_code": cryptocurrency.SOURCE_CODE, + "mainnet": "Yes" if cryptocurrency.NETWORK == "mainnet" else "No", + "testnet": "Yes" if cryptocurrency.NETWORK == "testnet" else "No", + "segwit": "Yes" if cryptocurrency.SEGWIT_ADDRESS.HRP else "No", + "coin_type": cryptocurrency.COIN_TYPE.INDEX, + "default_path": cryptocurrency.DEFAULT_PATH + } + documents.append(document) + elif cryptocurrency.NETWORK == "testnet": + for index, document in enumerate(documents): + if document["name"] == cryptocurrency.NAME: + documents[index]["symbol"] = f"{document['symbol']}, {cryptocurrency.SYMBOL}" + documents[index]["testnet"] = "Yes" + else: + raise Exception("Invalid cryptocurrency network type.") + + for document in documents: + table.append([ + document["name"], + document["symbol"], + document["mainnet"], + document["testnet"], + document["segwit"], + document["coin_type"], + document["default_path"] + ]) + + click.echo(tabulate(table, headers, tablefmt="github")) diff --git a/hdwallet/cli/list/languages.py b/hdwallet/cli/list/languages.py new file mode 100644 index 0000000..11aed27 --- /dev/null +++ b/hdwallet/cli/list/languages.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +from tabulate import tabulate + +from hdwallet.cli import click + + +def list_languages(): + + click.echo(tabulate( + [ + ["Chinese Simplified"], + ["Chinese Traditional"], + ["English"], + ["French"], + ["Italian"], + ["Japanese"], + ["Korean"], + ["Spanish"], + ], + [ + "Language" + ], + tablefmt="github" + )) diff --git a/hdwallet/cli/list/strengths.py b/hdwallet/cli/list/strengths.py new file mode 100644 index 0000000..d0e9114 --- /dev/null +++ b/hdwallet/cli/list/strengths.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +from tabulate import tabulate + +from hdwallet.cli import click + + +def list_strengths(): + + click.echo(tabulate( + [ + [128, 12], + [160, 15], + [192, 18], + [224, 21], + [256, 24], + ], + [ + "Strength", + "Words" + ], + tablefmt="github" + )) From 39ff4dca82b37e15baaaccdd97374410338394fe Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 17 Oct 2021 10:47:06 +0300 Subject: [PATCH 27/92] Add: Command line interface tester components. --- tests/cli/__init__.py | 1 + tests/cli/generate/__init__.py | 0 tests/cli/generate/test_addresses.py | 40 +++++++++++++++++++++++ tests/cli/generate/test_hdwallet.py | 36 +++++++++++++++++++++ tests/cli/list/__init__.py | 0 tests/cli/list/test_cryptocurrencies.py | 42 ++++++++++++++++++++++++ tests/cli/list/test_languages.py | 43 +++++++++++++++++++++++++ tests/cli/list/test_strengths.py | 43 +++++++++++++++++++++++++ tests/cli/test_cli.py | 15 +++++++++ 9 files changed, 220 insertions(+) create mode 100644 tests/cli/__init__.py create mode 100644 tests/cli/generate/__init__.py create mode 100644 tests/cli/generate/test_addresses.py create mode 100644 tests/cli/generate/test_hdwallet.py create mode 100644 tests/cli/list/__init__.py create mode 100644 tests/cli/list/test_cryptocurrencies.py create mode 100644 tests/cli/list/test_languages.py create mode 100644 tests/cli/list/test_strengths.py create mode 100644 tests/cli/test_cli.py diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py new file mode 100644 index 0000000..5f7ce86 --- /dev/null +++ b/tests/cli/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 \ No newline at end of file diff --git a/tests/cli/generate/__init__.py b/tests/cli/generate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/generate/test_addresses.py b/tests/cli/generate/test_addresses.py new file mode 100644 index 0000000..8a6c7b3 --- /dev/null +++ b/tests/cli/generate/test_addresses.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import json +import os + +from hdwallet.cli.__main__ import main as cli_main + +# Test Values +base_path = os.path.dirname(__file__) +file_path = os.path.abspath(os.path.join(base_path, "..", "..", "values.json")) +values = open(file_path, "r", encoding="utf-8") +_ = json.loads(values.read()) +values.close() + + +def test_addresses(cli_tester): + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", "addresses", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--entropy", _["bitcoin"]["mainnet"]["entropy"], + "--passphrase", _["bitcoin"]["mainnet"]["passphrase"], + "--language", _["bitcoin"]["mainnet"]["language"] + ] + ) + + dumps: dict = _["bitcoin"]["mainnet"] + + del dumps["root_xprivate_key_hex"] + del dumps["root_xpublic_key_hex"] + del dumps["xprivate_key_hex"] + del dumps["xpublic_key_hex"] + + addresses: str = "" + for address in _["bitcoin"]["addresses"]: + addresses += f"{address}\n" + + assert hdwallet.exit_code == 0 + assert hdwallet.output == addresses diff --git a/tests/cli/generate/test_hdwallet.py b/tests/cli/generate/test_hdwallet.py new file mode 100644 index 0000000..b46c4fd --- /dev/null +++ b/tests/cli/generate/test_hdwallet.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import json +import os + +from hdwallet.cli.__main__ import main as cli_main + +# Test Values +base_path = os.path.dirname(__file__) +file_path = os.path.abspath(os.path.join(base_path, "..", "..", "values.json")) +values = open(file_path, "r", encoding="utf-8") +_ = json.loads(values.read()) +values.close() + + +def test_hdwallet(cli_tester): + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--entropy", _["bitcoin"]["mainnet"]["entropy"], + "--passphrase", _["bitcoin"]["mainnet"]["passphrase"], + "--language", _["bitcoin"]["mainnet"]["language"] + ] + ) + + dumps: dict = _["bitcoin"]["mainnet"] + + del dumps["root_xprivate_key_hex"] + del dumps["root_xpublic_key_hex"] + del dumps["xprivate_key_hex"] + del dumps["xpublic_key_hex"] + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" diff --git a/tests/cli/list/__init__.py b/tests/cli/list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cli/list/test_cryptocurrencies.py b/tests/cli/list/test_cryptocurrencies.py new file mode 100644 index 0000000..e8fc25f --- /dev/null +++ b/tests/cli/list/test_cryptocurrencies.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + + +from hdwallet.cli.__main__ import main as cli_main + + +def test_cryptocurrencies(cli_tester): + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "cryptocurrencies" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "cryptocurrencies" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "c" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "c" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output diff --git a/tests/cli/list/test_languages.py b/tests/cli/list/test_languages.py new file mode 100644 index 0000000..2c9537d --- /dev/null +++ b/tests/cli/list/test_languages.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + + +from hdwallet.cli.__main__ import main as cli_main + + +def test_languages(cli_tester): + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "languages" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "languages" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "l" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "l" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output diff --git a/tests/cli/list/test_strengths.py b/tests/cli/list/test_strengths.py new file mode 100644 index 0000000..d3419dd --- /dev/null +++ b/tests/cli/list/test_strengths.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + + +from hdwallet.cli.__main__ import main as cli_main + + +def test_strengths(cli_tester): + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "strengths" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "strengths" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "list", "s" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "l", "s" + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py new file mode 100644 index 0000000..adf54fb --- /dev/null +++ b/tests/cli/test_cli.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from hdwallet.cli.__main__ import main as cli_main +from hdwallet import __version__ + + +def test_hdwallet_cli(cli_tester): + + assert cli_tester.invoke(cli_main).exit_code == 0 + + assert cli_tester.invoke(cli_main, ["generate"]).exit_code == 0 + + version = cli_tester.invoke(cli_main, ["--version"]) + assert version.exit_code == 0 + assert version.output == "%s\n" % __version__ From 418eb73fd8caa81634a348d63873eb2b094fca5f Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 17 Oct 2021 12:46:52 +0300 Subject: [PATCH 28/92] Update: Command line interface off generate, hdwallet & addresses. --- hdwallet/cli/__main__.py | 216 ++++++++++++++++-- hdwallet/cli/generate/__init__.py | 93 -------- .../cli/generate/{addresses => }/addresses.py | 7 +- hdwallet/cli/generate/addresses/__init__.py | 87 ------- hdwallet/cli/generate/hdwallet.py | 42 ++-- 5 files changed, 222 insertions(+), 223 deletions(-) rename hdwallet/cli/generate/{addresses => }/addresses.py (97%) delete mode 100644 hdwallet/cli/generate/addresses/__init__.py diff --git a/hdwallet/cli/__main__.py b/hdwallet/cli/__main__.py index 8cba88d..bf757c2 100644 --- a/hdwallet/cli/__main__.py +++ b/hdwallet/cli/__main__.py @@ -1,30 +1,22 @@ #!/usr/bin/env python -# coding=utf-8 + +from click_aliases import ClickAliasedGroup +from typing import Optional from hdwallet import __version__ -from hdwallet.cli.generate import generate +from hdwallet.cli.generate.hdwallet import generate_hdwallet +from hdwallet.cli.generate.addresses import generate_addresses +from hdwallet.cli.list.cryptocurrencies import list_cryptocurrencies +from hdwallet.cli.list.languages import list_languages +from hdwallet.cli.list.strengths import list_strengths from hdwallet.cli import click + CONTEXT_SETTINGS = dict( help_option_names=["-h", "--help"], ) -class AliasedGroup(click.Group): - - def get_command(self, ctx, cmd_name): - rv = click.Group.get_command(self, ctx, cmd_name) - if rv is not None: - return rv - matches = [x for x in self.list_commands(ctx) - if x.startswith(cmd_name)] - if not matches: - return None - elif len(matches) == 1: - return click.Group.get_command(self, ctx, matches[0]) - ctx.fail("Too many matches: %s" % ", ".join(sorted(matches))) - - def print_version(ctx, param, value): if not value or ctx.resilient_parsing: return @@ -32,7 +24,7 @@ def print_version(ctx, param, value): ctx.exit() -@click.group(cls=AliasedGroup, +@click.group(cls=ClickAliasedGroup, options_metavar="[OPTIONS]", context_settings=CONTEXT_SETTINGS) @click.option("-v", "--version", is_flag=True, callback=print_version, expose_value=False, help="Show HDWallet version and exit.") @@ -40,4 +32,190 @@ def main(): pass -main.add_command(generate) +@main.group("generate", aliases=["g"], cls=ClickAliasedGroup, options_metavar="[OPTIONS]", + short_help="Select Generate for HDWallet.", invoke_without_command=True) +@click.option("-s", "--symbol", type=str, default="BTC", + help="Set Cryptocurrency ticker symbol.") +@click.option("-sg", "--strength", type=int, default=128, + help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) +@click.option("-e", "--entropy", type=str, default=None, + help="Set Master key from entropy hex string.", show_default=True) +@click.option("-m", "--mnemonic", type=str, default=None, + help="Set Master key from mnemonic words.", show_default=True) +@click.option("-l", "--language", type=str, default="english", + help="Set Language for mnemonic, choose language english, french, italian, spanish, " + "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) +@click.option("-pa", "--passphrase", type=str, default=None, + help="Set Passphrase for mnemonic.", show_default=True) +@click.option("-sd", "--seed", type=str, default=None, + help="Set Master key from seed hex string.", show_default=True) +@click.option("-xprv", "--xprivate-key", type=str, default=None, + help="Set Master key from xprivate key.", show_default=True) +@click.option("-xpub", "--xpublic-key", type=str, default=None, + help="Set Master key from xpublic key.", show_default=True) +@click.option("-st", "--strict", type=bool, default=False, + help="Set Strict for root keys.", show_default=True) +@click.option("-ac", "--account", type=int, default=0, + help="Set derivation from account.", show_default=True) +@click.option("-ch", "--change", type=bool, default=False, + help="Set Derivation from change.", show_default=True) +@click.option("-ad", "--address", type=int, default=0, + help="Set Derivation from address.", show_default=True) +@click.option("-p", "--path", type=str, default=None, + help="Set Master key derivation path.", show_default=True) +@click.option("-prv", "--private-key", type=str, default=None, + help="Set Master key from private key.", show_default=True) +@click.option("-pub", "--public-key", type=str, default=None, + help="Set Master key from public key.", show_default=True) +@click.option("-w", "--wif", type=str, default=None, + help="Set Master key from wallet important format.", show_default=True) +@click.option("-sm", "--semantic", type=str, default="p2pkh", + help="Set Semantic for xprivate and xpublic keys.", show_default=True) +@click.pass_context +def generate( + context: click.core.Context, + symbol: str, + strength: int, + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: bool, + account: int, + change: bool, + address: int, + path: Optional[str], + private_key: Optional[str], + public_key: Optional[str], + wif: Optional[str], + semantic: str +): + if context.invoked_subcommand is None: + return generate_hdwallet( + symbol=symbol, + strength=strength, + entropy=entropy, + mnemonic=mnemonic, + language=language, + passphrase=passphrase, + seed=seed, + xprivate_key=xprivate_key, + xpublic_key=xpublic_key, + strict=strict, + account=account, + change=change, + address=address, + path=path, + private_key=private_key, + public_key=public_key, + wif=wif, + semantic=semantic + ) + + +@generate.command("addresses", aliases=["a"], options_metavar="[OPTIONS]", + short_help="Select Addresses for generation HDWallet addresses.") +@click.option("-s", "--symbol", type=str, required=True, + help="Set Cryptocurrency ticker symbol.") +@click.option("-sg", "--strength", type=int, default=128, + help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) +@click.option("-e", "--entropy", type=str, default=None, + help="Set Master key from entropy hex string.", show_default=True) +@click.option("-m", "--mnemonic", type=str, default=None, + help="Set Master key from mnemonic words.", show_default=True) +@click.option("-l", "--language", type=str, default="english", + help="Set Language for mnemonic, choose language english, french, italian, spanish, " + "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) +@click.option("-pa", "--passphrase", type=str, default=None, + help="Set Passphrase for mnemonic.", show_default=True) +@click.option("-sd", "--seed", type=str, default=None, + help="Set Master key from seed hex string.", show_default=True) +@click.option("-xprv", "--xprivate-key", type=str, default=None, + help="Set Master key from xprivate key.", show_default=True) +@click.option("-xpub", "--xpublic-key", type=str, default=None, + help="Set Master key from xpublic key.", show_default=True) +@click.option("-st", "--strict", type=bool, default=False, + help="Set Strict for root keys.", show_default=True) +@click.option("-ac", "--account", type=int, default=0, + help="Set derivation from account.", show_default=True) +@click.option("-ch", "--change", type=bool, default=False, + help="Set Derivation from change.", show_default=True) +@click.option("-p", "--path", type=str, default=None, + help="Set Master key derivation path.", show_default=True) +@click.option("-se", "--semantic", type=str, default="p2pkh", + help="Set Semantic for xprivate and xpublic keys.", show_default=True) +@click.option("-h", "--hardened", type=bool, default=False, + help="Set Hardened for addresses.", show_default=True) +@click.option("-si", "--start-index", type=int, default=0, + help="Set Start from address index.", show_default=True) +@click.option("-ei", "--end-index", type=int, default=20, + help="Set End to address index.", show_default=True) +@click.option("-sh", "--show", type=str, default="path,addresses:p2pkh,public_key,wif", + help="Set Value key of generated HDWallet data to show.", show_default=True) +def addresses( + symbol: str, + strength: int, + entropy: Optional[str], + mnemonic: Optional[str], + language: Optional[str], + passphrase: Optional[str], + seed: Optional[str], + xprivate_key: Optional[str], + xpublic_key: Optional[str], + strict: bool, + account: int, + change: bool, + path: Optional[str], + semantic: str, + start_index: int, + end_index: int, + hardened: bool, + show: str +): + return generate_addresses( + symbol=symbol, + strength=strength, + entropy=entropy, + mnemonic=mnemonic, + language=language, + passphrase=passphrase, + seed=seed, + xprivate_key=xprivate_key, + xpublic_key=xpublic_key, + strict=strict, + account=account, + change=change, + path=path, + semantic=semantic, + start_index=start_index, + end_index=end_index, + hardened=hardened, + show=show + ) + + +@main.group("list", aliases=["l"], cls=ClickAliasedGroup, options_metavar="[OPTIONS]", + short_help="Select List for HDWallet information.", invoke_without_command=True) +def list(): + pass + + +@list.command("cryptocurrencies", aliases=["c"], options_metavar="[OPTIONS]", + short_help="List Available cryptocurrencies of HDWallet.") +def cryptocurrencies(): + return list_cryptocurrencies() + + +@list.command("languages", aliases=["l"], options_metavar="[OPTIONS]", + short_help="List Languages of mnemonic words.") +def languages(): + return list_languages() + + +@list.command("strengths", aliases=["s"], options_metavar="[OPTIONS]", + short_help="List Strengths of mnemonic words.") +def strengths(): + return list_strengths() diff --git a/hdwallet/cli/generate/__init__.py b/hdwallet/cli/generate/__init__.py index 34dcb0b..4265cc3 100644 --- a/hdwallet/cli/generate/__init__.py +++ b/hdwallet/cli/generate/__init__.py @@ -1,94 +1 @@ #!/usr/bin/env python - -from typing import Optional - -from hdwallet.cli import click -from hdwallet.cli.generate.hdwallet import generate_hdwallet -from hdwallet.cli.generate.addresses import addresses - - -@click.group("generate", options_metavar="[OPTIONS]", - short_help="Select Generate for HDWallet.", invoke_without_command=True) -@click.option("-s", "--symbol", type=str, default="BTC", - help="Set Cryptocurrency ticker symbol.") -@click.option("-sg", "--strength", type=int, default=128, - help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) -@click.option("-e", "--entropy", type=str, default=None, - help="Set Master key from entropy hex string.", show_default=True) -@click.option("-m", "--mnemonic", type=str, default=None, - help="Set Master key from mnemonic words.", show_default=True) -@click.option("-l", "--language", type=str, default="english", - help="Set Language for mnemonic, choose language english, french, italian, spanish, " - "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) -@click.option("-pa", "--passphrase", type=str, default=None, - help="Set Passphrase for mnemonic.", show_default=True) -@click.option("-sd", "--seed", type=str, default=None, - help="Set Master key from seed hex string.", show_default=True) -@click.option("-xprv", "--xprivate-key", type=str, default=None, - help="Set Master key from xprivate key.", show_default=True) -@click.option("-xpub", "--xpublic-key", type=str, default=None, - help="Set Master key from xpublic key.", show_default=True) -@click.option("-st", "--strict", type=bool, default=False, - help="Set Strict for root keys.", show_default=True) -@click.option("-ac", "--account", type=int, default=0, - help="Set derivation from account.", show_default=True) -@click.option("-ch", "--change", type=bool, default=False, - help="Set Derivation from change.", show_default=True) -@click.option("-ad", "--address", type=int, default=0, - help="Set Derivation from address.", show_default=True) -@click.option("-p", "--path", type=str, default=None, - help="Set Master key derivation path.", show_default=True) -@click.option("-prv", "--private-key", type=str, default=None, - help="Set Master key from private key.", show_default=True) -@click.option("-pub", "--public-key", type=str, default=None, - help="Set Master key from public key.", show_default=True) -@click.option("-w", "--wif", type=str, default=None, - help="Set Master key from wallet important format.", show_default=True) -@click.option("-sm", "--semantic", type=str, default="p2pkh", - help="Set Semantic for xprivate and xpublic keys.", show_default=True) -@click.pass_context -def generate( - context: click.core.Context, - symbol: str, - strength: int, - entropy: Optional[str], - mnemonic: Optional[str], - language: Optional[str], - passphrase: Optional[str], - seed: Optional[str], - xprivate_key: Optional[str], - xpublic_key: Optional[str], - strict: bool, - account: int, - change: bool, - address: int, - path: Optional[str], - private_key: Optional[str], - public_key: Optional[str], - wif: Optional[str], - semantic: str -): - if context.invoked_subcommand is None: - return generate_hdwallet( - symbol=symbol, - strength=strength, - entropy=entropy, - mnemonic=mnemonic, - language=language, - passphrase=passphrase, - seed=seed, - xprivate_key=xprivate_key, - xpublic_key=xpublic_key, - strict=strict, - account=account, - change=change, - address=address, - path=path, - private_key=private_key, - public_key=public_key, - wif=wif, - semantic=semantic - ) - - -generate.add_command(addresses) diff --git a/hdwallet/cli/generate/addresses/addresses.py b/hdwallet/cli/generate/addresses.py similarity index 97% rename from hdwallet/cli/generate/addresses/addresses.py rename to hdwallet/cli/generate/addresses.py index c1161c2..60881f3 100644 --- a/hdwallet/cli/generate/addresses/addresses.py +++ b/hdwallet/cli/generate/addresses.py @@ -15,9 +15,9 @@ ) -def generate_hdwallet_addresses( +def generate_addresses( symbol: str, - strength: Optional[int], + strength: int, entropy: Optional[str], mnemonic: Optional[str], language: Optional[str], @@ -25,7 +25,7 @@ def generate_hdwallet_addresses( seed: Optional[str], xprivate_key: Optional[str], xpublic_key: Optional[str], - strict: Optional[bool], + strict: bool, account: int, change: bool, path: Optional[str], @@ -36,7 +36,6 @@ def generate_hdwallet_addresses( show: str ): try: - hdwallet: HDWallet = HDWallet( symbol=symbol, semantic=semantic ) diff --git a/hdwallet/cli/generate/addresses/__init__.py b/hdwallet/cli/generate/addresses/__init__.py deleted file mode 100644 index af1bcd0..0000000 --- a/hdwallet/cli/generate/addresses/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python - -from typing import Optional - -from hdwallet.cli.generate.addresses.addresses import generate_hdwallet_addresses -from hdwallet.cli import click - - -@click.command("addresses", options_metavar="[OPTIONS]", - short_help="Select Addresses for generation HDWallet addresses.") -@click.option("-s", "--symbol", type=str, required=True, - help="Set Cryptocurrency ticker symbol.") -@click.option("-sg", "--strength", type=int, default=128, - help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) -@click.option("-e", "--entropy", type=str, default=None, - help="Set Master key from entropy hex string.", show_default=True) -@click.option("-m", "--mnemonic", type=str, default=None, - help="Set Master key from mnemonic words.", show_default=True) -@click.option("-l", "--language", type=str, default="english", - help="Set Language for mnemonic, choose language english, french, italian, spanish, " - "chinese_simplified, chinese_traditional, japanese or korean only.", show_default=True) -@click.option("-pa", "--passphrase", type=str, default=None, - help="Set Passphrase for mnemonic.", show_default=True) -@click.option("-sd", "--seed", type=str, default=None, - help="Set Master key from seed hex string.", show_default=True) -@click.option("-xprv", "--xprivate-key", type=str, default=None, - help="Set Master key from xprivate key.", show_default=True) -@click.option("-xpub", "--xpublic-key", type=str, default=None, - help="Set Master key from xpublic key.", show_default=True) -@click.option("-st", "--strict", type=bool, default=False, - help="Set Strict for root keys.", show_default=True) -@click.option("-ac", "--account", type=int, default=0, - help="Set derivation from account.", show_default=True) -@click.option("-ch", "--change", type=bool, default=False, - help="Set Derivation from change.", show_default=True) -@click.option("-p", "--path", type=str, default=None, - help="Set Master key derivation path.", show_default=True) -@click.option("-se", "--semantic", type=str, default="p2pkh", - help="Set Semantic for xprivate and xpublic keys.", show_default=True) -@click.option("-h", "--hardened", type=bool, default=False, - help="Set Hardened for addresses.", show_default=True) -@click.option("-si", "--start-index", type=int, default=0, - help="Set Start from address index.", show_default=True) -@click.option("-ei", "--end-index", type=int, default=20, - help="Set End to address index.", show_default=True) -@click.option("-sh", "--show", type=str, default="path,addresses:p2pkh,public_key,wif", - help="Set Value key of generated HDWallet data to show.", show_default=True) -def addresses( - symbol: str, - strength: int, - entropy: Optional[str], - mnemonic: Optional[str], - language: Optional[str], - passphrase: Optional[str], - seed: Optional[str], - xprivate_key: Optional[str], - xpublic_key: Optional[str], - strict: bool, - account: int, - change: bool, - path: Optional[str], - semantic: str, - start_index: int, - end_index: int, - hardened: bool, - show: str -): - return generate_hdwallet_addresses( - symbol=symbol, - strength=strength, - entropy=entropy, - mnemonic=mnemonic, - language=language, - passphrase=passphrase, - seed=seed, - xprivate_key=xprivate_key, - xpublic_key=xpublic_key, - strict=strict, - account=account, - change=change, - path=path, - semantic=semantic, - start_index=start_index, - end_index=end_index, - hardened=hardened, - show=show - ) diff --git a/hdwallet/cli/generate/hdwallet.py b/hdwallet/cli/generate/hdwallet.py index 14084e7..c83db28 100644 --- a/hdwallet/cli/generate/hdwallet.py +++ b/hdwallet/cli/generate/hdwallet.py @@ -38,7 +38,6 @@ def generate_hdwallet( semantic: str ): try: - hdwallet: HDWallet = HDWallet( symbol=symbol, semantic=semantic ) @@ -80,26 +79,29 @@ def generate_hdwallet( mnemonic=mnemonic, language=language, passphrase=passphrase ) - if path: - derivation: Derivation = Derivation(path=path) - hdwallet.from_path(path=derivation) + if wif or private_key or public_key: + pass else: - cryptocurrency: Cryptocurrency = get_cryptocurrency(symbol=symbol) - bip32_derivation: BIP32Derivation = BIP32Derivation( - purpose=( - 44, False if xpublic_key else True - ), - coin_type=( - cryptocurrency.COIN_TYPE.INDEX, - False if xpublic_key else cryptocurrency.COIN_TYPE.HARDENED - ), - account=( - account, False if xpublic_key else True - ), - change=change, - address=address - ) - hdwallet.from_path(path=bip32_derivation) + if path: + derivation: Derivation = Derivation(path=path) + hdwallet.from_path(path=derivation) + else: + cryptocurrency: Cryptocurrency = get_cryptocurrency(symbol=symbol) + bip32_derivation: BIP32Derivation = BIP32Derivation( + purpose=( + 44, False if xpublic_key else True + ), + coin_type=( + cryptocurrency.COIN_TYPE.INDEX, + False if xpublic_key else cryptocurrency.COIN_TYPE.HARDENED + ), + account=( + account, False if xpublic_key else True + ), + change=change, + address=address + ) + hdwallet.from_path(path=bip32_derivation) click.echo(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) From 721439906da7e704039a56aca663721d56897c78 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 17 Oct 2021 12:47:49 +0300 Subject: [PATCH 29/92] Fix: Derivation class from_index function PATH value. --- hdwallet/derivations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hdwallet/derivations.py b/hdwallet/derivations.py index db932c2..b34a6d4 100644 --- a/hdwallet/derivations.py +++ b/hdwallet/derivations.py @@ -27,7 +27,7 @@ class Derivation: >>> Derivation() >>> str(Derivation()) - "" + "\0\0\0\0" >>> str(Derivation(path="m/44'/0'/0'/0/0", semantic="p2pkh")) "m/44'/0'/0'/0/0" @@ -106,6 +106,8 @@ def from_index(self, index: int, hardened: bool = False) -> "Derivation": if not isinstance(index, int): raise DerivationError("Bad derivation index, Please import only int type!") + if self.PATH == "\0\0\0\0": + self.PATH = "" self.PATH += ( (f"/{index}'" if hardened else f"/{index}") if self.PATH.startswith("m/") else @@ -127,7 +129,7 @@ def clean_derivation(self) -> "Derivation": >>> derivation.clean_derivation() >>> str(derivation) - "" + "\0\0\0\0" """ self.PATH = "\0\0\0\0" From 89fc21223e3c377ab5e74eb2ea86ac1480bc8966 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 17 Oct 2021 12:48:55 +0300 Subject: [PATCH 30/92] Fix & Update: All tester components of HDWallet with CLI commands. --- tests/__init__.py | 1 + tests/cli/generate/test_addresses.py | 46 +++++++ tests/cli/generate/test_hdwallet.py | 100 ++++++++++++++ tests/conftest.py | 20 +++ tests/hdwallet/__init__.py | 1 + tests/{ => hdwallet}/test_from_entropy.py | 2 +- tests/{ => hdwallet}/test_from_mnemonic.py | 2 +- tests/{ => hdwallet}/test_from_private_key.py | 2 +- tests/{ => hdwallet}/test_from_public_key.py | 2 +- tests/{ => hdwallet}/test_from_seed.py | 2 +- tests/{ => hdwallet}/test_from_wif.py | 2 +- .../{ => hdwallet}/test_from_xprivate_key.py | 2 +- tests/{ => hdwallet}/test_from_xpublic_key.py | 2 +- tests/test_derivations.py | 125 ++++++++++++++++++ tests/test_exceptions.py | 31 +++++ tests/test_utils.py | 14 +- tests/values.json | 24 +++- 17 files changed, 367 insertions(+), 11 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/hdwallet/__init__.py rename tests/{ => hdwallet}/test_from_entropy.py (98%) rename tests/{ => hdwallet}/test_from_mnemonic.py (97%) rename tests/{ => hdwallet}/test_from_private_key.py (97%) rename tests/{ => hdwallet}/test_from_public_key.py (97%) rename tests/{ => hdwallet}/test_from_seed.py (97%) rename tests/{ => hdwallet}/test_from_wif.py (97%) rename tests/{ => hdwallet}/test_from_xprivate_key.py (98%) rename tests/{ => hdwallet}/test_from_xpublic_key.py (98%) create mode 100644 tests/test_derivations.py create mode 100644 tests/test_exceptions.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..5f7ce86 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 \ No newline at end of file diff --git a/tests/cli/generate/test_addresses.py b/tests/cli/generate/test_addresses.py index 8a6c7b3..52bb3b1 100644 --- a/tests/cli/generate/test_addresses.py +++ b/tests/cli/generate/test_addresses.py @@ -38,3 +38,49 @@ def test_addresses(cli_tester): assert hdwallet.exit_code == 0 assert hdwallet.output == addresses + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", "addresses", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--mnemonic", _["bitcoin"]["mainnet"]["mnemonic"], + "--passphrase", _["bitcoin"]["mainnet"]["passphrase"], + "--language", _["bitcoin"]["mainnet"]["language"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output == addresses + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", "addresses", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--seed", _["bitcoin"]["mainnet"]["seed"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output == addresses + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", "addresses", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--xprivate-key", _["bitcoin"]["mainnet"]["root_xprivate_key"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output == addresses + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", "addresses", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--xpublic-key", _["bitcoin"]["mainnet"]["root_xpublic_key"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output diff --git a/tests/cli/generate/test_hdwallet.py b/tests/cli/generate/test_hdwallet.py index b46c4fd..33ab587 100644 --- a/tests/cli/generate/test_hdwallet.py +++ b/tests/cli/generate/test_hdwallet.py @@ -34,3 +34,103 @@ def test_hdwallet(cli_tester): assert hdwallet.exit_code == 0 assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--mnemonic", _["bitcoin"]["mainnet"]["mnemonic"], + "--passphrase", _["bitcoin"]["mainnet"]["passphrase"], + "--language", _["bitcoin"]["mainnet"]["language"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--seed", _["bitcoin"]["mainnet"]["seed"] + ] + ) + + dumps["strength"] = None + dumps["entropy"] = None + dumps["mnemonic"] = None + dumps["language"] = None + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--xprivate-key", _["bitcoin"]["mainnet"]["root_xprivate_key"] + ] + ) + + dumps["strength"] = None + dumps["entropy"] = None + dumps["mnemonic"] = None + dumps["language"] = None + dumps["seed"] = None + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--xpublic-key", _["bitcoin"]["mainnet"]["root_xpublic_key"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--private-key", _["bitcoin"]["mainnet"]["private_key"] + ] + ) + + dumps["root_xprivate_key"] = None + dumps["root_xpublic_key"] = None + dumps["xprivate_key"] = None + dumps["xpublic_key"] = None + dumps["chain_code"] = None + dumps["path"] = None + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--wif", _["bitcoin"]["mainnet"]["wif"] + ] + ) + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" + + hdwallet = cli_tester.invoke( + cli_main, [ + "generate", + "--symbol", _["bitcoin"]["mainnet"]["symbol"], + "--public-key", _["bitcoin"]["mainnet"]["public_key"] + ] + ) + + dumps["private_key"] = None + dumps["wif"] = None + + assert hdwallet.exit_code == 0 + assert hdwallet.output == str(json.dumps(dumps, indent=4, ensure_ascii=False)) + "\n" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9c6c3d1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 + +from pathlib import Path +from click.testing import CliRunner + +import os +import pytest + + +@pytest.fixture(scope="module") +def project_path(): + original_path = os.getcwd() + os.chdir(original_path + "/tests") + yield Path(original_path + "/tests") + os.chdir(original_path) + + +@pytest.fixture(scope="module") +def cli_tester(): + return CliRunner() diff --git a/tests/hdwallet/__init__.py b/tests/hdwallet/__init__.py new file mode 100644 index 0000000..5f7ce86 --- /dev/null +++ b/tests/hdwallet/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 \ No newline at end of file diff --git a/tests/test_from_entropy.py b/tests/hdwallet/test_from_entropy.py similarity index 98% rename from tests/test_from_entropy.py rename to tests/hdwallet/test_from_entropy.py index fc50085..30a1020 100644 --- a/tests/test_from_entropy.py +++ b/tests/hdwallet/test_from_entropy.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_mnemonic.py b/tests/hdwallet/test_from_mnemonic.py similarity index 97% rename from tests/test_from_mnemonic.py rename to tests/hdwallet/test_from_mnemonic.py index 3bfcafd..2bad1df 100644 --- a/tests/test_from_mnemonic.py +++ b/tests/hdwallet/test_from_mnemonic.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_private_key.py b/tests/hdwallet/test_from_private_key.py similarity index 97% rename from tests/test_from_private_key.py rename to tests/hdwallet/test_from_private_key.py index fe3295d..7ab9451 100644 --- a/tests/test_from_private_key.py +++ b/tests/hdwallet/test_from_private_key.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_public_key.py b/tests/hdwallet/test_from_public_key.py similarity index 97% rename from tests/test_from_public_key.py rename to tests/hdwallet/test_from_public_key.py index 3c75696..bdf5c7c 100644 --- a/tests/test_from_public_key.py +++ b/tests/hdwallet/test_from_public_key.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_seed.py b/tests/hdwallet/test_from_seed.py similarity index 97% rename from tests/test_from_seed.py rename to tests/hdwallet/test_from_seed.py index a85a76e..3eeeb10 100644 --- a/tests/test_from_seed.py +++ b/tests/hdwallet/test_from_seed.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_wif.py b/tests/hdwallet/test_from_wif.py similarity index 97% rename from tests/test_from_wif.py rename to tests/hdwallet/test_from_wif.py index e4d666c..65c112f 100644 --- a/tests/test_from_wif.py +++ b/tests/hdwallet/test_from_wif.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_xprivate_key.py b/tests/hdwallet/test_from_xprivate_key.py similarity index 98% rename from tests/test_from_xprivate_key.py rename to tests/hdwallet/test_from_xprivate_key.py index c93eee8..17a4b68 100644 --- a/tests/test_from_xprivate_key.py +++ b/tests/hdwallet/test_from_xprivate_key.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_from_xpublic_key.py b/tests/hdwallet/test_from_xpublic_key.py similarity index 98% rename from tests/test_from_xpublic_key.py rename to tests/hdwallet/test_from_xpublic_key.py index 34b870f..bd783c5 100644 --- a/tests/test_from_xpublic_key.py +++ b/tests/hdwallet/test_from_xpublic_key.py @@ -7,7 +7,7 @@ # Test Values base_path: str = os.path.dirname(__file__) -file_path: str = os.path.abspath(os.path.join(base_path, "values.json")) +file_path: str = os.path.abspath(os.path.join(base_path, "../values.json")) values = open(file_path, "r", encoding="utf-8") _: dict = json.loads(values.read()) values.close() diff --git a/tests/test_derivations.py b/tests/test_derivations.py new file mode 100644 index 0000000..27c81f6 --- /dev/null +++ b/tests/test_derivations.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +import pytest + +from hdwallet.cryptocurrencies import BitcoinMainnet +from hdwallet.derivations import ( + Derivation, BIP32Derivation, BIP44Derivation, BIP49Derivation, BIP84Derivation, BIP141Derivation +) +from hdwallet.exceptions import SemanticError + + +def test_derivations(): + + assert str( + Derivation(path="m/44'/0'/0'/0/0", semantic="p2pkh") + ) == "m/44'/0'/0'/0/0" + assert str( + Derivation(semantic="p2pkh").from_path( + path="m/44'/0'/0'/0/0" + ) + ) == "m/44'/0'/0'/0/0" + assert str( + Derivation(semantic="p2pkh").from_index( + index=44, hardened=True + ).from_index( + index=0, hardened=True + ).from_index( + index=0, hardened=True + ).from_index( + index=0, hardened=False + ).from_index( + index=0, hardened=False + ) + ) == "m/44'/0'/0'/0/0" + assert str( + Derivation(path="m/44'/0'/0'/0/0", semantic="p2pkh").clean_derivation() + ) == "\0\0\0\0" + + assert str( + BIP32Derivation(purpose=44, coin_type=0, account=0, change=False, address=0) + ) == "m/44'/0'/0'/0/0" + assert str( + BIP32Derivation(purpose=(44, False), coin_type=(0, False), account=0, change=False, address=0) + ) == "m/44/0/0'/0/0" + + bip32_derivation: BIP32Derivation = BIP32Derivation().from_purpose( + purpose=44, hardened=True + ).from_coin_type( + coin_type=0, hardened=True + ).from_account( + account=0, hardened=True + ).from_change( + change=False + ).from_address( + address=0, hardened=False + ) + assert str(bip32_derivation) == "m/44'/0'/0'/0/0" + assert bip32_derivation.purpose() == "44'" + assert bip32_derivation.coin_type() == "0'" + assert bip32_derivation.account() == "0'" + assert bip32_derivation.change() == 0 + assert bip32_derivation.address() == "0" + assert bip32_derivation.SEMANTIC == "p2pkh" + + bip44_derivation: BIP44Derivation = BIP44Derivation( + cryptocurrency=BitcoinMainnet + ).from_account( + account=0, hardened=True + ).from_change( + change=False + ).from_address( + address=0, hardened=False + ) + assert str(bip44_derivation) == "m/44'/0'/0'/0/0" + assert bip44_derivation.purpose() == "44'" + assert bip44_derivation.coin_type() == "0'" + assert bip44_derivation.account() == "0'" + assert bip44_derivation.change() == 0 + assert bip44_derivation.address() == "0" + assert bip44_derivation.SEMANTIC == "p2pkh" + + bip49_derivation: BIP49Derivation = BIP49Derivation( + cryptocurrency=BitcoinMainnet + ).from_account( + account=0, hardened=True + ).from_change( + change=False + ).from_address( + address=0, hardened=False + ) + assert str(bip49_derivation) == "m/49'/0'/0'/0/0" + assert bip49_derivation.purpose() == "49'" + assert bip49_derivation.coin_type() == "0'" + assert bip49_derivation.account() == "0'" + assert bip49_derivation.change() == 0 + assert bip49_derivation.address() == "0" + assert bip49_derivation.SEMANTIC == "p2wpkh_in_p2sh" + + bip84_derivation: BIP84Derivation = BIP84Derivation( + cryptocurrency=BitcoinMainnet + ).from_account( + account=0, hardened=True + ).from_change( + change=False + ).from_address( + address=0, hardened=False + ) + assert str(bip84_derivation) == "m/84'/0'/0'/0/0" + assert bip84_derivation.purpose() == "84'" + assert bip84_derivation.coin_type() == "0'" + assert bip84_derivation.account() == "0'" + assert bip84_derivation.change() == 0 + assert bip84_derivation.address() == "0" + assert bip84_derivation.SEMANTIC == "p2wpkh" + + bip141_derivation: BIP141Derivation = BIP141Derivation( + cryptocurrency=BitcoinMainnet, semantic="p2wsh_in_p2sh" + ) + assert str(bip141_derivation) == "m/44'/0'/0'/0/0" + assert bip141_derivation.SEMANTIC == "p2wsh_in_p2sh" + + with pytest.raises(SemanticError, match="Wrong extended semantic, choose only the following options 'p2wpkh', 'p2wpkh_in_p2sh', 'p2wsh' or 'p2wsh_in_p2sh' semantics."): + BIP141Derivation( + cryptocurrency=BitcoinMainnet, semantic="p2pkh" + ) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..8dd2699 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +from hdwallet.exceptions import ( + NetworkError, DerivationError, SemanticError, AddressError, SymbolError +) + +import pytest + + +def test_exceptions(): + + with pytest.raises(NetworkError, match="error"): + raise NetworkError("error") + with pytest.raises(NetworkError, match="error, error"): + raise NetworkError("error", "error") + with pytest.raises(DerivationError, match="error"): + raise DerivationError("error") + with pytest.raises(DerivationError, match="error, error"): + raise DerivationError("error", "error") + with pytest.raises(SemanticError, match="error"): + raise SemanticError("error") + with pytest.raises(SemanticError): + raise SemanticError("error", "error") + with pytest.raises(AddressError, match="error"): + raise AddressError("error") + with pytest.raises(AddressError, match="error, error"): + raise AddressError("error", "error") + with pytest.raises(SymbolError, match="error"): + raise SymbolError("error") + with pytest.raises(SymbolError, match="error, error"): + raise SymbolError("error", "error") diff --git a/tests/test_utils.py b/tests/test_utils.py index 6e6dba0..f390e5f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,8 +6,8 @@ from hdwallet.utils import ( get_bytes, generate_mnemonic, generate_entropy, is_entropy, get_mnemonic_strength, - get_entropy_strength, is_mnemonic, get_mnemonic_language, - is_root_xprivate_key, is_root_xpublic_key + get_entropy_strength, is_mnemonic, get_mnemonic_language, entropy_to_mnemonic, mnemonic_to_entropy, + is_root_xprivate_key, is_root_xpublic_key, generate_passphrase ) # Test Values @@ -40,6 +40,8 @@ def test_utils(): symbol=_["bitcoin"]["mainnet"]["symbol"] ) + assert len(generate_passphrase(length=19999)) == 19999 + def test_utils_entropy(): @@ -55,6 +57,10 @@ def test_utils_entropy(): assert get_entropy_strength(entropy["entropy"]) == entropy["strength"] assert is_entropy(entropy["entropy"]) + assert mnemonic_to_entropy( + mnemonic=_["bitcoin"]["mainnet"]["mnemonic"], language=_["bitcoin"]["mainnet"]["language"] + ) == _["bitcoin"]["mainnet"]["entropy"] + def test_utils_mnemonic(): @@ -73,3 +79,7 @@ def test_utils_mnemonic(): assert not is_mnemonic(mnemonic["mnemonic"], "korean") assert is_mnemonic(mnemonic["mnemonic"], mnemonic["language"]) assert get_mnemonic_language(mnemonic["mnemonic"]) == mnemonic["language"] + + assert entropy_to_mnemonic( + entropy=_["bitcoin"]["mainnet"]["entropy"], language=_["bitcoin"]["mainnet"]["language"] + ) == _["bitcoin"]["mainnet"]["mnemonic"] diff --git a/tests/values.json b/tests/values.json index 64c0217..b75f4c7 100644 --- a/tests/values.json +++ b/tests/values.json @@ -152,6 +152,28 @@ "p2wsh": "tb1qlp932cdsy78jfka7gcv9gk50cdku5g6thxjpzstcwn9gnqd5ll9qzhuqvw", "p2wsh_in_p2sh": "2NBzP3SsouDEVe41fYJBbGGPS18tk6mqZSh" } - } + }, + "addresses": [ + "m/44'/0'/0'/0/0 16W7JeQXEzmYKX8UMFMrg6FwNSPduVdYHo 02b506c3585ebaaf5553b5e3b767c958c6eec4d0eba8a9b9fe90ff2e512a70c30c L15H89HiXh6m4CNPkMvAg1Wduscw5wuvktACPzrHowDHufwPgR7k", + "m/44'/0'/0'/0/1 19rtbaqSprjGpE3pxUovx38t64dYLxJ5H4 023cef50818c538a76eaec937f566acbabb0f2047fc2a082c11a9dad398c15eab4 Ky6pHMChq9sT8EhM42rzCab9SxHbFU4CM7rXhZT7fRakAeS47eob", + "m/44'/0'/0'/0/2 1FBUJ5QkwgBbFgHnXR2BWnyjYAh8KDXbTk 0349e0525ee723e4e9db2fb1b01e8fe5a6bc73067c024ff6d3bee7036a14758059 Kxfgz2ZAXiJ3V5Uy4hL7YKssJV3VXf9N3k72DV8XQoF6ndzxYa2U", + "m/44'/0'/0'/0/3 1JBW45zCCCHZ58z9AnEdbqCQ4TBnq5hVfk 03028b9e813e4d7bdee5a2930272673decc0736820e4a65676ac2af3fef89b49f1 L2P9gZUoobY1Z9mmUNfParxeMDg5rejvptD7fZYbam8CrgnFJ6CM", + "m/44'/0'/0'/0/4 1Dxp9jGJqpZMybuj6RoD4qozUEbYMeJgET 0398e6783af803f5125c0d0eecaffd663d5873c961b65493fa31d29ade7eee2aea L3wFzjZDvf1znTFkfcDk7YRHDpG186o4Wfy6zzRSyerGGjdYqXG3", + "m/44'/0'/0'/0/5 17FymopXFtsz3FidDTXVYymEksfRVA8Prr 031900f3d2c249dd54c3c59c506bbdda65e61f614841c0254d309d5f206160b459 KxagjpYP8VGb4M2U1Q4tnZ8vuvt3BnwnzsiGen67kwZprMUeYCBx", + "m/44'/0'/0'/0/6 1ABU3xfqxYPMx37tK3ytNFMfF4EJhNCe4F 03c016cdac08271650e39748544ccb16bbcf89a470ca03aaed9cb61cb4eec79122 Kxong4YotFdirnM4eYKSq5Kyq55eKnqUqnyvEsskvb63Lwyk7oay", + "m/44'/0'/0'/0/7 178cLTCrTBRs1KD6rvkrGrtGBFibzwcWHS 03857c2f52bf8363566ecdf0c2f1aac249a8a09ad9b442f46782e397cd7ddf3b4f L1WyWVRf3sHMeJs6FRbE2fxq2Ld2PPUwQcqDHMKs7NY76naFjQtP", + "m/44'/0'/0'/0/8 1raCHQ7gUGKS8LCxSp18Dw6pWq28Mubc2 0209683884b033ea08fb9ce44c8fc77fdad2c7935ffab765b76976f2404d84a58d L2A1nHGoLGCMBS9BJioAEuHGQH4NYLqxEnSaBKYWrXmhL4xg8h5G", + "m/44'/0'/0'/0/9 16rs2as6bHkKxJCKkeFKB4dMra2QMGJifc 03055297a3a7cbbd6ff32958f5d82f0cd4f1a29330fbd83d67038e406c27eb7412 L4nDdMuCKnxM9oqGtse989sS6fwyX2UxgafgZBnc71Z6mhiMudBx", + "m/44'/0'/0'/0/10 13TZhvvzcK5WYqT88tpmc7zcirKRMB65Fc 03b2d37804e7c04b12035c9d87cb8f8ba6fdf11744b2b37a52b023073eeee2fe4b L3NHZhyLjvum8nbKeGSYMzyjQritbsbtm8iPMAtH8sVRkqNw8VfR", + "m/44'/0'/0'/0/11 1LEkeCdpvD3FRRbCajLqdEDwTnKjxEte4Z 02f88c7d8fc5b83f2f24c3868d3c5aba8ec4b8bef75338e09efe7f19f2653c10fb KxWhP2iqkbkS34MffJU94w1hsy6ixoyhh1xKWeRm1ZqX92khrv88", + "m/44'/0'/0'/0/12 1KAcnth9Ebr5bALRmFtWCRnd1vivgyGAnt 022f2f6a8a57c355b057d38c4e263760a50e085ba6b3afa25aa8c13fcb861c882f Kx1JcdaLVPt4EFseRYf6bhP8EwpNWjEkgLMLaNQT6HrU4hnCGe7g", + "m/44'/0'/0'/0/13 1AUMAcfiiXVqrthwtAPEJNEMoLQXYkLpx3 02695fd7ac3c09a3b568ec374965331d3146740ec938d1d3166cdf420e23688914 KxdtUhqVGD6HKUsCtaFm1vNXLzDQxWDzpTqhBk9y5RY1ACGj7BtW", + "m/44'/0'/0'/0/14 1E8Ywe96WNrGDb3V1zA8LLBbDqir1TBREo 02b2589ecb684684a332ad6753710af251e5455d387b60facbc78ea60998d4704a KxEZKXvR4pW3eVmuog714cHgJwxqm87uUcjrUU8e28uPzhq88qxX", + "m/44'/0'/0'/0/15 16oBYcHjoQ721NWPwuojmLuQbTmbFbsnyX 030f4b6d7f4ab090a9a4f912f6dc65d2b221b724fdb29fc06974aa011c3c072c20 Ky2Kpb9ephAmjyP2RsUtLfydwWZmhEWBkszfPfQJJPQFdmu8LyVD", + "m/44'/0'/0'/0/16 18dfhagZDyQcpFHN9XXG8WLJemhiYpkPSi 033b25fefbb04de60b7bffe61d0bcedb48916c08fa96f76a80c4e9288b61cb6044 KyAitBv7M7jVqtZXrJxypnZipfg9BdZhEVmUPDWvWRSN4KsB2gcH", + "m/44'/0'/0'/0/17 1ACpQPMNLewMbXWvVg78kZZKMf64bu8qtc 026691563d2ee9b7a5ae095b443a1199ff53d612928412d8965e163b78c31b89d6 L5dBkDYHfrTVy2ZKT9Xy8V5m34QTg3J2dbhhXMe8wMmAawLFpPGT", + "m/44'/0'/0'/0/18 14xgtqtqxvB8QFgaTihdTAgD9Wxm5tbonU 0306b36921313c59e4495bc1c4d9f6dc9c9303a8740767c7a1844c36c506b5f9e7 KwS6KDcNUA7WQ1FTZv9G3A6uWrPzEcYpiGQW5R8j5CkcjvmFduJh", + "m/44'/0'/0'/0/19 1BUauGHKJ69j7UsqcwX4p3NmELZuPbkY2d 03ded79d490a0c59ad79549acae0cbe9c8b102185c254f27a26993a5911f7cd038 L22FD2vSP6c2cxbD3ssyJZSKBvXxzjCGnGqAihdowkb3UADZn6q1" + ] } } \ No newline at end of file From 8011cde3cace365869d67d0c572024d4ed84206e Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 08:27:24 +0300 Subject: [PATCH 31/92] Add: hdwallet.gif file for cli example. --- docs/static/gif/hdwallet.gif | Bin 0 -> 207925 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/static/gif/hdwallet.gif diff --git a/docs/static/gif/hdwallet.gif b/docs/static/gif/hdwallet.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d5b667569ecdac836dbf9acf46366d3c961241f GIT binary patch literal 207925 zcmeEtXIN8ho9#|EDTEe!htMP-CG^k}dM7kRKn+C&1q{83Z$c=c8WaT)G!y|5G!zxF zh29Yq6%iE_6%-W|5i2L^H}5y!%$d2)b)z!_}V;jk9 zhu@BnfWQ#K!2`tDXq!ZeU0Q;}U&%qy$zgjUBctLdF-h?WX-Ub4x4O`^Nk_E2inaYt z>1=1|_*d%&HR|qe!R@+$+ue#Ix8Xvs>F?#}hxh95A2EoUFr?f!h@CZze`b`lWSqQc zoVJpFnC?JJamr42X8h%nM{{FldmLksN()HkMP8M~KGh|Db!FQcPx)Usv*RKwpuIMz zqkiZ0#&mjKwy0#Ls8oTdZ;m0R$WtFV-vfj{nF*D z?N_f|yLR)^jjkIvyDpADyw*S5HusCRgLcx0?+baG^TVr25(*n`( zpPrm~GWz)WgD3M3p1pkd?B(Ny#kslpxtB|C-@g0s;p6Ae8$W;kR8z-o!QwQv4D}4m z?HxS)w(r{Rzk5eu$ez8Cd&8rXQ~ydyrDkU5GmB3&HM85=uXJ9!ar0JpfB(?P$k_P! z#Qg`;PoK^&y?(Q{_Gt@NPX}jeY3&#qcHj)FwzjsRvGGFlg_ePV;k$S5&CWhvTwGdR z{h+bc(3U|(GmP*UX%FgfWADo)H|K`o|=H{lRmJ!~}#=+6k)y;S3u05$~ zv>ZlZRdwCjy2jSF%k7ssu5?@*86BUzGxg}vQ6aX$k5I!yp_6M!J)oY<@`I#N_KXpmjilY>@qsc>qk?#q`l*grei zQZsM_W1A=E+*&(y4C~VvG}L-_q!brAT;beSH+Bji_j-1yt^W2I0v#pq(%vvx<8(}O z=WzSEyY-~f49v^ffsP@1pz)5&|w&7Wx)@7%M zB0_jfO@ZDx77*SeZjwWY>xB>$)EBB$fLzENLWzOBBdBu;GH4?oH%;5ln=!WPP~x*| zsHTKcLXyh1hprHXp#oi*Ae1(kXO@n>JDNLv75l0p{(i(h_d@Lj$Vpo$yI-C%J0Hl3 z1kgG12Y@+nOBojGf_J~Osw$flBuzMekhK5Y2_W2TAULIimQlq(+4Ul(c1XUeY`XmV z^?A0HNW2L_!NWj?&lV&gf6tB)@rXumn&0N4KSZT!gEzj0g70z<|nM6bcY)K2s8xRBllJ${#DH6tW69mcNxbanuu*en|Z%wJkg!U{j zG5FfK=faQm*;{f1`KKXx<~!9)DjgR5J-SCyeh!lX52@Hm%aIKW=De2;B7H!bu-qoZ z87mN7_JbQxnHB2WR63t88NC?62{++D)|@DStP~l*vmt8$h4zG(2eH?jAQ(FVuwIa( zLDMXAxSk+Xc_j;1Fu(3gLk6yOW$BMr2_|6;kPXkp8ktn|rW&V5S!R{t~*U%pxp?p7o7V6fk~bG49CP$T!s zX~1k|wJ3SCM&a||fYsO4BUJuc48nQPM&<*P;a00GIW*{C_Mwh za2}5>|5&l;R&VxTXgsm=W93Rgy~Qi%+o>}jPj8IYTYVndynXoV$1{LH0|DVOks-6j zLb^BDNDfbAo2^x$3mfdzT_*Da)~cn(8XQc9CyP?oYA^!loE%;594%j~#k!w!**<*d zMCaOBT;VyleJ*#)XV&WQW9K}QhVP#Kx>ipRXe8yhOjXN#YH)II^e!Eqsx$j^j#Sv_ zTkmqOG2m09|5)R;E5r9Lq<%V27C7%e;BvpU{8Ll7`}u$e!}l+Derl!^o)3EE@}P6( z(}m=*^SeF|Ke+kz(?zO46B*$;-7T}uX1F(nNRCYRo2|Dn3!B2!T^|kythbhpHSIMS zc{rZB-o_GW4tI2&xl_L0-r(N6fBVSH{m%7E?84^AeXfsYHfPo^cZ@YhCyhLM@^$?R zN8kb_$949Z%;%0l_Y1M5BeO5fK3|I zdA#lP=h0VI-@e?Y3bwNl?u$0EUndwI?bVWF;e&9V-#RPD)mbeofFD3{e*vTwS3t$^ z7v}H>L0EY}_pv+LFF2bcu zgXl;2y`<2uC2hG&^8Q}ZjJqVyTvSC@3<%NE@0;-L&Jy^`PGmd3vMpx7qp&R;g;WBr z;8S^qTKa{6bi7sMB88>Ni&NwIgYfrqj{lk1aJ{T zNNGu~-nKb|{+0XVg#mps%3Rr8T)*0!hL*Nk#*>a`mWn{3|GZw(-vEgK1VCdb-0OY9 zqiNVTF{PXZl{KrFL`drJi%3?MLp-;iDFB!))h`6Yu9H_?j1NtNUM7kdrG8&*RUMBX z1oC@T>0cQA{>EtiH%6fS9LQcQ?>PW4K9M(80u)mgo){fM&Ycp9<6MfE4K+78b9+)yhcA2Cwa4q3dd`=Sk4>BEG?x%7*SOui|La&BnJ{OPsws7ySf8C`1>kmvDgUR#Hb+0LFmOcTETw2TS1P6)G34kaY*Bc?~$(J6L0iH?QIPDj$5k7c-> z$|s#Y=5yhMZ|lE@`U@d+dT+D}J4UPY!rNnj_^4nB;bHU(Eek+J6e*+GD1nF}ss;kI zaB_ppAB4XfO8md$P+eW03sM7X#09Ch)yhQ2o=YQl6tL0vb~N;NHVPn_gzU16{R3|g zAvMx2gG*hJSHn@C<`aJZg6CiP`}G%0gUg}c4(FQi=55EJkq4Z-7J9|KB!w?Hn(fve zY~+_JP(48&tz7=b!aTY$9=@*PL$7bTx3QzUn8|_I>pQCS zuw!sUKg&@XQszPPmHE?4e%EpOzp!V(-Az~HAa8tVfO&L?bVDh5xO;c#*Tz+F zD~;h<#igsn_u`5FqSF35e0d~ZjT)(D;Sz2IiaCq)>bS@yQ&C7c0z?%FGN1)7TzGvl z8ZhWT__qGyFXJEl(Yf{ayYj3oaIUUKLEMex?q9gg;e+;B@s0(DT~5&4%8z(8{#{i( zYG@~Rc@&SpL`4$8i=v1%Zs> zIHkT^{;h)7!}V19vt(c{Qh&>kbQjlVfdgs&xM>06KRfJ^zj?Izo5xvxiEI5UGs;LK?3c31iC|5jm<4I*aFTZ@ zlxspIHmmCllTfj@maOZ9Ohv;E{v0L&Fc7HV@t{aq?l+Ja7xLyt| zBZq4Sl$KR;{earD=Nis6UAlC&r>B2-c=X=A2M=c+J%7HyrS1L7%KG}}KaR+MPZCU> zdyx7S~!>MFk53>rN;FrPf0Kq|)z8{=M^M|G4B|*ti~v zzM%!z-XPexaB;@QryNSp$j&L?Zs{Lm-BPY2!Zmen-=5@JHUEcO3e)AbAlIuH zJE+*;f(O6S*$F;TkE2ruNAbwhBiqr)gZz_v+2~8DD#hB;^8Sd7erY4EmiEVwYuwWQ@#DvTU)ulwT`mK^j5~r7 z!1_4)f%gb+dUCRK%Y^U+p};|sKL%cqX|JkOY!^JB zs)`rWfRLPXw#epgQR)lF5i0PpbC@abEY}R4Aa?lDY8B=LVq5D5)=8X$Y*K3tcP4a? zoESowxP_?l0xYNE{i?ZBi_C~ABfw670hx2gk!k$B#qiw3n0D}zWEC^L{pRm3QlzJ2bFr$G@rt5acS{ufia&6o7A0_DUi4HF+GasU$a+r#n zP_sR*fFvt%klQH4lf+W--abIy7eW;(RLVq(BT08EA$D5TnaWVZ*x+oy=_nn5&M}`T z0#YCD<>af{g%EX>uVes#96Af)B?rL7_z9EUqEII#ju`Yhp(o*?{2EeNsZ;Dpa@@Ta zNY%84BqoRsJEtZS!%=edL0B5u@NN$WI6nU0_rZj$ELd5g$PjV3gn!8jITK;p3Ia!@ zSiGuCo@yQ?0RK##<>;(WARb*@8Sv7CQo>nCdvqT^%v~J<@bl|sC3p=2^di#Yba%10 z7!C$#TF}C59ziy7LrI=nS~Hu>@ELaCHe)f4EVeT}{aCPPR%IcAGc5OuBxDSwD4 z4bM*{XSwW1W_3BMuB|*K?$yf3RGSlTu@$CE1iZVdbVo`VdQ|RHIQT|FcoHQ{@PYui zX)oYhorf1q3~;K-IMQSl1K2p&d1t12mQOkbT$PwQU>nY&*w`c{vi*dtrgzvtEigE$ zkOkL8h3}DA=n3QfzS0Bw{Q$~|P#%3oj;&Hik6>KYbejMm=QkmoPonVvNV#W^CO%uO z+7LzTAOr}GLrs9;_*)M0*s5xPb;wV|+)1iGKn2pV8MXpgC=Tf#zkQ0MT+q8S>9K~J ze{uJdqZgcnRWc2r8}l%)Igw{%-WBcVVkyd3)tTF)zq~l%>0$b^BI(^#yYjzuzAo10 zczj)I*dCqwy6MXJz1J58bgtR5Z%2Q73rrP#d)NG6E-msp;%VxiiM#^B6>9(x2kM~q zkY8fMO`KG>{E8sBV#785|J)G$Nt}LZ%F;Fe|0GS~W)9py z$-#-_;^yN)+Rl{_Z=XP}g!~GZf+HPocXq_p zjDN;Q{lDTPZkWT3kGOfxKjNc1zv83&)7<-`**^&qH$wS4MB?5uy?nXI&1(L?wERCx z=D(&ysz^u%HH(`PiIE8ZRZ7I~OdHQV{vT4JeM9D+nqTGqnGzW;oi25+ajA5<)Ohc_ zf14&h5Apnii{zd>c~|2Dr)-2q8h4Id{*@95pcZ|UFFd}M{PFeUtsga>-lBqh3I$gL zSe==oTX&6i=uJYgnP#Vdr$ku4(d5>aUhottgiUHVX+Twv!y$ZuG*+ zLdxFhb~-$#d*(_gPq(lWS%Gmq{r0Z$>tA+D;_oAC?>Y)n#j=*$bK^jc0+RN6c~gVt zFp0qf(*U%#yfeXqtGnFn%;U7!^rfxS;Gn<{Jgt5EV;~)1cgq7!!M6O+tHl|_3Fm!| zNm*j-9M!ZAGX1Y4JJKK~>3Z>8#`y8@XSw@%aU1{+^`=6B2DB3dR>IDK!X&CW{P9RM zLO@6$mMR_BG^boJP*@BHXl!c0JfLtB^sxo<(J(zJ`UVTC#vV?es7WWf7fdP7%1E)cG$Nr-g(K`z26O4}&5oiivT$({pP(8;P;k zfj8Wg=#)sy&25-Uf(8&$A$!obmLs=#jyxcv)y+rcOJov3;3u!@MFLph6`27HQrMie zD(2fVqyW`HDH0hED(LXfImjNsvK@w8B@CTl?=EvwBBdKR27mu!F~k{J4RVje$L{5i zw+ZU;KnP$*@!`e6o4F}bPD%G(iDDW_P8qe98IH1yk`at05!mvH*J(ZB2S{kg-j6$f z1l*0h@^+oT*Mpch2GZkxX%AtXqcr(V}XRM(qd`1bDY z30@8_-Hxh?O6Uc2Fi9*D5@s^B>M)cwHM)`qjd0!aX`lW>KgAYNt7yeAOlNiN_0off zPeaQfgAes5TJ|c!*SvC2>y7LIc|;8iI3$y0w%>jh1mq7HFkVq*82geth0W&xJbF&iSGnNj#b0zB-jUe}$4W$9y zA(2=DOOpdP;Zy;jQ;+=SE)}{(2M2SX8#FjQfZr3t2qch+yf6KXGMf0a{YRBU=coo{ z7UX?0OD83`DbbAzOvD~;O0@d~RJv@7(&?5#u+~A>L}y{Yk?)|CZ>Vs=d`uxXCGvV! z$23`VP*%e-3CNSoKDDqTlJid=r>YFP1n8?bfmDHaISPhrG@TcwLb8HzsyTrWFYnrg zX+P!^B7iw|%hjJa&1NC_mC=b5Ti#`6PSM$5;iiWR@)LIvfENLhvS=$im06{7MJXTg=7PuiL&<((`5M>~^eLso^wy4KFfXS2sx~X=)wAO1>f-yrV2W>YXnWCQTUoL%6i0R4Si6QwB_v%jSJITH_XCgOnQ2oFz zSCk}Ttpo@p2)3ySAIKToF9G-k(E|O@4~v9HHU+Ems6lr{_1*|(_9#fm%kw1kzkm0v3*t@e%PY_wkTD+U8`2r~dvF>@CPR&IWrpzJx&u8B0l==HjM;7384Y49 z*_A`(7#Lpm+2Tr|?=gL=>R~oH0?4_5JS7Mwubb<-FU6h->v(7u<}eY3ej!sU7)|X4 zJ#cc5foHt#-c&iX5?Xc|K;O?g@|Z%hzb2h2u+>NEdI$3d1kS@bhN02w?Veb1rbnGNX$3F|i&Y1pvio0aAA!eXIZ`4BstcjYKV`Z{AWzg{7pnHX z4qkBS-j_*Hi(qO>GG`Oh^dkLg|1Kb&KztgQdI)z#Dfpq{;5VZP&@OTv7oS%Gc;gUL zhs1=YvcP$h!%N%cuq!-KB+R`e#UGzzV1sI&OoaHegX`+lXWO@6rcz}(gbnu$xVr!nClX~JTAyrQY_SPa#!hC53$BmjA>-x zQyW6wACgZ%6cHTzS~4VI0HB68U(}m6L}$T(IX$%Yanm$5`AjI7h|YAgfjD<$LW9-n zY4jj}q&g`(j1aYjWxdn|55Z^3Cg&)$98$+504&v8%DzgU4(U*_xn$5A3WU!gbN43X zpG7-wN+4*VepCz?$A%kBAhf7hEe@25lfja_?$+j%w%+66V3gQpNO^(PPpoIIRK{-r^v;R=*Q zk~=k>+s`O47b(;Oklm9xU5f?jCg|PSIWLjIAQ_&7!R8x*<|_Mk-DjA^`_79au6pYt zh=)*J(CAB0F&tte1d|9L;Ap#Kf`M?jjNzp%Y9u4r3Y;>hxS@=IuRx=yh)%s?*twz% zW%O!0Q~be^FtUc#eI``zXxd!px>suWUPA?Ev>pbFfZ`-_i<97h!6pTfu!7+=MeC&; zgJSGzT8@IGW3P?TM!4`-9VJR#n&|i_9bdS7e~B-z$QB$L&4HfrJ|1+HfJK9qTSVf0 z(IzWUpOZ((8t8JkXqbuU&n#hI3E`L>BA#C|`3daZcI0PXR$eq*MH90MZEoNdb_q`lsea)S<=cDJr1DEnWvmyosQrt({LHISm#?Pu%S_z zwBjqBZR&jtp2v|9d9C<-$LVJ&PFIQ%Gr3hwUhx0{zVPs5Zj!ZwO7&-2wPy|s4nf%D zFkXm5^z$lB(CT7&EEu5py@Pe@Xca(!q*D;jlXZ2=t7Xd-zU#q1rpPbz0s{|An@q() zg38la=pbm34uc(J2L^v~Ua#?2(AV5u{+IC@6h$^PRMv>0l*Lz{1y@wGj%t`t5Rg9T zK{Qg{6iwySIT&cc(IgjzJryZ&%_ za4ZH3QlV5f1HJ{=+er@VL>$+&;>&7`qSWyaajPJ3Isl#`L*mgRzg_SoHPj2;Z#buc z)+Hdq(U28l17YEO!dG`idx+6QEEsd%O$5y9w3wV}gb}TUIQm^}@O9+5Z)y#SHSw^j z=5!X&cdRk~*?I0%6uGY%Ms1E`o*&Rdc9)-nvY_U0m4m5gcMypmk7>yJLmQ83Do=Z+ zm+5OCC&&;(i;ltrVMwEi6We+dkBYR&hx@qjhA!#BBTT?PVQ(c_4LxJH>;d#)o0g~u zFoC%{jZjvoob^T2Pi=FnC9f+QWd#4()f&n6GrI=A6e_1{tCD2p$v2i=l5lp!8J%l(JA;RZli;OSKg=eINDYSP) zP_8J%L#3sVI1~7f7tn3u-e(T@RXhqSJKy?1(mE0i$$_YZAt2Hl+97AJ(53up0>gSQ zUxCLU#QRkn`7T3=5M4spvFFf$7af&>Xk|0xEpcRo*LCrjj$HFH?b@tH*Q-x$Zd`Fi zi;Os0lc10-nUdz=G^(6ge>fkqGRE3fJ=qLlUS8e`Ujs z35C9?GVc<*q^}2q!rkx!I8qCJXX%!393qhnZb~uLm^c$HWqm@A_V@0F1Kl?uaYkl{ z7^pi)YwhRj(qnP>XYGI;MlRnu!jWh_imZ)R;H`TMzb%MFmd>Hi{;JpgErWW#pMu7) zWLA6qPv|Qu6ewts5b>-oPpZiQxq-OrJ@8;~7z*g(s$i@E>~&~p5IU6~NMOnOZjNGt z3*hh-xUWIYUCn{a>wUtEbMoGPVoD!0* z|2f__4{WQefD^~J?>Tj+7(qdA`{bqp4OTjwF`h^U%r&bI^TUF)#zh1qc9d^bFVE1L zgCE|1(M<%>*Xk$zw*JUKy7>JxfYfkQ86jg)fAs@An&Ps}1`-LtR5ou=nx3_+DS;V| zJ1O0FM1R~33m!Kv$+Ld5MPX}by|)>FNDG&}B;C@&lV%s!)}Z|fnutbh&I^h_X>2_TpHq-MnrmEAjA!JEQ!pCnX_H(;sVZa^VO3!ulNFh7%c&(w{ib z-+py(03{EBaa^V9CgAKRuHJ6Ty=u`^1a{)d`$~kAG2Ed+CYp5{S`3fNMS#uYcZ!Fb zl&2Pd%sF^X*3%F$vaS|cY7dUwsF$mT17v;Y75%o4Z3PizKvEDXZG{wL$z+kB#$%nN7=7XSqfCI7SyUaB`L#z1wMQ4;e|BVSO5@EDwb{V*H6 z?tm7ibl{)}j)x1LC7YRmyKx$hYYn&oL);Vwqo}>+KFdGu!xQG08*1J-$8j|Y1kv(c z-`e0xcOS=qoL%;+x#YJOrY@GMG0MoEi@RZ{waEp5Rqk`C1eqqxl>b`RhSa^<1{wl_J`$N??}4n zFBg_!2hrqBX^puLNZ}87iE^0Ur$8YU)2FN7JlBKo)xoo4Lpa%AjE%&r5ta2?9- zmEv26+G4X-Vv^YKic=er7|PaJL?{OnLx4XyP<~eROY3C>)-1K+A~gH9bqWB(vVeFt zrTP{Yw)*uh9v%iHWO`8^P(43?0Nkse6TF0sVF6lFteTkPM%mgHZOl zv&2oNJLd6vLISyB+6uj^FQ1j^(M|_hEDEbqMQGt-s&OLq9zgo=Y;6Sdr9A23)^viMIDHFaVw0@`1VSC~I%YScc9n5r=yn!=YWlVplQN4)oh$H5*Smojp( z#M)wr<)a<1;W?WHJ?fo#aOyrd)donNp#cOSk01$311o#opA*4;L#xwrq)IEb zF#eDlfOE$Tue{I#A_;3t_(cLvNFnFo95ClL6Z)JS3(%B%rG2VA6bwE*($xd{87h|_ zik!bdxQ7jo7QzE8a?wIS+FOeynXN0Fscmtgs$wOf(YE~c27;!LFLbaF0xxFT0R3B! z5QT`f#Tjoq^MwlWK|pV@S*FTG{3{udr|79d0i}CkD%8{I83m|i3(Aka$80&!=4JiU zDqh7-XMov?d&m>eVh_o99^=NCDo}BJe_`Yu1{9(XI)7Zyn(tkhI2C$M!bdFbhDFO( zn!E51aFg9@dWgrHM4$yH?@h+u(M=E6Nk@MdC+gugn?>ohaK|*)tZZ{4pd76;UYK|L|$q*1^r^ zx#lPhU~xfb?_%KDlO6#@IVx5{8Jq9MLNyM35-%eIEPqDU>AwWc`ipk%KgBP%&LA`6 zlB}eG3lRTu{*R4T7BR=SE`EC-r-6U_x`|JU=dFbT;`@iqVUMz4f#PVSa*-w6BP&}R zKE6o5&tSj4vlF^A4pISUy(r!8=>PU?lswVEdsBN__p6a;mC^)RU_Ki%Q77X+gzRD)t#={|%|XAl?F+t%W_9&e8%LD6 z5-)ol`QEE&=nMx0Tw-%0m+PPvwzoezO_rRGdb+a|A7q3tQg`MWnDR89H^XXyoSIJ6!6Lea3>?A<2z?%}efx`ex!HTzKnBBlim;8TJ6Vc^8Z$V|D*d>6T0N zW%}p%G;s{EK@Q-VoS_`TymLJIQ$xay1G^)bIpQ6QoAu4#{4pJk56-q42^viI@SU*r z@7ZN^{*dKTsETWVq=iu<;NOM%x&l4$asV(Bcuf+g(pmTBF4E*9gG1;1*eFbWBnLun zzPr8sH0w&XQFH!FnVgryerh=R2Dg>n&b0AQT zz}A$c&g~-#$6+*b*Xh$EyC5A8uJudz{+LB=@0(FSeY;RQdH2fGj!wxgfD?t;Pn~|+ zaixhj?qbUGI;aJU*dB1hR+_|tB3(-_tje8-ZkZJpYQuC=7Y`(!b@6*Y{mClm5<5+A z>w~&h;`N3EJz(Pe6WH?v{!40_`JSC*ELloGee-a1Jntbf6E;xm10Ty=rk;}2Itqg^ zP?6zeglfWzot0ix`PWS7{B*E@=f($liVxK56U^3~3m}_vW$PNvWh`UIqgT`EPv)gQ z!vL{E1^oL@JKK@tZW?L9?nxH6S07})G#bE!Jhe(BY3{rhtbXdj&2690_EqK_%t?iF zwlLZtEAI|II`#0wWLr{nVRlhf*Q4)GK5jM$TLMjMN=~H;$)`UT9IRky-i^1!w_Eow zg)8b#pm>46}l78y^2N1FPR_sB+lC{wv6kZ);x zLt1;WM6veSmGzB={KE+AV*&T0kcqZX!Pu0M9ZPla6gmIHK*jFP$2W=y+u?dXnmId{ zL)FtBjvY>HuiyFhz`nFckAEh14D5UtS3fkccsS|$tDW!DUZp+#@iU2o*tJ5h|G>M8 zExLc{WzD^J1$|tG8BdKqJXl8cL^?3mj9>t@zRANaa0vi5c z1oWoG-v2%{|(g0*$=8NmeWWZlHa8?~OOEE0N_a!nEP!4|{Y`Hk3_R zJBWoWAp*4}IQj6%1`g0O zx4_tM(<8A!Rw9J@_FMb33UW!*a_$XkK8?C`O|cwl{eG^XmkQi5ze%6A>|t5gGjG*U zIW-LHwKO_}1$+z76^ZB6pot=D1?nrMPz#25ILP5x?)S1@H@AKydh>GljbtTo5hUIN zsXwkUB(9r`zsY;{rr@PHQhbU?`BK!|U}30oooB|%yy3*w?^wo4f!0ESNCWaV+eT-q z05$^}U{HZu>8&KH8JU}ZcR=(Boi!Z#A-%?@i@Ju3bnT#mek@&oFwdflR=-kY$mA&q zN0lRaxFLY>l8YB_mLcgY zJx$0yFJIFhrc}5sk3I$zi$_&HLk$ozp@S73r+Tk1q70`+clR>q-t^Zv4J>x>$WdsS z#gH~g<|k8Ba>>B>VD7%Jw&H`l1{?J@wI>2c`v`J&2M*a;E~7lf@JT^=p%%!)%Z5f% zsPcy1&GEV3{K0|DOzKY;Iyh7?#_HZbU-)F2L0YDpQvp~9SjGLq0W;H*(wpBwN0RC4 z4K#}maGF4SL#9JX0(UxWB$(hPhpta`J{eE*tIE}lhnTP2gs0G-Vg!$AL7X!6BU+p* zrkqb_UOuy2jMoQDGzu*+H-d=DcaWKuRkjuc+FS?ZE{g_rQY^$XgeIBL6#)Q}X*ncP z*pz$fPzHfXFNvXHx@hTF!S5?WW>f~s<>oc~q2FYhR8urHqg zI_J}@rs+^B)uI8EigkN;s&IkADD6I$W0; zFlNO72@K$jp|k~yZq5SyX;6!4k9%S6R~zZR`EG7qMV@i4VgwIE6_4jZ;IgmM>@pI+ zBFj@)WVd3FTTIJr8Njn?FPcDl87XD<_WkcePD-9nlTT>1KunT>Q#?FbuJpNO`T{od z>GFtW#O==o<7qMyVt!tbMR2l!w1mtwJ#hSeNBtOuZn;r*?a<_c6kQ48E+k;Llg==9 zI*DLNEUOMh6qkN0gTQdqgAw#^Y4lsl0#kNZ^5?WHu+&ZUK@cocveGPqa*kM$0JAdM z2^uKLI}Alrmt%%)7C}ST^Js6H1)iEW=dDyxeF0B}b|@O{@iEErK~kww4ItRS7^$LL zOrJoJO!*tFzlRrXu6ZMof)a~#iv~lNjx*Pm74NXx5rd#3v#c93<|M#l0Px7Wl<5IC zC>BU@h7Yb8qT4wVl2rx4L(=S|P&kGq`!ou0cOifl)BH$eCen_FK@lu?Scezq+Ogn7 z+sSEDWOS6^dQIkx^8>%P^lb^Y&!x6c_*AGj&=;kE!Tk&qJfs=LGbBxyrSPKi8Rt(< zNtJ>63@W*2$}Sx`IO`{mx*ewtZ0`7^7xhDMZx%ol;IY8dTB&q(0>pWdu3?iYf^Ph_ z6_N`1X+hdQP&u97N@;+3T z^6!zBXdMGDH{3rUFmou&|M}bu9eMBc$#d6y11BEIPPEU+cF;j5DbOj?7>{&(?4aFz z;;2`3+M}ULazkdkl^t+Hoxwb+H)Urhok!>h=oZS2sgJzM5TRrqbZ%f_>Dq2A$YKz% zG5Z}0O#UdcOoo2gYV#H}dr8yTk>3OUfQ!g4k0*`-#c5ox9W3kXPNM(lYKa<`^_nhV&raYT|*s zKI5@Cr{NG*AfOmC{Pag37QDaR@ou>a^iqR`5DEu+9H*I6YtozPPp5Zp&D(vsk7mx< z8Kov<*g=OZK0Y%4XrY?-@EBM=4NkShP*fQ@+QbbI%+L5Y!$A6K1v? zC{TZ6bavBf=E+GMXpg0?4$@bFxwGNqP%zI)q+9UtmQlfGQc8)+W`C1JcV(4Z#5629^P4(V#GW5CrbGj6D`>+H`il@k* z&01N|XV80Nhpm#e++t|QGO&&vj`!{D$02g!3VL8{_ZEUU87t5rZWo3xX4G|1IU{oj;rzJU9B*J)TG*}dIzsiaQm}7Sb zAD6NS2eAYm)P^3i;Z<54wOGnp@Fu9g!H(0R#S3OiQt8H+bD~(hR@ zVorII9duDL(vFfZjehYuC)UGTJC+#Yz|#-Zr`CT7Y8c$CQMdQN+4zTx0~Siw|Gct9$qTb$(*rl z6lZ{?$)2k%5f$*FTUH0We@f<_+;2GHEH1?ZV)pe^Q>~mpJmdK)dFhE$gt?(dx;E2r zap}x@OXTIq(eWdhPfR(awGui;c@78&Z7oJh@lF%Xo;p`MX#BH~7s_Cc+!g$O>T zOEGAnLs$8P+C>;N^D+>|$R!-$Rji?dW%R1*_kT%eTH={9Y;bYr$g6lrDV1tzU?5KZ zpioKyNGqZY&>~q=*_zq=>5>T<;w=N1VqzDMfu^`4o08pC%`tRPgQr9Sc>VJ--k)Z= zVfgJn{;&BJF&3S0091QgfUpMS--Ergpkxj`W+(lP1VOHWir@OdGR?4=nqi&{MnYog z$fKf8N}{C@+wTrfrM5mM6;e!%7V!Ge*}p!VcXY1c8uJ< zPiLtazI^LH_ws#TlN3u@2TvLsuk7X5rEUKUl-mBu!Qy8PM{9LOma&t{8vtp--6JkviqE9TQodZ11*DmfA$bc8bDGTHEv9$MZ6@tS znz_bD{>oQhhTKzMJ^FH5F{S2WilznRbfd(8D@Lqg!w|{LmvSEOSijw1QDu!fZ~f)= zM3*?;W{G+e!iG#UfQdNB&XTl(3H!pWX(kkKb~<&y|6AT>pgxX%FTtwgeO`D^&2iUZJNnq^N_shD}3*EFtXNY4v&5*&# zZ=hLTrF{k0zunDyerxCBr=l`q9HWi)6UpE=;E<9VxOg8QBK&j7_M4T2<;OJe<@@hi z+AmIaSe36>&219y!x$z9BLQsq1Ub_X2(sw%VrK|IOzxLpow$yv%)to@Y;wjJUq0+9 zKZ`PnaXtn{vNVwPb3g&&OmDkaMx7g=9yB_*^ih3+Ws?Ix+tn{Pnc;Md>#Fv%1PF*n zJTM+&gS?^OUotz*;*WHpXcOoHQFQ?xSOOYCCZ6lAAR-=jA5WuDRZaCJk83P+M~sWn zhS^=oJ!7ZLrS5IE_JxN=DuKzLUIv?ob07vqJmg+_od61;Y!P6|O4djl(!I&E^exQ1 zsyf6Z$ES`TI1l?c$}V<<02W~(iac)&-0j5}qh%Tt94yXJ{DV~HvArA?vTrAV^tI6` zw9vWV;T0(tz6U7vx!4~r5p^0L`%>(X7f3y5oF_EHuEC%wB;sBmO=G#MG6|?v;sx}U zgW>+vrr>P;kYX4hlP;Dm9H>W1LdVf9Xu^VfXM-INeT2-(L?%HpGGq^sx!)f$B(Es} z`hd;6XWJ()v>>IOh>%eU!QPdRy&haa>Al3mQNwpoLy2AGj1%V3Oc zNU|kcHDf2sSVHkK)@(6EsMHy|>_U`k$W~E9lBAlkWXV3yj%N(NLO z==^Hw43x*DIE38_vyc;=Q34c>PtkZ7bUIq7Ub2;klxx^@dEzm9S5inW2D5JMe4>-u z*jxo&OOe~XAtnT%`7WsVbO7Hco2@-9RjWIuiv)Cg5BHV58;rjtgK!aHRmT_BdXXr$ z2ahR$9OhwKx)5KhH61=RtDwi>u9AS;hXDnBO5-ls7ja7gxM7P%@f)={`5=Bh5T=SDvNw!B3Vuh%? z632y^(7U@l=|CHu0FI+$7av4BV1A4*U3zTN^BS1W7qfkUh0%pNPxa^2%hMClK!f3! z+{H<|-B7 z{vhVmsta)QmK5FA0cLwkdd&Q(PQNTy^5pVDXk_H7g4XJM1~#1t07nz(5zUqO8L^@^ zb%~(UwCpL}ckgz>Nc+BbEp29CfX@6sl_&M(mwF^`o>bfVaN#f&(w(q36^g>RLIWZ? zzg}LdKYl_mhDlm<;YzGCfa~<5m@qC)k)k6s!rzt{UUg&0^rBQC1$PV&%JK26{rv0j z-Hr_ur0IWWZ%;EF}}cMIbisAg6ic(NA{W4mzKB$9%S`6(_livY1w zV!`R4MbO2cJ!WQAxIafO0mbb-I>>6A7==sP3bZ7xh#f{Ocas$8{W2IRG_JoJ;%nZd z+d=~noRMvhZq|OP(q5^dYs&i#KwS{k{1%7+2B%qgOQ3WN~;p zb~CN3lSM9JT>AnrVRk)1<%$>uB9=Xjl9s4=Z89Yb6B8;s)^gU+fe38dD*-yRC@C~J z&;yWitBzGkha6P`KJ=UQuyL7MLS*#$E=gDLNF)bhKG_EKt0Q4!v)mysNc7}i_^fSN zlr%S9zTN*>e6twJUIa2@i&`QSHGAYqePiy(?-qS>sWSNHl)||U?01Kda`G=^_+@&s z$3smQ+r#+njX2J5-Sb+k9$-mX6gE{=jqhi2^*yv3O9ZcPI1{WYsH^^X6o*E1I;y5m z=`wb8pg%ibDsU{nXHm7Np2qxD9 zHdX7Q$E1;Q)LmWMwy^~1x$wP~O+r5l+6q|akvcbDnf3E=2^%|tciogQ>Zw9zCt8qI zOtbj^>W)nK-+~T}p-jhFn7G6)6$tmpWSN?1Ug=Xv04N0Mzv~hNSpI0WN&UNs1kxl& z&z(>{NbR}(5RKr*D+&QHC>!p|bN0OqYjsMo(M5nqP6>O1dx2`5NRowO__X&K4X(+o zutdP2DTxV0tBx3+Kn}fxn!dG+Q||gG_z-ol61_8sD{AzaoKljp>ya(QT|d%cj4pl0 zS6U3ctav)l4VBV3N%|L$vqX{pTfs3zofGW^Mxi$zX;QVrrYs8IKJ{A|P;JuOcQf`A z2W4F7>Wyj+OI>GX8jbS~_q}#}91b>-`((Vs4f=1j3>{j*cx0i8X(x8)!y;PvPF>B-Hjm3#@GYq_oC8yA^a#RoH*46G3yxlSH)u5wLk;+tY1@!RUG`;%teSu z5PR2!2fSBWh3BBxyS7_7TOZ^$=@C_Kklw;Uh!_7-(u-zimNYo_DFlD>UJuT4 zFCf#;7LwsYaJN)$KY)-T?^D@WCJ5ABe_3V5^;z;cF*3L-JyWg(geLK~80z~!|H>0b z#86;O$K-_svg7Io70A1v&+V23zizx<&-8u$AWL}6Q=gmINb4ZHBC zv95p#hJlPogw_(DD;jQ}p)rY_lf5uwl@&`|0)K?I$OaoF+1Nz7Z501aZpoN4n})>z z@_4aJucnBdEZ=e~c?uV3rzy-(Q)eLZfQ=%-T#*fKq@QDq4Wq27+Y%$Olj}y$gADPA zwpLX>dB1sYgr}~GU;x=aBfvOPo<$>@+G{b#Qu2zRAPGh(Ru)+CXhM|fta&$DL{M^AlAhad(L|BkY)=& z6G&CG@_>B0U4tc+SU$BQ+~z)=jd)~DYH;6z025pu1R#5ra8*A0`$xKx+X943=g)79 znygIh`Z7AZ* z<-;@rdYPdNNH01GR{=vbg;q!iJuo8p&6*ea%y*h%D@67YB$?5JsYHEGud)5tGnuzY ztAUiVo$Y-k^$-VL8-rX+RIK}J>@rjwGZrD85MofiGgpPq4sz7%#b&@=fMEJu5N&bM z3HgpjQFTTlT%o<_9!Mji*WIJG!l?F86${X-^}Hxd$mL0woTJgHpS{mVB>XX z&wxWWKZ&|^s=Ad2J59aw6qohQ5BfDsE&mDs4~g(zs%6yeaKVJoKfn8L2=rSD_WKu1 zr_|Bix!A>7I=wEqdNfd6?P!^u@EY80T);g9AP|^b=P4j)3;z6at@CNM!^L*OajvP2 z5SM?J!i~HiGHK9Uh>X{WNDaj0NeIo1D%%<21db<Ts^H_#hnddHq^_N z*vFR9lBN&R%HdxP;0fh4I0hh;Ko;ifs%&NHMrqrt+9WBX3?$-fx~3RXgkk%E%rQ*i51%>?&1KTPNxi(lja{hlqe|IwY zoc%Qcgp`|)6oAhw*#Pnl{VCc(MBWlmZ3j;`Lf{4~ZW#0Mt)7c<1R!pwwUN23NZVJOqr0E^?$$ZSZ=s+!{v zkea?=Bb)+8!P5WJW1E@rtAiZTmQ?8xUoTe`3^awpEbG(aOC1N72Yiddt zc@3@zS;`8H6LGI??r~Tc-024d$9zwQ&7~tR+TE>QwV&%InaK8Aa5c! zG%Fuc;xA7HWGP*wRd-+(G8kglKLtXV_A*1*hAi}Tf~5>0Ow+yzOaFA!r|AUfh-T#Z zjvYFgjJ z_xTxz&vUf5C-P6utd9QmyjpqnN0SpI?(_1Qto&!#5!;K87g-&_tGKo@`5BfuefLW~ zP2D&@ld2;u!ad98IR&pt^xp<*XeaN=-0!jcU|B9~dgK+ySz%Z}edW$fj8kc)bZsp1 zKE!5EdgWaH8vURgoxELjTe2s@ow|R1S!UdZXo4ue(>Q2s#AW z?mEtf;>i2B_WLGOUr=YHdmLr5M+rS=|G1D|@gSi&U-&v`&KxQ0{gSKA%TMKiofVPq zw(GVKmJaS{0x|873qPN#!^{sPjcQBwBqzI&_&njE60A{*sK;)g5kllU%fZbJjH~ss_~e)~a7>zXSZD0NYQgw8$u-63n5_=f{ou!KpWX|=D@B>}1d9=i z=jt2SNzi1*x4+j5BEd}S6HpC$^LUxb&vg1Mt@oUx-;7J_k9+awi2|fFGjxycsD#!X zUu!o|F99wy<{*rK%8$OVzNv3|wV$?sUG~RaS_#eX)ef)F($l3m=Rb`rgnBDZ_X7^* zPZA@`R06Jzm9EWD!3trHu4Gau(A%x#A(7k#HGG2b1lt&w9AvS5YlPq+( z%$XTlviz&|2G;Q8vBASqbH4~4i zFFRgKJ0`GAdpp_a>*S9=QNCBEV0bS=#qQFtyOle7kj*?dQkRY!Vj|3G*iqpIyEL_e z^=_>i-QGYe@loud_LNY62wirlMAOHAU4EaGu2*`(S0q>GT5C*&b+(|eQqO#qJJqGSo+ ze!i}Una?!=qmsC8^LwH*s9tPTaOmVN*Q%+Wq`V>|W@UI2!evnsqlzpyETYrdZ)O*?4G^}DCr;$QdP`*`Oy z6^-$$g$=y^{mY#5dsW`l+VV~18;!#xlGp$ZzA*W(pGRtg=ZZwg#kmSk@tg*L_=l?U zgEUCHfG2PXa59jk6V5<4Aqy~+e1EOV(JwtZ;Zm~xbkNmloVQd8eBPk zRPeJLGIiTMEt5#yr?7?rxB=8z-p7K%D#By$t6bvD8^Gt491*mzuliWfPb9r12h@mo z1=4zGkRIA?HfS+O#po<7F@lKF+~2dPtzp(-J^u#xcbQbd-{yIlJl;08r4p)({QlwF z0v3dx0D!_GB)8lY8oa8}iUMU!kFhE3fkzRbDN+gA|3asbXQa2dnDiVkiUs1}0C>$D zsbGQz3VARTRX+9D0wJq=2@aSU5|8vWxT}FO>JU;_U|q;LDpq8(+5fFzM7|<&tv_4m zPMvMB>Y_obF!)5m2wYBUj8Pwf0b)y(8hr**&PY222LQp_jE}@sIsmHC*#?nvvgPM9 za~-Ad1S}(K!6|o$iPvm(W954R7*B2$hO4)fAvP`bQ?C8JGZ5=FrlCt3zOq*vv~GYM zJ^sEZ`T0>To~X8k*X71{9z?!L}716nBnTk@oEJ0Mr`C9*fe5 zF5C%evRLmw2Zy3wh-W|Su~cwmXgBU?r&u3R?i^jv_iv2T+($}RS+%A-kq$&cj#61x zdnfwz>`*zOH#bZ1is9Hhjk| z107Akm!Vvf5z5u!!oo?M5mQ*De>z3jk~aXkLb?V3?zS-7dumMtZJn3~2Hj?RjLb9L zEe{-kG1)Av90mqD$Z}hiwoMhKHx770III-CsRkM-F=eZaKnIEQYybG31W1g;MmcWD!h$ckZGF1fDn9(V;(kgK@Y*EhsP}?Z1a*=4q4h5qk`qNw;!VwR?S=4~0?Dl=wc8Hxa zwi`#O*=@Mu^)~zQtO-egwSiNW#1=*Hm21F9u4vJ0tc?=R0GW6i-@ZPT%Awk`mGk#F zWH93fXVGc-LX?WB?=QEyyRm_xwu&{Ac)sv$CDo$tpU|HDK)a|YuY&i%wH~l(lq~g3fv2lWD+4C|=6Va2*-A-Qg?< zf%^;Rk@^F<{_!n=uTvFP^M;pK4@!CLXxC8W@F54byUW( z1!=|x#E#n%y*l368qQqD9$P-Bw4e$eo@h}@Z&x76o_y+~K)sSkp~_RFGQS)`02#mL zU^rBX#*#lK22UmBhe>mFTGW#oy0K3Vk1%lhc(2nCJ5PLO;>hvKAL6#FKSVdxh|fbL zwo@#{FB%o)s@KwZ5K5}+J+m<$M)&dum=b_WXwlc7_qHHLfthC19^6eW51D11=fzM` zb@GQ`_$C)BImfNkIFx62H#?>7hy=MHZb+VE?{aVPWm!9%>ixz$!4XVTXyXCnF zPzslqsZN87jFBwu3@slj>G~kt$F7PKR8&%JA%q112WlqGjY3ePE!+$D-aC!@FKXAe z8o&gMct5Ki0w!P0V`hhNo4tq?qYHV!*S+G`(lUMq^ThHRl4Qg;XP!9OVz(q{ii)m(wrUa$%;BrJ52{O-Bw75y}9d_+(r0v|;a!e>-*wS~eA?#IR_K|gZ#kjeR|$o`$ZKgt18ojj@d$+-<0P-Wupl%!R zSM&ZZ|FS6Mixy&zWl`TwMju=}z=4K_9(>E&`^t32Xcb$L3VfaZozA&ZQ8sdAlYuJ_ z`?bl<`b^r6iepB4Kd1!%o=G{q!1#WoK)(hm8if&?0mRk6-sbW?ug5#>cFivk z9HD#_f+-8?mT?{1u^^!jf1=co`Q{|VCn{1Bk!O3DM25DQ>ClaF+W5LE!(WmB)(HsgjE z*yli%LMUR9-yOn7gva17q@-(Wi*2dnpOa7fq~H#+;czz66~IR_kV7C&u-z`L187%1 zf=LyV1Q=Z?#Oe1e*=N{(2F`~e2C$*Xd6mUzL|XuEj-Ndxw&4Zf#NXlw5DTb3l2I?w zU621w+VY9nsU;?H{+WsoTi%(xBf%v%=&RE37!vTuc0R*jX!dFdQ26K~s?7Ks`B`OC z%mS~3n4#jBpp@efCzSsLS_JjrKj7X}s_l|(b`hWDVvznw!my%$AK=ACIxT!x+o`m@ zQwr!;`Pm51s+PUoJUOKMh(Hn>BHdq&u_33pda#kUV=}sFDxxIffoBNudDdDCR!y8O zq&L-p2Tb&Qr?{A)+oE_ECFdrra=OZ7EN1-q+jX!Ip?rB%jivSXC4yb~2 zabF9u(}4KRwbXy9gRPu`G3uceKCnf_t?(g{Z?UprI4@BPA-k+aHi`d4J>|(R3N>q) zga`qJg)MgeyT*eD)gXaGSGY}Vs;<_1;UK;qsIN{v4oomaTe|g&+>GzQ5U#04Lftrw z1&(N8AUA*u)j$R@48~Dn=3+RXE<{?t5g#~fA__E)?u}ePxKhj{sklXUQAR8d$IuBe zM`EhA1E?AaLG-ce3;-|?>LNu3nreuK{1Ry%+fZ)J93= zDZ6E%TDdngWVhdMhiq}vU1Mkn#?IqRcAI?RB39UHQC%wK=KDYBvz$Iz-_?gM@DW6U z(P1HjmST&CP$23XWP!9BtR~AY2)pM%r_USUl|;v=K<5j!6B37px=BLHqK4L*;@6B7 zLBt|gxvs}@Ul-0K`WS~mvDd$q)ruWl2nZqR=6{3CY#_h@rA_lRuo`&Rejn6nj%m~%jSkTjr+0bkOPgTQxt6$F7V{DitmW@9$3kV9Nu92?W7cAzad zB9y!TVr|c#v=BLRNXWHd2>b~gS5;_?5*CNVh z;!WAR5Wy!{^viDuYTbc4kImCLI{hADXN1CMtKoye5lRb@y%W14xg{-a7ULdjIzPfg zYlCddsNW_+-xway^3<)gis4Nhz)v2CoKRv&UHoGh8+ZY`ni{?D!eW^eFzpd4*Bg;p zmO91=V77(7EKN|gN_?>CJ?C+B77>@D7Js zK`zz_wUdaDABm=$fi!q@E)EM1Er8b~yV)KQUQCV#a1^>miG=iTes{_O7Fy-Y3&kR7 zU|c_WlA2VG@;4L`{do_ZSx6Qh(E5=e3N~Z;pwc>sh5tODHigqPI)_5v4UI;6g zBIT@>V28wU%OEyaQtjuDvp;_z^vab&CJ4klb+T`o7r)St02IxEw)_U-pWfoYw+YdaNiWHgx{kUi4Tfm|q>=Egr zK7Rf$D@o~$BU)@YM16=mFMhlTkT47 z_ln<_%UAr=b(cn%RABeNDjoL9kzc*wFO#?H*YHAurR_jP<6G<+V6k5-;|lkRK!J_p ztBabw)gSG`VbF*~&7#O_CtO=`+w=P-Nk2=ZE|Od-n}Q*fl^{RrJX(QN6GY)cmo>C& zpJ!GK-RK}N%$_%37TA^74Dkzymg2++QUoGR1Ic83=7(Gz;8xu%=kb_sS{<%UwqQNN8`(q81nfx*V*!ys7}Fg&@FAX4KutpsH; zAtt!c>7wv_sG4k4qHL~vXqiSD0^o_+YF_XU#0|YFdje|kWmGXoYOfus7&+R~`n2x3 zO|37q{;^3Boy=^F3fypLPBW>#v~}Hc3)k3F?^00v-@#^%N&1(1B>_R?mL0mvs_>e! zY)zkR&7F>`%`2=D>(Lh3E0Q1UjAa}0I&M^?wAh*GJp&wUO={H#ucga2=*u}I%HEFf zR0-;OkY&Ma2wT)-ls$(**9OH(2K@xNvE2ALmOTu z#fn<;5*MDJ=LfHFnf4?VwAfQiE&MtgV_Xp z-lT$A+LV=vU1hJ;p0lAN!}o;5gNqy&W-eK8L0^@8T7oJmq5bQPydH{)LJzBsZdGF@ zxFd5mqmNplP5|oB(@|(ARJ*c!+Pg;1ynFIEo>KEhI~6MsKV4Vzsat(_r{L7OH1>g< zkJ1(cztL4Tmn&}GhpaYLF*yDB9=Ydura7K>FCW@_{LjHdY%FGko3zq*v%24SX0-5h z-)z0}<`KY$B#w@b>jHP-;WT~XFDIJ<9r5re-3(H9!_^Kem}D(sI%uAf8dgtC_`Sk zf%vTZ=<`F=p@V;C0u#Cbe>ou-q6_jrQycH?}UbyP2iGIg8rPcCtrTD&9^(44P6KBdiW#gs* z^t47^0Mk6FEBQG?cakk{vxeX<%*p5A*1B;Hn)pbc@05R#x-Y8={Qz!1&hQai_+Pau zZr=Z`;S08C`P%$9`K>XNk8QVh#ipp!uL!F@!f9Sv98`ZUyRbQOdGRfYKm7K+iGoO=Jn2;NpBd+3^w-tpKO{)6+9V-}iWlUBd$ejrIPi zspfi~E;U^|s3^dyEB)Z5!O*1VNVI7fz-GY!(0p%{J;vl~3Z=(*FMM#%Id_d}>Bu?i z_x6e597zM@_1>)1TeA<76Xr0hQraLYSrK3qsqcEXSM=YJAl?KpKSZ)hN#^{bVa>Fl z{|$GPR#onm#1S30PFlUJ3V(6Y>UH54xn9`uk^7Z0pjS(($WFye8IH2&c)!op6&Zj3 zyb@hl?|6T`ZLa#t(aB98S(RC?i2r~7-SQOaB|ndP z-FJkTpYwP=7|vSCh#O}mbPG<;%uaeADX)F^Li)!p&xwrDqL(BVg3m){+=6h9M8GeX z2l$3H{d}F9^=s+1Y{z_mV8K2qdY@ceL*Ie~P{Oj28+=F0gvJs`Hgf74x|#BLfSL(} z4Kr-yce_*j@*%vkd1SEmLI&Ah?e>xU@i2v03cw|*MrPZ{dy=mQ zHJwGGsZIBm&eaNt9{)^S@8V|)Q3`qhNu%~P+CH-#&#`}rSyHG_0m3kPwudP_MH7$y z7DwE^o3NC9y{>xYX|ybiThxb9j{nuwoPv99qiA^AG^9BOd$X1uW_QV>)8o@o7TgKqrbIaq9lst%uc>d!_<*R30DREBfD_5i1x;Z!8SqZE$0Bde^r* z;g@>fOCQhq!|VZn7$=UuXfX|IFvAs_UoLibx?eDGC_icGV)RJT`|TM2$>r(-r;{J* zuI8Uyxp8~sq~PPN$7ec-oP!FJt8H)cPkrwAGIHw6BLVL8TGs_%sEF?BEhu{R#x{1L zS5cHJ5={MQE)$n;`2$ z3RfY$5JVE~E*mY=&>ZK0Ww$qVr|&#&Wv`{`sC54g2e=Yb92@*FWDuNtq6abS$(@J541tXSDPy-=}NStSz zhCNeOR3A{*)#T!`8x|FQFJ~Bmx}wK~($-Jb*g0+X=O?PvYCr(H-yjS1^zn?H z^Zmsir{Agj;q7%kS2?v;dMbxe_Zne}ZMr+^&5A8OHA&zHyjZ!uU>qnW_oyF&scVD$YS-rFWIA~L$tR6C{D8L+hx$qUo@T$ z$dgktAyYJ5d=>!or=SSic+L}9RLBw_0nFUMZHEAfn^Z}?njB3g2>ta$z%e3zmDm-< zEcdr`%~YR(RM@ki=Lvj+=4^s1SqAiXf}-@y<`07amxQEe=qHzG>OWVIn_&{EaNS+b z`|{DNVCD`!Lo{iOL?7V1yj2`lqkr)kECmffO1OLMUuD_QOeX4sL#rh}UH6|pS9YVW z7DyoZ1uzilMju4j;l?{LY|xGALoBx$u~bW7p1hM;d#GJE{>+KzhOGMEx{wOjRvBI{6$|tdg4aDoDm@f z@d*{-g5GAAZjnk!r4FE~csI5p=YvCpKsI4JZWoP_NHyEw0a>mz7f+ob>PAVsyC#)b zGe|aDRe~l7^(dgLfNl=!`lUPF62*ETWSkZ$xxHV;v;6eOSy*~Qs!aZkkGAh1M_3fz z1?9yBS`UeO_tD)=2r$oQWPMP2D$M!nArLocy>#-6A}VQwyR;2 zmtF70{P$%J1HSAlBDDUTRgEiQChixE%tMTtVp%h107LT@nST?@ z8~T{6OTn8he-}^P^V8~6XYQKZ$f2Kacvck4u- z(~()&Z&Z)~i!MAB<#k(OdMX4_H9CCaiuSO)WAeX^+m8!|sDr9|!f-p{yGw4wsm61( z45+%2bX0)3S|SM*S}jnUR&k5dQ8RP(QzxL3sBR!zHz5|aqXc={9I3%T&8&llAhLx_ z38e2*O6b^YOl%J}~SEH0^ zMe=v*7^RUcKwsCC{+68%?K%y5s4;WYu)a7UUiD|aij7dpOl+E+k=oZ-6rQ0mJ)Yu` z0{hUSz3Zn+pwRBv!VH?%ZeJmQ(XaEjYp>azjQ>JaWfN%*cQhiq^kH;XKOtQ*D20>G zGGt@`V_B9)JLBBc3j@zy>d(GBrF`LSb`|B^wQ}PYi*xl^=LX)HU++KHw0!QiP)@V0 zu_Q>?_grvrKN;q@X?$;4?n9~Te)F8MZf9O_&V^6**=O31ZUp)^daK`+}J?Z}A;sI>?VHxq}S{f;h_E%gU1t z#L;D=)fU=be%>3J6NhYBi*~Q(+$5Y12>a5^6 zYd^xivpwhc>BSx&Lq=~vqe)SVHI90Cp<^VkldbuHPRS#GM;CYXV`Ip4ur%x|GM+C$ z;u#W5z&eqStaFDQ<0CJZAfwi-7vhMWB_#(d9BBi{Lx&x=u~5#hkiU&${bb6hRTmrv zw4F7)iUX0wY?l}vR0^~lPlbSdq;-mA0^mG6u5A6wdM~#;UfG&Cpj>XCXOm?AsH)WG zgK_gl`Ed)Lr z!O|<*i;icY;>J)1pVI-RPZ$JYA`En7OIg$cR(o8qh|A9_f*zK7L|7fw1B6}$UhaAK z-!tRO^D>aS^drzx)Zt~9sCp?QNj2-|C}+@NDZ~4im7Nh=12*P)r&nMrx`qTcylYhH zmR6U)B^^nD3Wef#yfwZo$adJMIO2H!%Cz#;5So{m4(gV_CkM)YGmZ?_*`<6@>5?R* zJ4{9AJ6yvA8C2`JF5~eu?Aot^2p0Ehk8zcXEM57ACs9^$*?60R%+9E)q28$O54xnwV7+XkmE(hsapL*|hxZGwi&t=BJaH6(a6WKK7v zU8tM*1vM~7TEyPG*^D|?D0AE1ZfA;6C<;|^Q7Yx4RQC-jw-ndZ6U4!jNz5mq1nPBF z#`W@Zuz(wl$LktxJsR^fQeXVK4(q$VXRYZ@@vXA5+VA1F&7jAfkU(G|(wUA5EeT)Vw|?O{UWImkV0R_bDjafKcB%^g>~ z6Zi3;6{g1v@5MI%COtMie)Dz5ZPN#-AE79QZ4Lvb!4$*2doXqsh0}W=W*`J+o-i+d z1as-Azxha;Pe(vBh9Xcb0CHz%;NGIgs6xSm$a@cd~b^hVk9qoz^=O4<@TLNKp=Aj^AAeKo9CBdG`7#NYejuoR2-+`}W${wojzS#PG z=G(|q6p+^K>)MS$WXoYj=}4xHkCbQFS-v9dxP|QLpo70bihK3;(J;(zOlzaOP5C6 zp&MBr@-^%9YB*E1pHnP!#}##)i4x=XS~;W?7^Nv!Ii{Gwj$)|k_-xOi!19(}B_Zn^ zVs}pNK*@4?RHXG0EelC$_0t%^;B-&lKBzA#_v!t{rw@E?7xg@STv@wY2iZM@=SB|o zPBUgjpNz?0RF zsv1Gfx-Izt;!VSJj<(c)BYV$LLpa*MeAHKFo;?g2J@R7gt*tr*C^jR*+&;)@I;z8u zjeYw=qwF$P8@9lFlB3LyP6r|n{Dm%<sM_R0Q&MwN4VH?)$RPZiXDjykW zKDnnT?@QJzF`-HKz=}s-oB!;rzNt_&rk|}(mi(nhQpZaVPo2=4KB4(K2@6E> zN*Q^m=rwzB%4^OiIkB;6&)M;TWak-UaTLW+eXS*e4!}!;EQ+CZA_LAO;1o&t6d!u4 z3}y3Pj(-Chrvu#d-yUYn;sD5yJZ&LQIfhYdKFv!o>^EiMmnXj`^PW&d-1hz?UQy=-l^x)dlz|(inRC&*S^MX4%xNu-`jUG zZWVaI6%-Tmklsh#{VTpU1#1a3P= z2vp&?wkFcE`h466fO*qE=R;i@Bmc<<2dgF~zVR4TXi*ige-acv(2xkimfnR{e-_9# zU6exMg*s_X3XjN*QNH&z#joyf>KVN5Zr`-mT8;Y%Wx#<3WjEBB9KCvF^#owe@r^kp zDGnn=L>0>Lr44QC8Yiz1TcbW&6gQljdp~$2aOsLt_KjN?>{sd(ZY1d(UGZH7*q`Y9 zPj)AO4zH!(x2&8&sT*}rb~||+*@akx2Z?dQp4~HYg%;~P#+a%Ue|Na#CK1UMJH>qc zYc>ka4L_a#^|DHgjBfOIHCcPlXxG=RA%#Nvj5dB?_0M{ab-J?Z)%Bf%dTm1>j_dAv zs+s0dQ~_yo=Y3OpeEEe(la$|vo2htqzW2O7Dg?L(dGEHzdB1T~{h^3Y^8ErNE#OYj z)3`XQl(l4}$(OsXG+KMwPKpj!swCVklJZj}U z7H~^!O%MK_-0V>SVNrDOPIZ9RYpLD*_vPzXuONkXf>8PkkC{e<5y6oqaJkS|2Hsz?9Mz3I5%n9VzI z^q$|pRL<{*(SP@3z)nzo6Z?>cW1X1ORZocEJ>W^`iCP5( z(rE<1EZ3;HnwAFmHZWbb@jf&pzz5vPg`foG^N^8P16(8@i4gDf;^X9qC!8>P(s5+D zDyuvNtBdX*TAUhtCJw$9CAL$0_x_CuftfzAIEOVZko$7|e#hu69W>$z5j02(x6yxjFs6uiGOIJBGF*saZ5*~}?ypRjM}X1@kVfueii zg$0j0oTHwtiH=$J7}K) zEk6lK7cYsGaFSvh0iYzi+$}>L{U17A0>_!p*r$}ZJ;|~u{h+#!&JB$7d6I}C2ze}-IMF5`vL3CcG>xQ1=y&$6+AesBp zi|})Y~NXz(@}N^Xh}9~5WiCXg4;UcxR(-}{T~5vehQSjoxsYL zJY%1k{YKZizQ?Y?^MzhkiY{mMo3r7LkoNhEFh=S&(K1oFLC-{wl3xq3Kn>F|j)X?m ztgBr<{(WL#p~RDO%00DX(r=N*%l#j5d$+Q9>HBANhVD`6l$E@_=&ysI`I3=s=)YX+ zBb0NgEi&)x(^C$!O#i3uy~3L4+VJ0*G(s8y0)!qy3q=G(2u&vtn(}H8L`2kxfGCIo z5wK4PB@{6f6%{ZvrHOzFh`gH6L_`He#2%`ssECMI+4;V`ukU}jPxi^qoaAU-_nNil znOSQ+&+k^TVZ5hqgLmeTKPQd9Ew8!toN;zH28eDvi{YMVSl_^k^ZsFE1z$jE`6tJTU8^+0So+)JwKooVLL)q#D9mp$m|6XKajU88AkSr9xWMoa9wlt`Y@y57y(syI$=GVmXGg-JUF1-%3o~2a;dhYSg zM@Fw36tsR%po$J%vd&P16Wt=nY!;g^0;KOby`hw_m8CO7E_3r|Gv?Y{e+2}#Pg)fg|bq#93#jAaB zYL99*b?*r%0n!B&;9!`rq6e`fZ_RsEGnk4xI!DGf=>RzV_M? zwn^=-=({O)qCHz{v1hfrz{2X8;dXRQlqZ!fBA@( zWz8WHJCz^(Rb$jmz6ZA*R=z${+N=P5BAl+heaCb7#?VHop6_8YGHlb0fU9_XpH|O@ zbDOR-UY$8it$Jqt;CelJWbZzrvsJqmyNyqUjyYG|sZ$@3D0ud%Zb%lbf?!f^h;`-y znGX`g0Dr_^FPZ~(qtRpvfWnW+(r|VuRri)3*@C= zA&?HBv=fp-xMZPfozx-QqswU%SAmh;+J&HH_(V&tvLFa!TJ^f%Gbfz5pK5Hb4CJ2p zJ$Y(=@s3ubu<)HMnUXSLVdY59&<>rV`*qQms6H%F?4!IhkvIBQFhE_QTiPd7C|di( zU0*;vrSmyy&|};F^B&1q4f4QUqRqVLW*E_-8+MlyX%}bq4`gehIcQs&ff$*^pliM# zs13Yc^v_m_rs49wv!%AE0J+zcA?o2FtKKZ4B*I}_^FGoFr)8APjd0LTT;E_7(Zf?< zX79ixjC95U+%(sAu4TAQf$l7CnP5n6WFhWMIg+Ft=l=23$T@+&1`!L%@#0C6&Yg61 z1zSr;Qrg*tM0rMNO97~qzyO7FFa{Wa9MGJ^cOeQk|IV{W56c$w?i~FXeeZ+JShg64 zIL3sG5M-n^vm+z_v`R5!%<%S-@U8;++3l`0nUSq>rq;TGmy$28nOFILaCvDzl5|G@ z+WYoByngQ55TBGJgt`RB*bDIwN8=g5qc>mU2f2qG{q+wG8u|lZL~lzHxsWGY58>EBHyXP+T1PMDq)`C$gFc4@qxjX9DfIiYHC zGSo&G2^hkfWPLMAQU`vCZhh=~FHCsV_HWQ@rK7L)#h6DcGJm~r&4uV5n94k%HUnH( zfx|(R*)6wYrAnHSCp24Ki=ACyrMEmEhth{Ig9EtKdCq-dkg}tB@()Ak-i&XTq*9(vg!P!HP}AmpAi~K9KT?et|)>RD1SZ zuPV;o4(A|Bv39I=!J(XBU`-*oj36$9Sg=ZfHCm{~s1x7$3 ztX3^B7ipPmc21=n*;}qA5tNI~l_{AyOAt!U8tKpgdQBtG z?C#lHj#L9O6Sb<`gy8Nr(6mB1x(ivBg!I5RQ>TS>6e6<$tYsmcg4`%ReXD}_+nbEf z3IS78k)5o@B`9SQks{NER}~gHd-KoE2viFDN}BqFih2Us+SF< z5?gqP;9GVmnOxoPIzaKn(I6g@Q>OFbT(#QtJ%v`#4iZ)cpUX*t*#m{=Ugoca1^dqN z9T|X@w4d1u>MUyR-*flW{QWo4_AXTyT&4@pcAaU;%nbPLBfyH)X3Gtosw(OXrh-f# z)gT>2NKae5_j|Vqme7`lGb}pAq6yeZDqjL%*CoiSo@>Eo6jbyJGz$41OyEXm_WMX= zKVZ{DH(6Ir!%FO(Bn>S%@h4w{*&{t-+JKnp`#!+;@VwB~y8`q^X=L9)h9foQhF?%h zY&FMY%$(qE@l)dMAk0F4RJ5lOvJI?FUDlP#>cH9Z2HIUYN9T){g=}b0_kN8HHpKzb zyAlfi+}`D_|~WcvygbWrfQGu$S$f%r6gA8<1sH zFfew(ftNDO@E1q~4nWGXJpvpLyo%DYZ9rx+;q{qLfJDG%2%Uf)g-9h$<_(Gvc5S!` ziTOAD05+tD^r*8r$}d<-%J%dqp)%yWGyxL0M(Pgu=en2tZBzsanBtE0#4PV{!QCE1rDx^f{03oK$cq*rA)M!IWJFdvA7D^K&9ekWk>5lA zph(FfG<;b1ar0e4@8ckq(M&ICGt1&x+^>8)Ye(mS4DE2FJeA>OxZn&$?_=`j$a{iG zYlrxyn@Gx8q^0KO%WJh8rS~$OyqW{ z8eQ3_(y$WOxG{falFDU|7uWVS-yc_|w7R6nZZXN&vi#td>(^i0balZ+UKI=Z!SlkQ z7r7chNGWfX24D(3+Y-H-2qy7g=YtFw0ZP;uu>40kGc6M;6z9{ z3((SBY@tg*Imc0p0nqjmZ7Pww)Np}C0=APU?17vxK0Ue8*k=T}E?3}hC)8e&g<$S? zVXezL-x0IlVXG@M8mmr!Uy1Fa;WAu`=02_-{q|-F&8?dL=PAW-yd6|rnYGi^#I8zU z9Drw6!8ALeSf@`Q;bW74D;;^rW588wpnpOM%YZs87~up1Z`F9;43oOzMo;1&-TUZ7|RUyf4EpRHQu@DL>kZcEPc^ zAR^9dDYDa{e(ZMjdT-zDSwNlKuxj#72ELrJqms*aUUaD1 z@jyPMoq55G5rJ#ag}Q@TE^;F*3wff_MC9qIhX){j9IpPp`O~vgd#9!%k8WVHGW)MK zr+t`;vwyq4BokZsqPD8zkHu?M-mP-Z+k0!}oREsLB);|F3vuDD_=UH}bB2en;~Y`& zCo)^)hvVC01AQN@>npbkC>4U!85gHd74SFco^fau1Q+m~v3Hq5fqet=UN@4-gsCu6 zW@GF{-JY4TluD6xT{eTsVTZJEb=T`BGGev1m2BEnSpwC{dAfA`@s+QFFcqw1Ayty}%1W`3w@tT%*=-w$^T7i^tA^yv)n*Ye11;k`}GJ3{|%Hp*3OVPK*m0 z;;W7HQz8|$UqxwaKfe81@bmW**JlDvF`&5W&AD}rs~U9La*;;C6+|DcO!bf=2|v68WO%!gP>s` z;4HS(2|BkjD0->m%kv~YZQnW9#ap}8N-P7Yru)w7#(z<5iF}}KwgyFlBWmWTX z?(n6Y_pUfSx#@L9EK+kCdzXk~Qv36HSx1gL8rqXs3NUo@ z$mt7UKsiltc*$h3cwYN?FXgw40^>wa88t&#D zT>JWIa{^9F_N(&DFUj(^U24BO9FC-C0ICv|0}+{*Nyh?zms8bvor%SgVw1DTP|2X=9T7|Qk)}TQ<@5$RlZHdaT z!b_E<#gaQGe|LHvH@pi812fgVB8OAIl);FR`+{ecXE(7>PSpM%Z??iBRisz3b1Xn( zW96Q>w!r&-%>cfKzGDJVcVmSq0!E}x8L<~rNW~sXlk*3a1v$Eoz}SdlKxo+Ad^5A) zBbrycI?@cl_G$k+;XNl-XL*YFLK3Ce)(n-5h5s?IUk%Rqj662*VMp?|v4s%?K{OZYr?zcz9Cn_I`05F|6?iF?`!pIqed?uX}*$d@l0`KL}4 zBY`PVHD830Q@yu(iuUiq0O)vxF`FnpNmK*ECQALp^Sg7Ch5>km*~t^nkQlS2h$Fy> z^IAH)o*uL{$pMFshP9IM3Sk+{#GvSUBy9k6hybuc_|O1sx36j6=a`6t`+NJUmd*hevc4Ag32;X%L+q7Fbv5ckzY5Uz{zrr?VuUg0g!?1kiljYjtX=12LE)?y~rch z7#h%OAlUQ5U;?8nEReYA9)yRC_81fL+z7hFF{&H$rH!E;;ggNmS-@^nauQtRkf8(j zl38)IS1b(95g}4Yhyhf*ckL#GZZwD;5v7ZcW7bH|ztug*&|BJl+OZeE`x;*pl@HKN zi^E6w9eE{vT;tPD^mAT%%wE4<01)p`ljlOhy@Z%no9c7eV%m85i)u1<^J+|WS>ql0 z`MRciwlAWE4wAuLoA*sf)4+uJ!GOS>6|7CfM2TN7$RB~Rx$6o=gJ*PguQYac{*@iCtJ#MVL}2Ouxkjp7^zc#79sw&M1+`klj9sfD_yMc1hJcE-?j z8UVFjg;x%veVe@W6N&(=o{6%~K0W?SX)n0!N(QYrX@oBz)_3gL*si#&x zsLYH1? zV(t%IPKX(iLvBq+`04J?M`Z)+-J816-~1?Uvyg#T*hdoF;12dSC_pc12AHr7dS%AT zvKgU#-x_@_9DF$@oJ_W7N_A_liRO0SYTbZ2Nt>;xEZ>N^V7$CsFjMF>&~4af>TQ)E$`1XK~*iJ@nF0|JU@L)d*XWAufxC-`(p@x6REA zF*4MC*X!kGdz8%NM?Kn*+yuhwkDLuNepZH{d)O9~=@^@>eYQ%A=91-`nyx+?tjFAMpNqnWzhqVtdypPSI{48Vu~e?)Cxo1Q#@@Z16!rww1C9 zs8ulutnDy%xcTn>zOki8-T?@I11YE#XMKi9P{*(;hSQY4wN@$P z;M(!u-2?e$c9rHwgLNY~U_$0Y($1t#DqZ5FMjjyMZ{Y)LW?`2u8L-WrNJ~mYlwv-Z zE6;MgQDh{d-mYdtvH8X&I`lx=m&z1Mbf?7(O5`QGz?6-8cA2+bm|+CwdFkfxvm$=P z{_`7)Ew+Bx^R)Vp$;rf57TbTle@f1kUOujCxdVTAEw+FrG;rh-*xyyv+i`$0Z-%M* z;xr{d#$2)$jfSbtC2RIWR}i|pawyi?DXo%i3PKiP-LJsGZZ3KO0Y`#lUG(g3^z`_> zFOSBO2XFwO1WX2RSi5B;xTiJN&k7yJOYSotZg^B&|10F+sb`AsP2s}JPgFv;HW#9o zl?_r!4^%6iBr`c^{{XF9C_OK>B?j&2?Tuh5paVKchNq(Bjy{Z&aK&-~Z?8A5w8xgq zR>@5@KGL%P&6-v&S6PsiveuB2e&kG>*6W6$FcC&1uOms2n8cNk$CM38i0}p}5tpp4 zq`*W}+*spo)ahh9!1qlO5pDuf1U5(^O;XCz8xRR0_FA+cN#*6XIM`GM+}+7oLTSCG zrK`zGf!L9_%b3{!pwNIsJ1f;&aLsmt)t{kX+YTb~u&lrH#K+8yGh46YLOleq(EU0N^Qb%4|k;1YUH1AmJ&f>>pY;6H( zJRXO{We|ibh1Ww zGKWL%+PnsZFIX-qoCB%tg1`h!8DeUO3I)T3JqAFf>3RSc_`6pMY+-ust7E_4X9Xv#=x~(|Ks-eXur%Z#ciwT)MKuy0I;D&lC>g&y_hlzn zzEvUyE-3xc4SaJ+1Ku{TPz7y>%@lo6i27Bg-|4BYo`=QSf1bN=d~4Fb3bmGWGX2`^ zji1D@W;Qh@WurHTy+tJb=n4@Fvo{(cXh5<~E1<_!)0zSG0-#zc|45^cet{HumIeNP zPM!DSJp=K88#+4;F~ipd?&P&XK_ilb75uw0V9E7Wa3x=pAkou-v?lmVX3%``9VD2n zJpcprb#61#(E--)-(p0ogg(QEBGbWnmYzta8n8pZNG-V(3W_hq1n&SD}eWO*7Bat?tIRg9Ob` zn$wt})gxqqB*vJn*ad_ev*SwvbpPpK?@(D4iK7D~FkoED7(S>6X%}d6ABs0X;kqQF zzfqQYBs_c?psBlIfdqmC_xlZ@wP)82PaLdHNM++%J@SvB3mRY*pwB;d>#-g4we zH)QMz5m0_O%35y*L~uF7+daOKK<9^tnyBA1NuV1w8U6_mavK5Sbm#!$qs!Cr~(wc!LT)MS7HZuRIkNL43T2E4mK zn74N1o73T`TNn5{b+%~N_26eSh%Z?!MMc_T>DLB)T?px>b!9|D4xcv#%w~n@R)zsy zp${;+8Qr^%DQV7*a2`!{9(aDqfL*2ea|7&E9G~eAKeYzE>t|76V^7>x(Nd^l(@u&{S*k7{?JlGDk z%YYsIFg@jxH?t$!8=(ty;O%m760#VZ>1&L_P6=1PQ%xB2{V@-suCX+&QxUv1M~0Qp zlSTYsRo?Janp;SZUs3m{=Zn(r=DMtq-V~i{dT8>i3uGLVCw6GmoW~EC=gBj6&=>O{085gAAzKtS?XE<_yuuWV{Of{_pF^vJ&X=GcIvX6b!qORtq9@_!WX)&$T-8%GKv-TEbK zwxxc*zHN0=$sv<4RAKOCv4q;o(iMZK01|GV705KNAi+2`Bzl1VGvJ2SA0nYZJb4TeoWeL{Z#_?a#P>GdRje@5qU+$$!}TgtzNw zK*c6tPrIz`G&+GyK_ik0%Q*U?k{aJ2gQU`13wPJ4OO#Ki0-!{3MhYR^LMaH^1i1Sp z%eYjkwxP#3umcbl>Q;c82Kl_x8B5YJR?Y~Ekj>JUMa9fWS@T|cy=0v_S$NpMpMrrz zF-RL=@fxFpP>9`-pT4N4yB-hTm)_gfec?dV;GqwC_9t{Ndg~-mX{e{c0Z>_MYCCoB zlh;phtxq4*$}+hhhbq8B^=Nz--!^#fv$<0U4#Z4jquV9udJmAHZd_xyMSS`B#Z-AV zcrFAV--aIUkvuj*y=Yqoyd=6E8JBJI02_d?KPh9jpSDQoQF3$JR`^w)$Lj|@&GZym zj|VQma0@DD3W3f&vXc-Ug=&YVhb}*BiqZeq@SmP8`IE~!xZAa)+x6n3UsXMAN$l1t^pf#E$N_&gGD}ig`3k#T2-R+@62O^?>)MsHQldTR|$k+wUam~@Q%M{B@wXE z!9p3AD^cIAN$rro_f*2&h=c89z~9s^+F>;UGzIL&pBs0Il@}tqagDG$2w34z#iSDcPX1j^!|`_B2|{x=F(r+%tA>@|kVQ*V3aEuq;kWb?47s<7M4? z^7f`;(~%o472q`3$0%gOW_^$-&pt#6Xv=7dku! z?h1b?u1j`7?QYdN*}N5{2s_{7Yg$i6SEUUf3?QA#sN$vQ3;;O@n0b(~?54!Ja*a*FmA-nA6pU@sC20#(tE`@vV31myQx2Oi( zbr|7dQa*3bkkThx>)a}J4w5ak|_^cs> z(d@bpyfx8lo!=!)fRhs4WF=%^)hEBrl;4bGgNM!<@N zhFc^zzHb;){EdCJ0iLdacJ<%KQ=JlIL~+^W5b;B>5H2=N0mP8r4b00D2|jGQ^MS4v zx8T^zaOoDS$;_Pb2We5vwyERnOEFid11nh5}XU@-P}*S#W$z}|jscHAHR=I5F?+C2Z?3%TQ^pEuTR zI664^);t-Zxie^ys~g#K6?wSpcrW<&tm;-?9K(AFM^?~j2bY?)b!KGgXUF?9a}-|i z^&cqwk%%@#pzJ^LdyhW^9;Vl6H@w-aPx-ZI7hXYu5Re?Dq(mD1*pX^n8-J*N*f@_hWQ>+q>NV zZg243m87j(4(nb5^=<;%vtQQzjir^lq$M=nOl40dqDG%J*+gOL4A^U^b7cyM>i|a| z^?+dMku%^Q^(43|(~C9$q17do*{mhn;w=SChi)A}AA#hX&#E@--#4<()L@Lg5neAr zt!&qC`%x|y-AG!8-V8`&F420=?F~n3>1YE+Zuk?3Qo;leTOz9l{HG*W54D} zq()bjq4|SXF>sk{>NbE$#zLqxD~??N4}d(fG;AVTS%nT*zFL6@fbyaURc=)i*^{co_+u&ibO9G$?prGy&XFGtR z0-M~|L#k;aL3ZU1W;&I0)pI`ThOYrecBxc5xeU}sv(k3be(my^@ARodxr+S3{Kcpnj+-c zgn*7Bimbg3TQP9Xs#ct0_{&>L0!plr<&Nqg>V#Ij(?-df<@e+LDZe*-8VXo}Ffz#b zE$3>f0}?|&@^{gm6E3qH2?nw}A5H>Cz?*=kgzwiXgP7)53QfRRGJg~zPh?T60N!F(TL%H0kJvTrVkNt_4RE=_Mz5W z4AoyouKU@O~ilY2+Nf^ey9XmM=%XP|luiw*xqv?3#5{%1Km*04aL& z9v7=q#c^Zk<@b4niLovPr9t{3?wT&s*le1z(wg}^PdQ;>aWi@AvMwRQql;}Clai{= zhiG(c-mgieqv3YrnpLkGjsks1xdf zGM{=&3Xn9k)7kNv-+!`iPwk%83TwDqM1W1mtpx^NnV!X;3NFddl|D1jd?_H$pM<1c zCO=V77Y%cm(6vUb<7OUdW1Om)zy1iB?^Y*^0+K2`=;LN}hgOoEVv#fii;n>4p6pk0 zTdq64sq>=%lCcEoM9&tw5+uZ(tx?-13o&Y~G=yChi}Ri~a`rx#m0BgY#Bdg{W~A{H zDq(Lg?@E~`yFN7BT@mopy5nYFCD=T$L39pNnIk13XYc8n?|*IwH`|$WP&iJ*aC6%w zva;M8Ordkg`K5ffDybuOgo8}${{rO6SKA@7H5%TzCO|`-$E-+niImLjUegOsI{}(Q zI{<-Mmv;25P5UYgxlR|_bAbc5G+W=C4&4LB*XA!YCjiTPksi&?7~6pz7|ZtpM9A)~ zwF?DOV$7`Xo;XzpBTFT_SsxT4PyzW8HZdnCy7x&iS~_>>+l$v3xwktHi=)u719z!Q zh2w3`6YJ~2xkopqtdVW6Tj|njAz_Y_Zt;T}3&WyKk0j?;f-<5Kn|0pJX*4rl9Wb!! z^K0v%VPAx?!o1y6mlLoahdE`_M<4kIEcsm-UXYJx z&B``RL)41)<;tyXokj!JFjh=s$RVp)w(-f=hf;VBt!MUrf^^PYnl%B0{$*^6!sL;g zG6hp6u437#vkn#R|J+x-2JV=ZG|D`!^##Rdyh@}TckR%sTiaOjt=g||L*XOJr7j#C zJ~;~NUEb~yMApoyD^)r?kfF0cJF}8TS4R0fZ5#Qt0Hc%5_Q|QI0S%ZVj}C+!73)wf zY|~u}!YiM6bRo6hSX~&9+}Ox*1iJma%VJS6Cc#=F-E;i#iUMH4%p1%=uWpK+lmZdo^M3Um5Ljc3 z5#xw|XjcA+1ynM#;!ge8b8D*;qpTyD%d>DydrZEo$y|F&;0XS+!`!B@871#kTVE}w zsHE$Y*Ed*lY2zW*ZV}h&0U~GDG{Z%OY^2V+q{V?~K6_mH4Y$$O){_j&%n)joh_5o# zxrdl_-Do4Wp~)=p&n!wU6|HCP62LPG7D>19_6BnHi1!+WL8P`lh9TnTT9cUD7lG#z zVz1&;P6PG?&#&ilf)yPR$lYhx5yKoi;1UY(ma>v4q5JX%3~kwdos;CfFKwxvwlK?56P?l0xqHS%28vz2m3fl6grQ= zJ(GD!aqBJu(hC^l5Z0TbF@toDjI85##h|u5x|3qYle-7RDs8F_?1H&^n_4NMoh7 za1Kbz%&odOIb)lQ;hHE#0O*b}`k_Hb%n8gK{B4ewJR!Eb79HGULgFxV(z=X@ ztpW6Q0>jA$-y%!ou2ZoBk!2aFUxEn|lf^P(GLqc<-UonJllh2<&WkfNbasWwIslF1 z8$emP2psV>d_^W+Ft^R2noU$75YM?JyB^{jE@?m}hIXwxq+*^^;n-J=*>jiF7fM)= zTS7`Ohbk)i`neHt_Q?Vrw3Q8yuJ4yuX^w0HMp1z^l_}#~tv(D!w#JiiA!v6MTZN{0 z)!J_s(39+A7$eef99|_51YJLEelZ4$sOqB+AXkOJBvb=kyte#UG|OcFNlSkN71fXD zmImnz8Tdf7c??>Xs<>)HFG0q&f6pZbO3;r!pveYj9?3M`(Ug5JDXo*qfybmUD1*FX z#crrJ5U?TjpNasKEz_)HED{%J!%5 zs6CD#>xn&E8*Xzh8<0l>X7cGXJyoB?gx9&%Gq8m@w~FkqNL;~Ev-{BO+hG;1WJ&D& z36mD8I_>DTB7z~X%nZW24RGBohI-h^@1_BC*hQ~8qf}C#s|B5^Fyt6UBR{(6Z{8uV z+GxCG!VpDwS<;DG0y=|698SJ(89IP5@Z1TLpuOhpmTOb$=*ej?Zpna`Y`AuQkn}Pi zGYjb#kJ}IByt71*Hvk`xnUH!d$vBqdR9zinKkL=}KaHOhXSoKaeL9EBU2{-~W6LpC zpXh&$ICJGJ%Z`=XCcxokIr9a>6&;-v*vJOqTU<_ZyQi#9C;o85OhGDrQRrjlrWtFf z4p;usE+Z$8<L zzCWgPeOJ9zpVbcUe)@)pa7!d0mZZ?ekFKG8MJ$a5ynl$19f;qcY8VK2%}s(S;h0cv zh*(An6{9S70P4*hdt}?Ra9(xyIAp#s}!GhmkAp(gxnlCwrGUC+$#%x7f*`! zW5y^En<_4d158C%O~SZDu}83;edxXd21Q0YCal5Pr_wJ)Mj?VjWhLLLDnJ}4}gpE^g**#tWzgeN9!q7&VO39$}| zYoBgB(2{txB~ejh-|@txjHmG$ElIf!2OS*ZPPH5?J`-!RY5#@9L)D3USexQ&9S&dL z#PUl#{O|X}_^VsE8b|K7967-w+6^3el6dr4%h6ZgkB&MVn@l|RuI1Rr@5jD49RHSh z{71|2Ki`joOc8PhB8Ir|D2(BR(PPU*LqPUnrWyaYNiDOs}32aa1G4%d}zA6BIFP;RSodf}73>Byc2z6Xs5fy(+PB}V@ zgT(kYE)La4ri_NDmD25H8I3T$N`@zCg12D21VXKHEKqXHU9U+X!?-RO$E4+%-k`@r zCvC=Z-lB*c8iHnY^2g#CRSGtZTbOa9FdYVxWC-&yT&tMgOv~)#B0D6a7651f@Y6~P zuK{IWD9|3pw*shEE?y$TcfiP7FtS;O>wQ+_2%jy!QS{Ie=#r&gc}BOCD3~vWDx|@n zIpWMHO^G5^R_Q*&{~=!bQs~+tes+U?jf=OV5madBT7dJ7T)asTk!(O7=8{RQe5qsJ zaxKuF1_UG(d$m<)KR?$3S0LdMRc`X~pQ-yH1@lnQZt=pGk<-d;mmK{{os&VBd$v{+ zaFXE3nrTuAK97kyeXeRslBvBT;?_ZY*Jh;XC*JoEa#*wWo+A+Wy!N^l;C&YNV6hsc zVYMVViO(;+`ibSY876ZDEIN8hOe%w1P&BN4G8XrPP8DO5eqL6*2^*4IbZX-d3}G& zH6(yP-G-OMUf=qwc~>Gz0O8xP}~Wcb#g>#65kOPv!u z07A12&v#PkO=w>fD=x+@ zfyX|4!1H+0%EWi}Nz?X+XFd4`z0;2R2#>GHn_({5u2rCIcVz$saYB)4QQ#VrpnoPbe8)a_mvIz`tLt@I57C=@z9gur=?BV zs~ng@vn<9*j0`|(-pJBd>FMnonf)^Nb^hDJ_r)J4pQnQm#5zXX;FsLZM_O{KX01!f znP`&@M;HMlNou_2hAJj3?as_d7a2eHYO1)K&+xAaxnYjZJY#L~vULy%($$epA%NV1 zG)uwQg$fzSk`>h{#I2THZcvH}u*!~YG^cy7Ht6b}SAExO&CrC7%zwcNFY>;0$<|&T z0s&JHOB~e7!O>2H8tK%O3VA#{y~^Lc?dzJ>hS5%;>QlT-;GP4#v5aV#9pz`W=mv2X zc?KQ$OcDkVaXTbi6v-(&W;wpIHno{M@0I*>vw1Juff*fC07585gv*iX;!#KeiCh&w zO$6Mw0Ekt-E&!qnv7ZKM)f~Ec^ZRF6XV$*1qwPPwPCu?)enb2R@%Y2|y$kDa-}>`w z@#}{-4{zU=Pb&;V2BXv?Wqhnjl`NTHKP?lGJsG1Zl)%W*RBA-kXqryK^k_OQjWH%P zIvqKd!MIp8mTB2MJ(gwN!x+!D8;%@5!JMiZ&v99p9zO|T%qB!^_1zP>-X_&@2(GXF zyNP^`r`cpdK;Z7lQ$Z2clcz%x-c6q2rkPC@hMnF$bvE*1_0+lO=66%)c|B%ti{giO zzb#Ihs(xE?WZ~W03oyofTAZT3XS!5qQZrqaZU26{T;yr~uA(4t&%4UPh?;j7ixS?y zyChCCe_vH`de8gns*5%6YwDWczpriRG5=6^ZFtXz%dJy2AL`o|-ha3v!C1^Rbg4(p zH1?X*&NTJgf0(&C;A!#k+E8HB$Lk{zwI7>bCVcqVB1^OQ)H-oG>eG$si?yHHW|}{I zx;fip@wt6|IO_Aii&M3qZ~b2Q@cA~NU^y#6X++OTv8Hvi9R!D&*-o;T<(DqXrsywA z-PB!mUwU*BXTIE_rCZMR8l8!ryUVy#H`iy`GBbD2`i|w-e!Hj9U+**D)_r~8@_pv( zLr7uS`~X{H@BE;*>E-!Hz78MfA9K8xeH#kcwD;SSpk0^04TmIt{PvWazHDJ6?9ASU zXOWjKFFcQK`MB_ccW2r6m+?>cet(tp_VV}FN4|gj{svaCT9l<|#4L^qP3srOvK>Ax zj*GmkeoPc>iuo~FxU2rhR8iumA8*C!RzIgJ&cytDS9PiW=li;rPd`62+_CyKbM0x& zuaB*7>wkS}|NiOMXNkh{-?LpB`+k4vHNEnCuHWJF@2>-1%m2&|ZQA$e+sLjfe->UQ ze*W`amcIP&*2Rf4`~LozzI5g9&zY9be}B#1S-$jp{^`D@KZ|d#EdBlc{j+=u$}4si zyd?;&Ss3dpMria(Fix|4a<&*{>Mg|u&n8p*#TbWPDe2IxfJP|AdU z7vKLWlz-=8QL~>D}stf@i}n)rVH7C zsIhF46xPoDvc~?%2-43%pJvb#z$m?l+6aRG?Y8!Ry1ns#8dv{MBXF%6*)V;sGYSxX z<1Wqy>mQtWz48cFc3vq{$2B<~D?WKMUfZVz;bm#u#kW#GaEwbYrojK_R&M^E(*G|8 z`@b@`{ZFR(|78B3Td8j!&m_~`Gn6gXbtU=j^yJqV2@A;N|0=ThPcK9NUldIX@EU1C z(k{v4CklrD7j^F$*5uZ;i_Ygs0%<_#p$H*_-U5UUn$VjPih_t5ii!w|2nyIj=tT%s zY#2ak0tReg!O#?YKve8tMMaH@T^IX7*Sq%mzW06iIp5yrJJ&h?xVSEK%rVEj?|Y0f zpD{s9Yi#!ivPIbGQsSIX%+V{k=l^5){o0OIe_Awq=uFIP%q;A!87_7XtmTobR>rQ~ zket4an~fZ|;*yFIenn*!LihsYtTi>awzdg>pSYg0efMtWBr4|)9Vd_{GC+l0>!Ti%KT=3d=P)6R9b188G&XNb_VN;ySHno{WScc0yrnkGtd_Vt?ppYfu5m9RrHmpnBn3SBB zn!bJW79KYv_ZP_S+b=lK*4}~a_sLUddV2@N;*ncpx5sYZ86Tf`^=j(x`;9S1wmD$M zB3*V${Lc2*oP{gy&QX;YnXaCHx|mg3eH&YLLFY^7L2Py`a+z;d?EhNw*1zrXf3JBa z$R00T>{370wzLwCCorFqPE}Dwt!w;M`(*_7Aqs9%7#n{Zuk6?i(r?iE1=75~HrQqM z-2Nw!R(3CN$5P!AXSfrcv2pTN5vA%j49vjI<7+hoQWS3$KkhHZE%d22(xLp%73ciF zwqnzNx#D;ma(Uqd(tS3@Q8RhehQ0N?qfG2?M75ud!ot6n{9lEtz}Y`9`Ez{$Ydssp zsfWhfwxcl1mZT>9V#>KilO(RoW|Eqt|MTeO=dzWhgZ4I+(uQ&sf_&M^zaj4zK2`i> zhULFT%-Cx+efi+iS1u^69mqpDITG{NBdlon4^}jtwWj)S-qzE%G&EqC8roZ#%(XFf zwKwy0w(y&08Njp*U|F(#Y?cJt$1HV8;>=BnVW!2qWh8qRZu6_+&EJ!|Kv2YP+!5GR z9(=emq~l+vbF(N{5SBl`zPe4V3W+ae3P4v(!|t1Db@ ziX6~#pnMrYi`#3TnmPkrMXb7Ui;DDm7HtX0h~+dPFQWZ*Mr;4(LjwaV#7HAU8^lOU zleu>09*7&~(dWBb1$i;Td~Cwmc2Ub*l75@Ff|(xg&O=;R9N1JA+*Y;lKiKnkj`;mE z#^_hX`qam^Eke%d8t=d-1ImG_^6g%Z%gu&bh~y&OH^=pSr~G^x|4e!z)tK=LBAAQ1yb05cS^(B0Fne%YX2m zHL{z2wh=-0(cw;OqvmZ|!Ag&F+m7trmf4Nli$q#pKrP~~ijc!q|0dG@SN0XKaYpsz zHu(x90+oCe2F!gmQ@MDoCKIAzQF(CDQ(s&#uZi*}!u=|@9RAaPnX}@1HuB~=)BXHx zB9M(l_Al0T+d7ZzWUr!azEv6iyGsIF{)#HJ9?4s{I{_^t-n)qzn5b=7w%mT>iwo&s zJ=y|G_H?P0(1;?|Z>$fQ^~8}sJpr(${{|W8I)nK{6WPi9IF4}K z)YR+g>FK}I+`sewf9dy9HcT#wa+_?Yfa!>i+pVJMw(2nhvfU?j0~ebI-_;jvFj}Nn z&B_fvMg;N!DXIJ92)RFWeh2+8+C2UDpfa*DG`9Zj28QiimYd(|xWwGN9eMf1MLWt$ z%XU^&?n2TfWW|EogPli?BZ<=8yZ4dxJ$m%`<;z#UGoycJ{%pon5U8f-iJrrK8ZJ+n zQ}(XUip$(&3gvoRoOp#>d2CtYjr2j!`L{O2G^rzL%3o!?-aj{M7T1hT8HnmYWaa8L z>kErYe;Kr|q4Ci1lm9?kTxy*Y+g-#?%S5vCJ%6d_?m0K$;EIs6bJuLL-FFDs{`2oO?LW~6xKTd5eS?8Vp<-8f9c+(T)%3BxXOYMqL}4av_8rEMaz=l zo}Up?5eBqetE{U6bWVmWPTceE@E5kgqvycS_lgf){r3(|ec5LiRlpfIojp@M(M?*A$3FtcvBHd{vsG_7oZ zEoKldoBN2zl&*hqMrU%zp)Bdg-bPgWeZSrCD?K0o(+#u7;lFo_5FL&P-G7j%-#EB#uVDR6`QV7Sx_!w7R;1OTZp1`L`T~^1e z_8JnLC}p{_VHleydb2_kgJ!dS!`&}Q`r`kQ7?Pfip&A9UlsXL*7O%gQhQ!c@n*o2t z(64ZP^QR;J3|BKF2TNlYBzh1=T17vcxyDj`I<0d8wWjT!D$;+P{k^8-SqOUnw}8;ovvi^RyIBYO+J*<(NB0Kg|!QFJK=)WYqu782u|`f2k@q&hj6+fLQ1{@P>+pWKDIK!#d@L-myjf^bJOz3no3rjP)EuHRU zW$9{b0kaJH6!wi^)^*68A2wq#yjp8HzoVF zg&!W9+7R-x%GE^q#mA^)Lz73-udJ>2&^@umT>JXn=FN_W-yT|Mx@|2tW+g9oTV!5r zR_w++W0RCpdS#`K9#t`hs#ln6Cc8hcBzMoY#`+D$t6D5t_tvx@vDj5Fj3w>YJ-9~k zbk$Bn{W%u;%BmELJ+uf#b#03|WaYLZMb#Qrbv2{Qx@IaW!xnP!6lD_4SjE-CNJUL! z>doZKiN|ZSh%wi1m})R46*?k$ri?jC$sCVbq&cfV3&XN3vKZOKIW4%zN7)$FKf)_g zJ!N+k;>o5}P5f1~qpF{L*XsmN3?5%=@Og>s z&qB^CE1l)L3k;SEz;In{=iU`91wk9GPX_NCo*Re2i{7# zenuF&oO-~3g{P3J{32sD?P08HyeNb4RLEil(D=+5jg?0Co=)Ja6NU)cDp5`6mPuKW zb8%XNNU}yFr}HwN;+))@_wdG~k8!NO*uPNbj#aq2UM#78%Q0G0VgA9cL{|1ip~@WX zv@-&cl*V)?C$qM+W5rJAmUn}h`dRE}rJgN;E3Nm8g+*&o4ydSQMJ4&T?y=byHj%B< zZYfY#tXbcS*G!x^kYUE*bJVrCeAlQ&TDzNY>P-{^#<+=0!#mkl!SLIAddAI)Twl0h zcV}-MdPjk%4{d^e&%&Cf7I>mfy0|ZJibB3FL3OXM)Zv~uFr}_7y4X~o(9p}n88|Z3^|CmQP3NI%!qUugvU}LjF|h}EW=-|8x;63YX8!!X5`$Pi4p-kpdU0U5C&QxI z%$xN1@SdV2eVYlbkx#n~Vo$~j$*sGa4Kna#BJy-8$yL*&shNjQ8|b*_xTc6}vV1Hp zqHuRi6SGu(&fP=r)~t|NTA$Y$JC$S+Q)HV&qh#>~7c34%?xR(;iDA6qQD)zj7|pGF zX!LpCtcP5hhv5wtdGvt;I^|U#STWTi*CmD<-lv??I4k^k;bQZTp}XB{rzpCnM?_Ym zE)xYG@5f)ir5Of3NrR=`hV6xBu2IX2dSUI=MBhtKbrKUZ+LK~>x;2bqM7>WB zl)rI!esH#Tl03Sw75k6g3GMsqS9}&(JhVV+R`np7|zbtmVY(1 zZOz<}#2U%|k&5hG`g}(xx4*Z!kvs>f-~C@UyWQ-pJnXG}on~=%w()HlfMlOYYV2#X zB*2b?=piHxMKoBrQ^IUT0V#XTOAYx497p@wB3K-^;5QcABUrp8%+bSh0g_+&`UN8? zR`9}z(6Hr5>F_`G(~60`;DlN5y4fb0s2fTY#C&S0*P_tx>mw3zI7r(Y*aN!gh;5Rdk(DdeE%mfBySuCt>)yuZiRjhby` zZr6paryM0J;}nV$CB1-8@t8@^63Xnh!z-O$eEr#lJl-1p1CgkMf@oksYvf$_gbM7} zj&2f5pBe8XfYzzbOY#v@yi2B7<5V;_HB?rpfON|*Nw=V{`UoNHln5TC;LlhskbrPU4%0zRDut+oYy}! z)h4Z{Fi;Ci)L+!?pwhsBSR7msKJMsvC{#*5$TYD!F0MU1Qw1-g?C<3APpljK4)^R9 z(i2?s>5mL`_IuLl2dm6m^6RDNJDG!dw4{XO53s0DzbLkhe-<3!x3}(&SIBD{?s(ZE zaG$<$zm&cwuup3Ptdiyuu`F_fZNLfY`pO=}ZbW4`>yB>j3#^WE4|jVrSg9x97Fze$S{b>2V#1JiL{a zZ+NreODNxU?>fb^k!S)raNgJEYsio@wCgePucD;_2BbNo-i+e4lTDRRYcta zSh!A-{IMj;=dq-IP(aFgeSQD=W&EBTZ!XLsKGveV%k$QJM#*X)e~ zpl8^MR;@0p?N`6ahnoXmQ@Ueeki4E772Bk|bS%&upElGfFQBYk;$Z^DZ2>(byG8W06}iZA0Vlmz z1NA(XiFvFhNVO#OzfkHc474dZy;!Sb>w z6<)0G2(eG+I$NZJYOQ4VWBFQG*M-vSqgW@sXL^cLTA`Otb?32`XWWi*3J27(ST>g2 z45_~TyMdu=UhuWeun2qe?kWNn%k^N&Wt@r1a+SkhE8fP?DZ;u7j9<1-#tSJKsbSON zrz1HqSD@$;nR0T-6`T$RLkdQ&Vd_0Rpd6G*HVS~~==)j`Oo_oULN z`C^}*)F|bJQ;T<7+;x`o(IELb91nOLOPLZ(7R(nDhek^WwMZSzDZJ65llq;SOy9nBAm?Ne63aU+6^l7P6CUs3Illll(kM}8c z#P&sRs)d$K&p&c9Z%VnCbbILxpb&h-9*QS9g|q?L6DM~J5zp;{z_>sGg?&c9!iStI zSyIuIGU5Q2D}Uh;G78mGaa%8%^rH?Jn&JIyNHl-lqGqY6CZxQV`qoC`W#D!|E^2*#Vh*h0kIC!tCSPhO{{2ote{?=*l!$>0337 zvDbmnT%O`LERYj+P}of+a2+Wd=d6#1kq;Yt4ycjDQ7S%TZNY#O7lNXc12NC+st^Nc z92H7jiUNtcCcH#l<;Y^5S%_tL*ei8ZXVYMNcVeNWWy- z)a1tEjSfPdx!MSDcRw%vTBC12dN9O8sWwbXUPMuv;O$XIKg!_N$PY!3v{R2|70h@K zkkmPOEVMCvH@f>0B+jfT-224P%sbfP0m@sgg~T^bUUrVI?Vrkjyv5H@Y8ktJQnAa^|9S+NRC_@c%vNs>G(C57wdYzFPZ#hnzcot%PzL6x7%+Qh z_E%?wj+ESymH<+9M-mHLd!~bv+wY_SY5LDfc;w9%c4$CgSWs~IFBKoXoU3$Q6)TwVt*OxetIyY((B>{X^a&HT>W5w!C7twXkb<&Ht3Q4K0mLhnid3eqS3n zcC33=;hvmTxMxLuvp2Ng}M(S6jkw{V5l%yQ>E-?Vm+~~9D*RMVY@SQh)IpC7Gek~WLi~;dze!LpD z1bGC8LpR@xA)9Lf(fA{nD@S)P!c*!zP%PcBQB#BR2B%cxuMnxWRlPnDyWjv_SRr-* za?8HZ+{F~PT;N})ncP0ryU%3?v-+`qY1GX&Uf;4jy2_ie2JjLd_w!zNGR3rV2fQ~H zDqNO(`&h^d!=ziT8WAychMHlKYNoM2vt`umMK1>0yOnGnH}5+4{)4A+;o?wto#j_g zIjb_CU4b^I477f23zY_*VrK@l9DiS0sseC01K9n>u9Go~y%T(;^}&LeQ?XV$vUeNr zemaFs?2|`f4<)~muk9OGUXm;x3dnFH(83N7A$yPU;kC5tAXuN`Oqml!PkEvin=g4% zE=_L|T_IQyqlcvNWVjy?ev~V1!tptIXr&razng<0Mns$jA}X-)r-x(c{!B#~jWK#S z#2{I@!SZ7Q){G2KY(KPvi8sPlu!@g+BlEoY8Pfg_C>aDQrwc02)+E*YDvA?XD7z45 zZ&B?>@9WYQS$0=<*-s8}A)|4Mcf}&vC^w9)x2U1GzKa~TXWdnLUbX+rCVkjGgaQK# zJ~so3Kbc=&44C0qDJv2l2=ouIGxi&_s%H>&s=0auqcA!VWrMty(A(AD|D+5M!woxQo~4SU42XRR=+k#d z=Cuj}zr617q^~bxUmIJ+5;aCI#S~{~J$l=`P^FKBu_N1CG!gxoy)v0uIMD7jyxL%9 za4hG1<9;*&7k|#F@@4xV;1k{-%lA&tXyR_W^rRQ}VN!Q*t|(rx%Sx7-iD3qMVqwL5 zdfviH4n0Hzk<8t(P z^eZFeF|2sOO0*rD;)-U{#eFil3n(I4JI3psvi%8`Nt%%Vh&K>V zZDwmfx>CK51?LdoWuUG6_nH6)B84VvC*txbR+`5|_&3K;In=p@gc`xH-DSaa&Cm;5?s(1D`3&^?U>urz**lzB>Nl%6>g05iwiWl(}d*XC%0 zP&XOESj`a`_yChfAY~Cy4@CrvJnY8s-q+`BxsWq8g4b08#j-PReSLxQY-T|a-D%Tp zx$WN`Ykap?F^4L~Q?|SIVnJf>kB%{e>%qoNrpt?^p98C9*osJDgKqsxeX-WSWd z`TkAuQ6{H`yx)DL@p%eN{9(Iu7Z-QJKcl_geyQ>y{h?5l#fNZloFcW@5|o+f95Q$- zv;F`wX1JOK%AWb`I%*^}=v;}#44=AEEe|Y*GAP0)kbD!D%CHyVjiu>jtDh)Im~ zX_FB#Ny==NOl%`}?zd)W{RUmlfQ6=zF-96gWYRI?jf4RpwMWfgCP)PL_>dG|PICzx zSKC8l6pW-<^P_O0F;c}3{lW_Q7uc{*kW~@+iVn_8a_wO=2kkPn98N!fKvTp`ZgAg6 zJkZgHvG;`nHuSO~kfEH&^lX{o8Z!;}nK)BX(3`$qB7^OCg9uAWg&WII;uf)DQ{tn| z?k^eiz3Ivnq(HHdG*LVv<)4VQXUogQF>MDNDA*M=fRU_Ps5W1aY*pRce{Lg}czXO0 zRT7ojlD=^C%wg^31BZxC+=rT!>zcD!i?6?0_ONJ3s$|jv z8A;Aj`x2Z|!lcgx!QY+Q7jSx`gH8Wtxzur$#CJcJGmNNpQ*&ztaF1c@&M zpqhGRh|KWozN39lo_J0y8_(z+8s{#*yw>D`5q3CdQ_tftb8MgwhxXVm!pOX1%Lc!D zR_8vR#Z+`abd5!KZhkpxS@UiiK_^p7DZeE(8xZrR8|y>VQj9wx={RA+vb`5znT!3NXUH>CGL+vKHz?!)r^5oNfOhYRm0Y0MN{L02d!^~|A(~spdllOC z7izq^tbeuYMzi`z8{PURcw%VuQ20a?B0oLWiC1 zGNlE&1Kl_l{LOI~s0+jtSqbAOnRzg?`ou?xjC#g3`-k^0^d`q7+IT_E0L>-;q%bp~ zqvM##J=FR4W!5G4G-J+Cw92(4j916X9^=HdKsM}DWVr`YaxK9O`j-!Ex;*^;5jL$}X;#)0Q~{AWF(1xIcYmGy@U%cm-wb@#W200C#-HA_5Do8ad86u} zweic02)l~SD;6z7J8^Ny*Or4jRTzmj=4`n9$@1F14Lcj%>;Z%)fm~Jd(PSmV#w6(j zzT2W8D-ueDpLC!cn%TIaUa&w@0zYkBvv!bHu?b?H*mB(~slzLV!hxI5&(OAgmZfp^ z94L?jMILX6s^%n&mTyW^Ks{Kr$bo4$>bBO330CQY2W2P%lEctpV{dE_nW(@;De)mC z68wg0YAeytk}Obx!ui@U=6cuaF#D?#+tXAo~QP-}Oadxi@tBOMopK*l=LIahTav zpu~X%x()+^u1oq{U`? z-sRU~L35sFzacD{%>86ou(*k5&EFwShvFyAJ7Q58si-&AzFE~YYghi$rUf`0B)8b< zn04MZ1^B#uX=4ynY+n-2b5s<=27=Oa*c@uN8rLvSnVC1z1kYhYE{XP2&!M(21x7x} z#!1E&A)8lxESam1b=_4m5>_-bSvrGM?%$RC{F19r$}@RUSi&zFiRCB6mfMU&dne7S zoAMnTh_uA=`dE8mG(_M#rll%b2vF0MxC>+M(riR2MiLuQSyW=XJ@LCe>L(9HV3&T$ z)g_Qnx@dSVhp36mA+TpqW@wPQ8l@wtKsE1B<)d`jv@Ba#>jF#l_DUrse(QhENRy6Q^tEzl66|6`yJA?*P%FKY7ua+VY%7B- z6L;;x0Sq0bKnK(mXxpTLgD{IHSYXbXqsyM7pQyoOE>{#sXG!*DQcU`pkc9{q==${6 zsy7a*SO_(rr)j*wfoDe8&pudvNJV#?W%zV2ehhr)C~Y@H86*M>@&T<#wm5Qw`fxNs zpjDd`k{b;hQozd#HO36SJRTU+*Y1SZ%-gsh5<_%xw1#kRjd$&Y5tf`}^RcRya7Uoz zK2xj6H&UXx6-L%hzSv8=D4TQ(Qlo?1RGZJcAZsL0IbftN_o4hZ=q?Vd9A}T?l`O5_h9=92O zS+F;KzZ554w}PlGOftL^J+EN@Gzqex!|S3B&OB~VNn>GZgTUAuw9g)^3a?NnnrA7VE490T+>n!=b~9U`eg;dI!8wUuiT?@30%JOY6KA2^`$! z%sqmYq8}@lnC7vMVnS{>QkE zc4F7O_4Wnwr00piWLyVD*Hn_|tBK%y62J;SxIx$3tv^WEfSPz*A8<87#Gy&Oe7#@-b?-P~AA(9zC@aQYSd=#jf=+3D)<=;q- z`=oY3QT2uf`MQSuQ-YFfX?Lasm?df`(bb0!bzkDBog#!Eyd-ns6tVY`@|`u>90p&ClaXO=x_?&K(rw-5rRPC8=i9yOrg8BcPhcgqrDw88R>xDHBo8x&3o4%YO93Dx{yOfr$3; zYVm%(F~h3e>(5SXhPqC%zYO)k^#hp8-H(MQ-QuzS{#ZhyhBI;g7-?|paJ0_2Ox#Ua zK?I(Uvj8}kEj&F`iZv?2PWkmO-HY8hqIprG`R)+bWD6uMmZythsj@+5XRIdaym2R( z=9B0`?HJ3xY|5!ILSGNFJ68e?>A*xWc)p~s++VI{Z{QA=+?{Cny$9Bg+GKOV);dVn zE(ld`5=du4Q|R z#op?Yuj9$>t`JsowQ;dwb|U`(ia8UKR1pQGnOMHsd+o6%^dW+#NxAYM3RS9m72;mG zfr&Va5qzG!|m;H@Ok&taneh{qbR%dM9tfu%{=++pi0SMEn$_lmqB55$TtS3Kri zdsswg+&3f5zm_f1S0SCtb7MvcXcU0y`;u zA5R%vNZe{>!<>=xje{gPe7}@V{+1eJ&ei;~O4o?kEl)JuJ>YdTxHMYZr zjC0VlrdA6)Ln>UQuEJ8BtuK-$ocdGV|72xyVFvL1c0_IF%=obne)DzxX7)W?Km$+a zV`bjVJj%#1dg+gP>O0Rw){?=6cb0|7^gMq28fNr7)ZpCjLr-`Y>%Kh$869{qS01D@ z;I~JB+A`Eormqg)9M=Q=7-dau^;($qSYyhZVgB&lnR!G`@Wk+=7Rb`G=ZR;H>+1DB zM@OIjT>AKj+0!?!PbKBc$WNXV*k0_`#}Cb(rFc!KiJom^OyVp3{2q*_nNPa5U({Rm z#DrN}uJnE&F|2n3YIiC^^IAD!3D2%&SgdDN|>EE{dm8l%l`$cx*m zx5V^mmaZ`eg&%+EUpIO1iY_czpv`qz(*AtcnBkYB79bUM4GNS}qFuc2+jsQ|{_67; z%Vw+?d>!7mxpOF}kQxQmn&&z~R~+f}^*3_73fM}3d?<>VDt4R}x)LQJflt>&-&{2I zouR^nmJR~^oJIFT7qxH~jh63;uUr_AiISdqdtw#V2S!06cn(K@;t|ka6crH&Yc&8G z7e0}V!b+-}l~zj8SMGOT#^b`~n^6xqD1oA6>BorS6`QWSTy--Olr!F+z`WP4(o~Xq zms07AJSn6mMztx&Do9P|Rfd;O2ixj@e7tCB@TT|X5_qxh2j`os?n?omfNA5p_kK1g zlFcUoK13_Nd-n8Gyv1tt_&arIF>xMXaTX`Mi%tkwv`Q|}bNQn6^OmBS65a6`bDwoz z-cYBh0q^rxpfvX@nB<_Ogkd4t`%M+z_SplD-gTLyg_>kPb7CsF?QzE-RewLy| zrWkx%{qEbjxA4&69|wcKXFUAgdp7Lyx)<|wKRoq}6d(R>Peq+U`b;F$KxVNG2TUBM z?#cfkkPon+L)J0iR^^h*_3x|zR@OqvLKEa#0WDnx>s(+fi^2fNwuB)AFm}rMb{Ji4 z6D?_nN?QmJ9BidQqjE7Hmy5MS%hNFooVAs)4KP-1aj>x=Yg)j4Gk~fO4m1EgJ3Bnk zwWk4d{7&`DmVC5@mX!-5*A8oue-x06?Wp~O;PqR+JW9b_Pl2!7U_7ux9Un8qZA z4-;7bmDfY$a95owUm{B6bNeMVEhDGt(0ab{e833oU_;OYQUpKEY(Q;&_Nzt#!k@rdbOnrpBTQ4^UZ zjQ8GKbFqA;Y>VkZZD6Bxu!=|`1184YKAKvPzlH7^7Hvf8!!pq0yy#bqFX95FF1j12 za4FN(J!1MfFLYOR&dvD+MM5fvAW6*BZ#2EWe~V)VCs@XGK%!uoJ3bx?G)-gs!8P|I zQ5!ehP#$)t#&Y}SS1ruE8m`tfs6vRoGec+LhJ~wRm+(1yT%vh3=yxemBJl`#OLr!J zu@8kMD?_J1IJ)@jP-6=q@zC}mdXo)<$ znhP&TUt2)xm3>eFAdK`l36c^bpWNf6LL|Kh9pZ&N7uoS~uC2nTB%`_MQz{l?#}tX? zQ|%=eakky~H5mQ9B$ll7X*Lf{zAS=T33+jDMUTo}nc+1($v=AH{FET?+`K_h@B~ox1L&$QC@zz{ZtS9YF+E}olA8c zv=tGok)$X;DfzMEEr2+1e3qvBPBa%S)#x3^9fyj!X4-dGSZ%_?&tZ$*?!@>de7dtj z8#w5`!7nfy-~R3X*L(JN=R+79Z&C35P0KZV^&pN6bBvUvIiXXrq$Yjp&dA#Dw~xog zJ5DK$YemUW;50=V zn<~J>bL8YJW|*EYlbEnQ>2!>l353(UKk%%qw_sEV*u0jq3I3i@uS&PE( zxCQtkA~0&@O|@K$Vv-+Q3!+Qp_fY`SFa-X5-deHiiRs=AK8F;H9LnSfJ z(a#tnG9^24VZ-RdfGHhIM|~gUNLhC=A6hkgn73o3>VD`FseHJV3)WeVFQTfh9#SB2 zVDrS@ljBWzZ>!aX5FZ$FCR$)-U<)cv^v78dc7==B@??r2et`A7p;uRpH z*HvsT_>m8A?* zU|lYR@&1P0P%|yxGj&%dHtza?aHW=@gDsThlEDgya5ErT#OM}%FsiUp(5g%XedLg z6`_$O&ytpgR)WKS`O?EbzI{FmWQAO5|8ZF3iwL_w$ooJj z@gfThXF_@pszg{}gR5zp^kPq~AH3}^Z=o#mAjsVRk;0xY1BMGa3PLYGQaSZykY3!e z{qCVsj44wYj~&lWxObJH$UIM*c(+YP1Sn^}tjx$yP@f@Go|6wR# z#QV*S5Njdt(>BqC+@|;KjHDw3p~!OjSe<9X51wf-6NbMq*M`>eOxI+Ajues0>cVTP zV{-i_T{|n~dY=+KzhX3)T>lkExsHdkT92i8Rp7*vdpy9E<;`vCZW2}J+{Ku~P00%h zHax?AJJ>j}sIFIekG0~-ODZv}1myc3mBckStQ@DMOqn-}19#Cf&mBu|%Zp=d2cztj zIWU{c^Pd}a`^uuiHV7oFS`(9`r*=g6`gF`{?P6NZqWb+l3eO!@b>Hc~>WgF3qh!%1 zZ^Ul=d}eA({2I5I;oL+fLDM=zMbozD%UVFFHl2CPnEPmCa?q|V^2%#D`mW1EDqs4w zYhNWDMo;7#9_!rovw(FzhNbYf@@(kH+wHk&OQ}E?`9$HVqGo!_!X2GI|9HWb6W)Gi zx2B+Lrd8fW8%A@??RVX>;c4x>H$I&L2%~!kcdC9rJ%r(Q$GuD5VaQ=AOJ3>tlAg-O zWg$3!#g$A*E>Ja zUcU*|#7(#9ViFlbg(es2uA#ZO0wC|9P2|u03nq;ub`XSNL|sz`0K4VKhlZQNOZ%F5Xu=%DCB`QgQ*MQ)arZ0-%UI=l%R_&!6;Tj2?ZuFe6f^esE- z+wY8I4yfuWre?-XZ1D)P!-;@hBGl~~>y5>cnQ5-6xWK-x&!2J=Po%x#@)`s&x$LYU z?=+e`_tNup%T#Qbw<$!P0k&bhNCq$35b~eQq6Ov#q$;Y2{9EeegGM>}WRok8Hj6fC zs!~ub2DmP!nnhS1En{lf{qNL@iPDoqK(8C|IO9#(du)YD8{W+k?`+MhaKO=mL$zO679 zC64|y3Ab>{P(~?R#ptU{qDQeN!hT>$pY<(qcxJYWQ;=I@YTtB)+M$m^WhO?*dMF|pf} z1H>6Ywh~o8oxR6)ElEgQk(!K)G=etY-h3{9w;6j$S@XUl&uF@A6!MJeD*!d~$O9t< z#{}T2m}(_RD!ibW8wN>zB^uEUa)s4MBg0xOlX1bUY!besxaS6!NL&x0xG0o(U-Jd( z%dd*c0(byJdNoGb9V0h&Aziz)0-~el(otgrCis&T-GQ+~62`qn~NNypaIznZv#V9oa z^uu9Q$#AW;2owu|K?m5$Ez;yvU$Z5sF!v|tK@(qR$W{VKBF?4r6|F>&HN9@@eE+>9pwiu3)g-74moXFr z774WNPLs_yTy@8`Y`4!%oh91q2^dZbUY#(2J~SPOvLi7MrDK~5LLJ3brctOtSA_#F z8*;^MIp3l))A4@YYX{W9rQM2m#WEj6EM!*l;@UQ=pu;8d=4Vc5v?R7m#BKI`pgs;q z6&OZ$v|Yi2USHGOiHB2;&L~Om5q`ZR^D2Fb7PASE(5~;G+LOg2j-4eLLM8RX8hhGn zO7V6RnKcE6#p()gIi0GfN-Twk?19kq`(cJ_8?K;}pm9Wt(K^^7C!)js@7oCO9VZKH zKJ4y@FKCDTj^Lpq8YU?E=CqewtjstZyaH#Ip*H%A=(HKyezVzgcheeU;5Z(-FAmAO z;j|mLCv21z+gtxsn~o5+;-YK-I(mJRl;GIFE!pZ0lAZuYEv757u?x!dRvfB*&qkGp z8NSL*lVh0>S+tE0T!M*%-|Mzu(NL7Cza zilY8hfkX$SKsdCf%mSi1iVf2lgIEQbWgB|jHT%nI`)(~6G)O%AV~G|_94zXbVU|UP z222&0@HDJAB4pA5je349Rn5;-&*L=HjP3nSV{mgTWX%rTc#jD9N^|1DH5O_Z^e%Sp zFI_)$mU9NjK?UwT&x;7WUP^YFst&x@yWr+PLQxM84(_@PZ#6AzufF&^HH{Zx$mpaI zl9@9VwWd_L;>2_B9U?CUqCS@y-j9L5-#u%pu!6K%gfsQVX9(~oFK%--T3j}tPC=n5 zuw_5(+&fzN_r8bg)9&}0*)a{QENS*fG&CdwR3R$)eO)5e0L6jKIJyD%dh9X=Vl`!K zgfR2G;u;oe|07sVcUk1+vy_#t_v;hO^Gk3&k-ob7Y&5kk_}D?sAOwxFxNx zmE zVLEn3qnKw#_h76U%u%%jpd^8wFdb!t!zcjHz@x4*@|So(QE=nS`;kRkaU3&f=~f3_ z5>$KlmT%?_2nC_9H=9f_3btCTw}yDZ*RjG|Yh`X}&oR+pL+YZcfS7LI>vC zDU+b!V}oU{ZxvhLDs$h|xZ{@F)?u|Bp-$7IKs{r4Kj?N{t`Y&E-|^5MSW&%0F0-R3^GxzqOyqxX?G z$}-H&VfXJYe>6IG^!~=zw*@;a{f`V9x!rHqz4yoKf;fs40A`Wf$+{d1RWLE4+BGatz^VxSx1iW0j7?R*GG*Ex2yQQiZ6 z$?m;$l{rF4{O+0@eL|{puyP+{$$!FZnTZX(3NLPlBbrasX$ZH$_3yQ`-C;epw|Zc0 z)mD$x2X>ZXXz7zxpRHC<#S5N%RfPa-K}uP2>v`7N)~)TwtR)FZ&u6VYj1qa-$_L6a zv1W-DB^pZ|d-sn|!)Z3U#+B{;TxA_GG&6m2^ywK{FA!n3u%u?Pum`U_0sUA#uVhcC zH1otNp6`Vy&9lpZBG-7`r0oV09y(bz{)S`R3xSI>+{h$#mlp}E8_V#0Kd}eWV^ZfhELDmahccVQ)I)KT@Jf)93$vxbtuJ?Z3}U|306B+qUf_D{@q@OW&oIfAC-WEQM3o2meECB|b-U z-SnK?|3hp~oi6(S(b86tutoSvDe3y=d_iGTF^L3UtJF-!n51Z_Bx_+(g*urqIa4TY zq?(LqrOL?zW#g-$w+}oWND-=-Divy^Q8Udzrn#E5o;bS(+*|7H~4~ z@4o{dv77_~2dG~PIBKo|D8@_5;`yTeFp)^slI^Uqe3Ke&{syAzoA0nX4X>!8Pf`P< zluT5CKmbo#1_PYzxZQ)0?u_)1$9n~x%6&%{PQW;^OUin;1^A{_T(1l9d}`a4nD_8_ zCC=Dbo?vNRYgtCuMza^LiEh?tx>WcdOE(g=N1|m~gV) zEc;P8Nos@LF-slL@!MCif*usqze){&hV=BP>7zxTI^E1pMLoQpRv58Zw7`=-r>)3J zTwCLUcV}QHpUsSIs@gRxl-)csFG`5Py6ZlTc%9cBMeyYrg8S~y2Xd)3p5C}|lxEP@ z7%B-p)6X~-Xr5i#@_n!N_gR;(8qOy2--ZtTT4yzMZ0_3X0#C4?-)Tk@M|onH)LpJJ z$?80WnEa5XoC*dw2~vZB|KUc4n>gqQ8`K5C(TT`>e;KU`1OOMACdX-FaI2b#ZWpN;e;Z?L$Pp zyj5G62-Ys#Q3g4b=;wAubuc5WV1w~}U7&U%x+~+h?p~A-*feti?>zzlL3xx@l}1ne zl~Zb6wxE0Vu%^}S5-`Y1Ak&lyzNf~I<4}Te2$inwAFWbXpiFP7g4?(7(UYbguq$aP z2?$x_>OcdN3#AdmB@2gfA*AG2T>20B%tqaype7}crvRkp+4BYf+ ziU4zrzA^3pZsPaW)n7M$mu1aA7FIU7L&XR%Vgy%el#_|zoQEph*pZ))fP7L9fzh3- zxh?%hoTlirwpDco1c+c)YxGM*FE^(_xIgq)23E<`yO(Q!jD7 zA6Uq_?$xo`4-)@NUnhVlovyA)()zMZ6u>MLxHvuV;wO-0 zQ7KHvIRs5Y0R8ra?p*ZWYcG-~>mezJ7u2dP_Jd;ACQE0Lh!FeO7*1a*a*A%8uxn*` zOF1$llYn}wK$ehVL+`qzh2z-~PTNz2Q{{5wPAjbf()YneTpo+eoOJ9U>4l+RAJ~zo z!lKii=1zdlTc3p2UYFtw6nc4KpFqg3R6OoXan6Etb^tz&ppKBXVAoQKuEZ~gcSgZ|% zA>!wY>hFx0$JUr6f6e*Ha+QAqtYS2QuQ*9XIata@8B5&W>zJC3Vr>G1@D9n1v7UCp z+6ZsC&@KI$)2Hy9mJ|aGNggkPA3Fd^O`)_62s-OZK!Hb0rc5Hg#I%!fbEK1 zEJ#Bpd)}{4OzxF@3_7AxWzM1Y@?cV$;f|Ayn8eo}sk?a9P?Z^QQQuXEVbbK}nQH}w z?^F))tPV?lBg&|x--MN9>NtwcTtW=MVR80Ru3V8F2PN$g-8wPA!s^nimg5gO#}U}a zEj-CC%ehG&4>?#eZqOCq4`U~5!`1n(AM7Te!~nXqEGq<0N<&n_MdA62w;Os~_BHj5bO%{h0MfUo^k4 z*+3R2gdNzj($(*}Kwp$F?i85>kIK8p-0py2;`2vu6dIFJ7GXa%QFiv)6?+ztS!UwY z(jHxr%QacjUe7aqRmX9*!2zZe72pQ=!1_!CNcI{)0{BudxGuxvI26}A8WU{x@zCA= zVRE++Bn&k4ca|=%JiW0gFLUBZs60CQmDyAvg+|1xy_v+fq}@@dI=U4dD*IzpCsWpwc zi4lOS?!d{2X_v2VMDI#7G^GXZA-3qjUmsrHIDy{M{pcdR;Oh-%tpT&ntbIg*q3)0n zSemoQ473XL^6AQDB`LX?NeG88^*QCsT(hYTt|kU-k4gFqca z!Y)Ot$ve7qFrK3w#3-~3V0Q}>$=Ks>oWI$%F5iv2w9GlwtK|)8KdY>FJ8Pu+({He4 zp^a8UPnTPNIu<|or95fbG|ktgE}!Oj;tTnsVG%>I_MCUqhT9(!mbN`R|2euYyepdKN$BDI=V?vx5Pg(GR*UiysvJ>Gj3Y zzgujM?mI?vkxM@}tF49k6t!aWoehq&Th0f5Ir8sIjZVv0>BatmY;8~xrSHE`kYHyg zL^6oNA0Sx^TJlIKyyPA_WY30_nUEIWd_BYbh8wPYlLY%9^(#2+3{SxY?}_rq zdAK1Y@IRi_3CQS*ft*NR^3L=0yJOuUgvzJs=iBO|bXyeFVM1sx!0{xUu8tH7(n599 z24Nj$YAoz$g2Ho3&__DFKYR%`qe8nlqZ0#c}$f)o;OqLM=C#%f$ zHHJ>*8CpkSDt*1BWXX#Q*oG;D(R9bej4Ol0bFCCR7O(|T`O4vg1ICtuG>Dn5$j;ih z0PE5rMIYR7O?pCWmeLqZ2J(6T_p+==bKE+ZGPW9`u^?j+Bq!9yabY8-wMs88rZ*cy z&FXTnwa-6t*fUMwwf$icDAvjk18M5q2{aY(M|Eh+3;6r^0WRv zCo0@nN|g+ZXnRQf!NT-JdRs9ss<@CwOWCM=>ZJ(S^W}geq_HL=T{$<@^FXi*+lb9V zgivX|0VWn$a|;~=;F9bbu?U&;0oHT~VHZRz^R}!3PJGCO4rB2}ozvSTN1`Je9P9}7$7z%kTQ zBCQbb-(!71j}2p4upVC{l@|!;8RcPRMY4z{k6|H#CH%exa|!O*hxYs;2ROtJ< zE9HPOA7=uHbmNLYGY;|shXxL`J;_k2?u@<|-rHYsPY$-^lP&kcmrqyjJygEUA5rka zRRx!%@dB;UDu`Nzko(PS;?x&@LaO`5;1ODSawa33!FqEhp< zwg#V6#io_VOk8Z*d$Co5stW6UKUCM3%G}v20lQ&`y2=VKisoJ~3EQVITJ7PaaCUFy zeW>mwi;w+%X~w9MTnZ<#FCXl?+&5Q6Sg4_h>YTmm*-5b;hhfPST<8X=i{PN2zOO|@ z1`Cn{h=s^RmM?39RtP=22--qj{#uq>MG{M-b4c~p@ zE&^PWoNeFIvfCB#qH4;0C!i0mY4d?BK*r-P{zhsC^i!>jfewn`#-VHuj&&3tdJEsF zjSoA9+xL+e@#i{Mt>y5tmdLFw(Z^b9mG0{>gi z*Lbb9{FWR+tDPI!kb__oTZ+%zD7|&#?5i7Pe{S&ATCf5yI|LegffwfA=$gjuIeasR z8?;s7W&!gi58TQZr11a^!$0~7lGkLVP#9=lzZ#YYvn(qHnb3uPP2m~(U7|dtNw&=d z-x{iv2^gKPi1m^yD_cXdM*z=3B7WfWxi*<2w~NMMEw1ycKPtu|lPnv#o7GE-@F-Mr zSLM?_uj+oVa-J-rE2Xd$Huc{Eoc3P=t=;`f$(mBM;fPHht$=*uT9QIj;OB0 z>7YN_Ds*aVGbY`ZW2?&MUr!aaFJHuC)hRq~H|BL`3AVifbm!3Trn2rDI#-fjYyQc* zJ!xY8{Y)VCZ1*D2!ietbK8!WyT_=laq0aKpUAp@OTC$aLbbhxCLei!J=C-a(PMuW$ zooeg$Q~=xbL)zR}tWYJDeHL}Y*+ZSTmbu9NP|;w?xSm{MKH}8L`3jRBdU{>el&O?s zaJ0ycl*M}dx4e6LRGDoK?Z38aUtA5;3+b>rA;Gu#;W0@t%%LzV>^J?z>g38HaUexlr^* z#>=+t_Ef`Ps2z6{W(aus_rwO{XZmP?p4yfkZ|Dm3o%MfyHg1~$WaR-yGC zPvo$qxUMHOo^>+IWR0J&Nmdm66@qm7pI?1cw9B40n)4+Kk&4&R6g*KEJuyC~N56gX z&sh ztR~aGOxW=6Kv*YKr6w{r>(@7Zgqwh(^PyQkoOJxt(s4W#j|<6G7jg%_Z_{dW!9<2N^C zle@OVQMNDTxsVxOcD(oH*0o1Zb}czbHLgzP!3C97mp4*Nf=P9{tA08;RUdIG@N<0O zOBGbgS~B1MakF}cS=Ej>uju#uGqS=2Z8~G4K;fGcInMrmWB)G8%D&+nipF9J^dzCp zuV!a?MaPrD2$41Z$qx~75KA7#RxnoA;$DULe~*7F9pN7Mbet%9+tTe^wb9uCg$p7l z?sa)q%}>x1TmX0cUkJ+p$I5npo5megZb-7GBG$ogo&1+kC@v40CpNAieXBdVEn<>7 z@K(BP{86F1ljaG1ey`Jm_xxvYfjn%(M1+j@mhGK0!44-V)65+AEs^Sx&C_+zSk8&j zT?07RvZC(qDo|wtnarhpXkjK1#*fN#z%{iuhCf4beOX#O84 zYevc_Dv^MmJeff+jva5)n$N z64Y0HpTjJxYIVVOhYx+vFdHG9>Wcs-3ypXRd1*EO-HRrx z^xTTz_gAENrsuk|Q*GomzKtNVr^JE|rVNl2im{;#mS;$&nu)!5TqW*Hm+I3?;&eyd zDeGo&;O3cC6K{`yx3$?GrO#udWc;)kmO0NXQr(rCmpbGk#D{g@^$qV82O|sEGy8Le z90+{8*23qM?g=mb57aCahpO%Pxzu~Yv?&aM2AM}(vik7KPr%BDtS&s`Jr9l16o?a` zR^1yRk&xIhiEKS6c*@)w{Y%ekTU_l~oC%wk&X#$|ta4@Bt*JPuAvHw?50m#KL5EHU zvJT!Ex~~)eV{A8k+9P4XD^$&EO*L?F9T-cYJDha0X*twVdC;RT_#Hm^DfT_qngf7^ zHID8k#bj-qIB5{Ak2e~bo*K@c=1jq4V*x{E`SRDI7s#NG^{~H=%>oBW4XWAWrX&=O z6X%r)NwxM=K|eDCNA)dJntVD89u`h@#=5#Nldz=&7)eHa??oV)1oY(Nx>Z`vn5-o~ z@01>LcI}c(nvf--bl@*f)=leeYlk~cy9LUSO{2L-pghl7=ZqzUpYX3!h5K#Rzsw*<4x(AgDXLx{wt@2*QWcbNf3U8eKU%Cc$Qe z0B7LNM}S3^q_zx*Vg-*B#rv&+wmTESsj_)4p0KJs=vHG@|MYuK)+`UvLT{QxD52u>rNStEbq=tn3uQs+<`Z9 zN6CMGJlOin_a&rcLq`Ih4Pyu~LQF(<0?xUN;J{4Kl>f~Atd=#doVIeh-1~sGkJcYL z4kgE-$(rkScw)oRS2wD5o8EtwTP%%sTzeO@IIpGq!X8$TwMZKj`H}HcF27C*PRMbC zQbRZi*wN0d^sWB5WD#L=JYPytlmPXmX3Mkz^@t2rq_fDRbOeQ`Zgkw06#&(BsP|pf zgd|exsS(=?uyGO!S}UB?*nBDNVxeUAXra7?jwqE#DJ|<aF6)N8HgE1S~!xZEV>F?a9uD9wKV^9 z4*z51s+AjUlH?6p{KYXA>84~?ppd@IrY2$gO`9Z845WC>8Tzsp)pg*+;;SO!G(b{2TGO_e7$Y^RsFwJOofEo{l)u56He9CzBbGZ z2MF1dBM%2aEC2pVmk%+l!1+%%vxNP7VM&gCDU49ugl2%z3yzPBS67A>)p-q^kBV0# zwa>iFIpJrw_U^}%bTKUdFzE`-)_`*5OU%q@4`%|n;|?s1@@1sTfzTX_d&)}~;Yt@$S9#L!E@!DohwWM8)I*>1SUlI!2H zr{Ijqh>$pFtY$7HymI#YC5Gyst=CR}*E#p@$@Wy2r(j`IQ{lD!N38u4Q_9elwu51K zx*#85P8%4q5=b9Z5DW#ym=_Dd(zxS>DwqSM#iMu}{e`^YH4Ki^hgU~OgXnyz-G9TS zteEE-C|#Ex)`VKTUymzo>;n4f zDBepxQA)X@B7wvr5V(o3DP5}jIkRZS=knFxzl8DsuJV^Tg|GNi;*hewxkBmDw8ygF z{k)v!Dg*BsudTnI7C&yjVE<@l{joTaA@2pB$UfI~;`hLR9$>Zf?SxeQn*k?w5NeM> zGjihWr-C1^>UNxU5dQ?XYgzCm-zZy zQX<7jk(Dl_aA|tvnEuh#e=I+jCRRArQ&cf*`Wtc;S|~U51oGr50s3)eLJNz~ee6rB z9ZPCl!y{^oFf`@AHspDb43_jSJ-@(JYWnrMJ0>Dnp*dyLW@qe!2O1GadXA4(HaZxk zQ08Ualm64hvEbvCPFSY1o7VZ8c)y>@m+K?Ieb=8tg#+_hCX9FUn)jq_v=gHawoO?7 zEO|_u{(g1I_#DGV>z*)kMa0IycpK?w-90Lz2w(`L_H3wT7*f6-mPr!Bowr`6K|iFr z+NP>%DDZ(d(de01V`)p#BW`c={+|2(X{XFBu`tpy? z-km=iHs1bzrsrs?Jnu*AolJg&T*rzt<|t`V{o!3X7ud!HqN;P=VOWwcJo@j4ngPU! z13o0|oNk=o4jVa4&TF4vSk3=$RpS%Uw?IN7rCARHEwCQW{>Su<7FtGy!OzEVT>|iS z_j+3!mxIq?ANv!Dr=zjFn7*IN$j|>$i=y}Z1kXF*i&lqE*d=6b3T_Q@(JVsL4b>X- z6K#1*lI=2YD;`ADL+BiYc$bo^jgZ)mS|Sx|UgA?mlFb6nxHqwUF3fyRnaDxZfQfZ0 z8_P+YZj>C9P)!ucL6}JVjx(phRzi72fw^l(%DOy=!Ge20@_Jl~A!oT$Z<4a;fOj5b z5|F5#o=W3Es#MuYOV0y1;LLPfDXs5qi24?5uW3yN!o(vB$p!(iVq{vZa*EADiYi_H zWHDsLhJWeem6_XRM8aXq}tO zHYTDuJ=}~gZZf!^liQIq8IH^p^O|;L*-4Dp03%BWD>xb@#T)cE-lBf8BNbu^fGOQ{ zb1{T)?c68aO&vHULP%{i7vmWI0t4>jBq$}MBgk87%~sh};BbG#i+N?bQNF#sWsN?jZE$`(oAuMg$3F> z9mDN8)HK_`)D}382Q#>dhQcUYUuT+8umkL}Oh!WG-;1sU>IxslcE( z@_s^tkeo6^HR9=hRz_?%d+nGqSJ$XE;!kVQi~k%tb5s^K6#xr5va+#Qp9xD-W$8@B z65UXG-%L1DLZBDz4cnv*mDb%aebnK;V@}I}kB3G<2DNNglulPIVfL4~;4gV4r(=|Lo(~%|#h!)w<+;2Pr%;Y$pJ+9Ar7L%9$ag-q5LcC0h$M&t{y5``jmD zE&eo~&k&tEGE?$-h%aM&micqZq4TJW8NTB3P>DJY{$2}=huMFEK-({$+l~cyYtQx0 z6jx+u;zjviZ|J)@aq~DO?|+r*RTX4boZGPFj2gYXwf2n5u)2BanKX9QmtE&_%F7?Q z@Vmt7ml}=jgs=ezk!7#R;FLKCRve)ruSdx=4q_mHJADy^T{_XL80RF@%3i;2SY4Is zU_}K=aWa64G)w9WpOHtp;X_+0)%HRbptj*Y*@$Co2`ERm6pkelZMpHqhOq6ZvW^fb zlDdH5A~=DA1($S*5BuvEPiMe>Cv9uxVP(E@DH}O_-{YGOWj_I!m)8Apzu-o@qyms* zrL`lH_Se28->u_^Y$V#}vD04Tpoa&}^}`aVoFatPxry=bFCX&6kp=QoO#CT8BrYdAO7Kuu`KD29@G+6Q$h&jnzRjmUCK{)~g9=;!cN8OyG(zQQ-J=ri1p=Xhj>mJM z7eUv~NQ4)o5R^%ldx=~2>gu$QK>FBKsXoS5NsuH2>KqLk9qAV){vBSjm7@`_iY2NP zupqd$YpnqLsu|q}!qyVT2!NS+&E`UjNgIf(ua`xJ` z2Kx0qA91^G-84|E2{ty#e86LP>{8)0nfy;tq8Mg*QOgLip~9Wxi0vp$*Os*rf%inK zIb~Y3Eg7OKEsoVq^wS0t{&y0nh~SZZiXV^NxUm*ldbGH-*qZKU~hhV_2Xr|K1^@t=~97 zlL)M7_afBq;)FWz)4L5+eB4(93t*D``rd;>Y8auB2HHDF-yRMxA}%_gFV}j^ZVhF zv$%@Gz3O9)W?yyoA11|ay^F3}G8=ubcCgdw0;GP=nRI!UP@ryM7}K|5U2lC>%V7C~ zQ!c8sX}Ixe>A}Stw~s$w9n3f(YgDU##U$}zCsQTLnJ6{+U~x8-G^d=f?uqNSz72gk z{oBm)*gci&NO8y22D3D~W*?BVpHO}&kF*(R05vDBxthiUTM;b#48E4%(olR6!&ZNr zPqO7by=p~Dcr8+UW~rY=N`@ZSKLw5)plxRPtNdOs8*QD(s{mZ zt>!1#q{2aWUJ11XX3LgTKRYmFT0X2n zb+RafHUH%?SGXS-8{!`rTh_f|UD@l6ny+h-ccreA=9X_lx`sA<7(+gfZCf#d4@4T4 z1LYM-4FY3FF0>5yc8ZTE(S3JdD01eeTW39)od05XtSCm_ZPS$3qHpTm^-S;IOGP$9 z;RYWz_jUL@S$=uie)qdN9e6nh9E~Xa%B@@1?M^|77@&rXj&=F1JLL;)wg%30vm^v^ z*=Rpce%CbMb}ppzfID>Z+52UN6XoX62U{CcGW*G(tfdHla(@1WCpy#LLFx7ZP;cI6K1kc<_bQYWPYA4gGVSEzPfLa z#By@wTd3U6RK3r-bkb0Rk4rFcyB6uS>dTLIOGOh{V#m3%zb^8z9=+?!KZ?@#K;x;h z&u^d8=E+J#5bBC3$*$a#%Vz7cOQ+k}Vnpy)Ur;&M%%y0Y0s+(z8Y}9{3`rn zkSk``LKasUGdJMjPEZJaq4WNuE^V@8f0c)jXzjUmU$$5Ph`6-Wr+eq(rlp_(jMAFi zAG!3ox1ZrxsqCY*A<|#OPYXY-e+yID3$L#Kgavc2R()su$6-g$5vzYl^j!}!46;K( zRPI9E#vKd)paUzv5Bu(5?cA>CKc~$9ZEUwg(_UP9M<@Q^pQzmLQ8M4op4T3$^Nafa z15uGtt6b4@G3>EC& z1PS^XLK%_8ujun%X0j^3>$$#Jdk)#B*QgzkUDlbK%$TKZ}3=Edhj} z?kV7Rpz0J*ILh#M!P}ZEn+hvd)g&pu4ek5?+9R5(?iUzt>|Dll3F<&nNZ53$3+k0H zGkP25)NYl|08WBu-;6OtMOaxqK6KO_>y0=i7ce2Al)QOLYVe0hlMOUIT=y$(jwwc7 z9;iF??Gg?(1Kq0WW;s@(tR5-AKd8fe$L;;+v8N0@D^Q9OvhR1TQF78D@AD1Io#-rS zc@kykWXZ0xiE>xykx!TWg3KoyfNIs?kafe1P}-x=5L~kcWJATsGi+ST=cnh?)wegc zTxspy8~UtW_;MSfpx~hHKV~L1Rt;9%$$y;abY$Z2viP4yK6Cyl!w~nPW`1fawFWk1Khm|F~Cr2OLBU|S1ZRR;A8Q9dwqkz783~*@l zx67jTeC;s9-CSMhp!5EypYNb{*!bq4+DcaX@METX*YsL7h?;{{=e-eFT8K3*=8Iw$XY|)g8??$l zN&8TIKfzCH34`h)AO;EGSg#OZ>`+1fg^|d(y>+FL!!SX!w{|^g3aBNL%56}j1|g~S z!FF7FKWW5M+<_}OF6$PUJ!LUw0Lk#n~W}r zIrpwZoB>554sR?v5I z{*KDR{v$V4aj(6}6CS)T4#N=xkNQfEtJd|XZp^l>y>NkK*P*U_>G6<2tVVgFGD*s4 zlCdZHH9y>ey+4?cR^;><5>H<9x)Pzow+-Nukp@}ah3dPeTgJPzTj1`eP z$Z9+)Aj?!y6Unx1LNY0ZEhMC_7SKXYa^m_i0OBBu4wXx=gOC8aUnU%-3GHg{Clgq7 zR4tq#g_mEYZMa}g`<#X8`RF0h9l{bzB9^IKh6&-_RJzBOV;F8CV>xnQQbg5bcLe|5 zjErD}5Jnv(IURC`t*GioOgh-F2#+(F>UL;_yb&FNFL1B}+CfNkxSTra-N_*pP9=wx z6fg)N)m4iref=xXgU=o2FV~86Mw#i#{CX$)m^^8TN@*8Cz^0At1U7ZO8 zUA7xwos;`y@4C3U#qkWzEHa`bAaHR;jkVu42&1=f&!Nn>Kmk`p4GI(@D-I>4?XUK$ zOYSg@&dk~PFjoP;0IBw}Ag~m^>r_@P*kC0}XbG&k=X=8|g~!fUy{Z%F9$2j+1kj-S zWY;!&5{*gC(Wud_EiTfR;!)%Bci)|6Oc6^Wo0lwgp`i#yov_(8sO z?ZQOK`xqj_T*$}8zejY!A9Xd47PX9CJoRik>D{4gw zetKApDD(q{a~12Pb9OknW8;2oA0c^O>I2?|^m~^zjfqGkVGwCO3!dOBWJhFo;P#$` z5A>l}S*q63x?4U8>qw^%Bd!O@T$rfJ2K9Em_ndcft6B|RfsIhBSr&CrBq5+PkK%W; zBhcI3O!{l0Tr-!7koR}50WmIHLc|uulNUCSor-CCU`#!OCX|KM$=$W;MY?4DfK@cOXQ>MhJAXH zuT(z^s4=4wrO^awr^PWTH9E4q#>E&b9-;2-Y*&27%$!)ZGlVc&OZZV3@px(N`q?tD z1u+-spPb&l;D0p6+Kv_5qn0sDq4)L*6P~G=iRXn3Z9Dh=&*K? zw+{8xKR-}nTh2s!i`u*0TrRzE4uTi%C$T-|opYU6nH>pi`!V2NG*ug>oYr#d_TS5K zf7;&Jzf`*Qg<$wr$+WmWfHhDY_6$g?m+TX!beJ&xak+HQD&0*3uoeb5@yfuob|{ z6(2lqXBC8RL&2rQ!yhgZ-E2ykPvY;6$6%^$()>gl}`Y(j%uJUH#{{a!Kr@yHVA)Yj48Es6$~f%eDQ#nMX}wH6%dj~1LGHgzfJo}I@Yy6j(gbu{Vw zsXO03bVU3(wR6YRy}F}g8xNt^*M=bJ2CUCRj|+UCvhHoyLciOd{Z&-N|5jA?@3WT7 zKXCSqvE7rXq>fzl{^owTOXv6(+qpG$TjQf%O|5x3Rr??OkGpuqAR4eDQ>u3i+aoVm zAKQq4Ek}dmdtyaBEDSG7B)jj8G-|7qQ0|7>48%0k%+jyM>Q5P&Q}yjO26SBHL>M4S zEU-Hn!HjC>{oFMu7k{E3wxGi^(MGGL+?D}~cUr)PkbNWX&~5&a=D=ga-2l}}dG3xOmc;PY_n@4MwB{#zR#geNgS z2n|L&or`1EIiiqcrie!o8Ih8pCTj#B3@Q}wlJl0CacUYe6N1O+7GoZ)hMILDh@Nl> zU`KO-1XDZ=QBX5=dPbok^kmv)N;!Zg0P=wu211yMZj)vq8*%wli9A9HWGMtBD$qi+ zx5`okHF-DZ@`o03=32uAT-sQ8UWJ1*Jfxz>rP-;#N`ZRJU)j{7ISPW4EENd3QSeQ7 zMc6K0w;VJ+O@7yEEl-8y1likWPT8o)mxV(Tlfh=AoN|YglszZIV)OBPfWwedxf`M{ zKCNK{NyfyoY$KN&r@Ml)dSeRfr%!Q=igDcHN4Vkxu@Gku(mc5RxAJK@W>LRGHl?^o z3U5+0v|Vu!3cX(}Xf5eNO0M>G_4z71W|#&t35B!VhZ@f*96eOAu{HxCS?eY2W|8^yCQiXL%8Is)>Z9!GsX~wP1rzj6z87+TT&Ine#8DQ6=S0nAGDEM z@-fy_z*XHwr<3RMjEX|mFEa)^>hPw?2LR`yqlbn%jBGu8A;1MP5CklHM#{XYmvGYW zNL1#WC7fAw@d8RH-75){5ZQj50SBt^sw;3plm&hTBI3T`Dv!R(^QDBnsyhEk{-75V z&ytTM0Q=HPAt9EK2jAX{YgD}uf2e*v<6^986%*idwp{8dy~s1JUGlwnktt^`f~lel z{xoQW0A2H9+8BdcW7-hml7z}#UD{Cpfpnt}a!j|VjI$po2c}$D)$Vdf9FqYqoE=sg z|8RNq=2EpS9hO!T;Qw5?y0o{~*L!;0;U8tM`HdW!t^m?)2<}a@M-00HH3HmL2pIg_D z-D*7}sdl{8RxhK$bJUo-^+5=!|Dkjqqmag^w@qZaZ&7KDZ6dYXPnX>u*n0cLvD<^U zWPR?*NKR_gqT3@NOwuGHD>{OM6qeO%vMlp zV*6LGj#Z{Y(>C}VO}Khizs?7ero*}{HHNs|XP41(=cT*;+q4aU5{=)X?b^1_mp&y1 zp_#%Ip_&D^6s5{EhDO6Vvt%r^2G>S;Ld>OF*N-_{0&PoJEx|F$FMSul=Iri`a_pTPF z?*n>&jH6;8G%#DHB+U`>tRx7Nj0XKq+^_bI*A?Wml>AG0Uw^$?7scsuw}4BJ%lQhW z%wKjn0EO`3_6x;;`O+2MBjS1%@!cU2ZHks08 znsrD7iH@g3z;FZ@KYF_C8Dxb*R$Ta@8`GW)jkpCUTzIw!JKCTFkAdTyY`@%r{uDmE zlKnvS+%uH}NcCZZDfsDb?-zOCfeH(rvR*}BUS(Wpbx`AFWY@DNiOVngnrU#YLM@pL za@+Bd=PR}wD0sY7-#Fm0`dR4wQ&sE1qqim6jtArX7tK1A8_Xz?ZZ9^U8z|5)9j6YT z?HXKtZou&CODe~)*!xBCqowC@6ZYerXM2y_lRbw&t3yfCphU_IuekiOjPdLP&uOSK z1Ayo+M}QW8`0o(eS!^xOw9??Ey9tZD!Vqm>=Eww>P7V^tv5U?)_bT`ptGF_RMl`Xi)OkEnlpL%?PHs%aM`zO673IkMgUzP+YU^HzpG?zIt&ix?gSz2QgvSIq;h0biu#YxLbXogugN^nMFS_ zZWQ5~9O;oz0Qen&7IZh85Gc5CjSUm_O{tLPSWj8$-v1VOAmyML7d)GE6ZwqCm3yC@ zoE*nQ8}XJX40D=}pni3k5hM=W5(=%Z@y`4KS&6;<8!QR_epB+RBg|L)n;y4H8~?Hk znbqubqT5OW=7eWt2cQKDD50jSrH1|ktevA`>Q|S zt|i%{8ki>8jPL8-aHxNR8Zw^ap1)>=bND~+>IS95Z{TAmW(p%ctK_W-to81T?x)tT zIV#V-KIe9-gc?Am|tHMk#b>edvlSbZjM)v+BDkt;ToJpLHd z?J;6IGxT^i@867U$aYFt;c)Up$i*>Pd7d%L$B&{x)EhIu#*UwzfP+PXUaL& zK9Ot6o-P3=>Se1@_zK6vk(V@-@KL}-e!Tfm$^!O0@Tz^LrwZVvUF@tqR8R$HThhNu z*dIdH=dXITB0#*JnehMEd#|si-Y#tSS!<;NfdBzQHS`XlcTH&0)X)?`G*pph5JV8v zgbpHvqSDlWAVt9diUsu-s-UP~K@rpd7Q_~Nm!0=J`i}M(`+Vn5NXAMs?|IGZ{Q-){LRO7H~9ALZO*q>6+p*cLI(U$hp(YeXnpGx5h^h_}lj4I|fM89rYugnuttg%PYDN=5|HZ(LOeAi%nZPP8Z|(X7#h;dwAbwR& z-rDV-bnxmti|ys1{in7aNR8vum~z)Q3VBpBC_$gPvR&g;gb8JXmBo&hZOqYO8;#YnGlK8z#AvP=4=^kpwC(oVU1@2g$YR&<1 zTozxr5=OAC)M)|ATs=MNPDu1K;*1{D_jf$wr~H?l0XA{6lfNj;p9yO`lKzW*CWBK4 zK-U1kVty8-VQiNxeq>cGv33wRf|a(#O6F?m0Woh|plfR^=&Cenok$KqyYk!ym6 z<5g-7vag;$ZMyRHyQP)meZX2|uShD7YY6)jq)JK!OTfM5OS;#`K?4A zD3B_v+!#G} zF3Lkj#$xN3fWw@p%v|%WCX<9y1gx^jZ(H6SG!|R44*~0mmtvN|)-Bv?CTlIkV6Jlz zjut5vr>&#`uN^sgkSabgN#sQ15P(M9#W)M&(0_l2T`Xx*vtKtck}g~l+|xZnvYexQ zn%cgKj?JFTk@B0T2-vfPUC*MFo0&NrEM;lHCMTFYVf{e&m0H@!=A)2|$y;hsIv^L} z%WAW0!a17mLw8-rcjvl^FY#Z_ri?=j9fKwVF9twds6*oN2lfyJ-M|<`@5lE|VvP9i z?7fEL%poaTvS>)kn#XQAB8hL+t4DK{C|Y?9AN47iY4g|zSoGjnvd+cHt2%{kcHk)I;E-Eu^V7t1ZU&7Hk2wuxCEba^|d2cX@?6*Z4B3yxJgYOg0c%@51^D1 zmFXyJFZQ4W@zJM@=i3U8h2y2j)J!p)WqKZMMUfUvhdZ$llxlO~#f8$HnH$7fiNyh$ zIcvQvV#iZ`Bc+#p+-=Ff@cP2rn7e<{(L2_B>yy$h{W3!+!>=*_dWM?4?`!q;uZ<{M zcM)D%iaJ@pGg)5p+K-bpTo`VyKR$#usAdRIkAVn6S>|zpv~(LS{hJ<#Feik*chiMF zB1nx9Y6#lri8--~}fkjY!=ISAf`{&4;K(Hw#sBfi+VJ4X7HN+cPFE1rgnaE;oYY zcV`c%Fl%)B?sJr^cMw%>aHMQ4Icp!mm{mM&@Wmd;-3DT`j7H8cVymRRSCsSzgy^a2 zJc#Cdx~=J-+C_D85d~J@J|F%!D%X_>$~06^Dx2jXAzBWkZWh<43cWEzL22G1;S-9% z4)2bJO^6V2r~kF5lV`LQ*-yR5{54_Bx4@PR(2CSte1;G$n}Jf5rJW~@F(DtP0lXA5 zN4Ava7^8whyHeD%%$n1+h%0O~Qv}zz1B@O+&OL7OQ08HP`5IcZMw7#U{DQ&^g||Tv zg^lO3!8t2;IDo-GheXvB7y)cNKkfF$hVbjDJ1vwpqg;XU3; zk&JVOQP%CVbh<={_$%cK4;|(~m-1t4T%1d7Mz^rz_84)hrI{$Iuod-99=elbyVmas zF>&Q*aVSpe9M5teBw<2*aPKPn%zrj<9=9HcDv)SBCAX$1q{$rl=pcT_p@H?ed%ar+ zg~tzfa&QxuH!i6hJwplRXvWUw;A%GYHkiH-ZBGbil^y{zPuiJ3J04ziy{~#?UsB?w zz24C0Bh5t7&J1Ce?7anB9tOkDxFyMeD8cQ{F0%W+UgpZX)ASXXP}HVf;A+W{bMnMI zgLF-{qMx`<1Ba1VFCN~7O#6WlfuyYzkiEl(#}#Cv0*MjdJlP3W2KUUgHgdoj9*~^W z5a$GX1D+v}VbK68!z5e`rD^_mOOWqWz2U>tUbw)LUII}hPD?i-33Z(3wx?GDjz~my z8g_GZ+Ol&#=cRQYN;>^5fWgMJ@w&jp(@tR*a>Nqv12}b>MifOL`V@Ublgf5}{kk5* zD|F&%g=Fo}DSecF5G&Tob`KFD1G7Lo=$M(whA<;cHw;31u4T+ zwqaG=5+(FIq`*ppz9p->I9Y>enaFrch3jIOaPieTp4(^frL&*leNY0puTC5pRQZ<%FdE~tE9phO6YaxKv~V@<*s7+bo+E$sn>AQ{UEz-Yu|AJ% zK9t<>{rfRvo7b@9`sVSY+u!q#u7_2g?mirK{JHGXr6{Gy?>@x^{@=`$+w9n8;*Axo zA5z>H%-eLSzu}3Y7ZfONbdalgy0D5R^%)_)+rSjApyvGtK>AK_K%0l6c=iAD{0L<~ z&T^6xzJf!Q*uPR+KdOvSC-ykLKDB0{&`h!-tBSSym6HE8i>%|{p(lK(pfBaFZ1%VG z4N9zRb>_~0eMBNpO^2}-!$y45bt!SmHj|J#5lq~Jtly))DM2vm00A>>2ToFgT zUvRe@3iuX29|fC(KsJ1Ayo1sJ^)wpvvEySQPH)j!d7IvD&t_GO~G?q!ecLm%8R-9s*-DT zqzDZawH0Q^A-fGUJTngEP9E&AFU{qaNew^*Uin>3=;~5&F1O%WTzSoX$bkxrv0W1~ z5Na1*5QwcI>d03OG#A!jBSbE=wJ7m|Jx=VMa27!JRESE^I-s3zNz7f73!8`_HLjXE zD3+#YY(nF{d5;nl?jh!y5N_f-$eo?`y->9N4h1;c?~;lZv(Z{NSF<72AWZ^>1Wr zNFT*BuQ*79Z1@FvnF{lcS?A2kidWl_Rfq_I&*MU;aPGX^3_;g$1_x3q`ol{8s!H7mVq zp-J$4{8?Yag@lg|(4V$H(L`++8Rso;=FR0a;5ycPCyX!YeCqDE zoqv81gzTfj=q4Revr}*IR7h5BM8sMkZ#lUFQ$dI&5$RkcoAIH69j%_3 zJ`&x!Mj))WTX(rw1!AqT7ws^nW;=>txydSjO<*rdr1+2mV86+lUH%>0{lq;_A_&81 z4ElJ|S}3MYVKV_^d1j3b55U|!X`z}4tK&{bTCGvD!a&MtiP{F`)|+h2TS2hY$$Der znQn)+KDod-b`!#tKJ$^NZhLLPy6u+}8#qah>~zzfKhLNf8R1Zt8~&PKGlVXFe^&vhOTSjTu? zpgKZXuO8}|LVl%4>eu%?7pzF#W}I!SP{#zxfBV&-eHt$3b*sHO3lb#FeS7ZUi5GHZ zZ!^wGO!*3e^2E|j3J46i-1Fk&*-9d$$3Q52$cm~03HYiJaQ3{Crm^4g;aJ%>s`0aB zd%qHznG$!}&*k3dJF0@MZ@SAG&r@T%$+J;k9MApUD-%$Hdl!GPkAJaDUvUYhRoBKT zD_lI3A}P(N(PzR^L=@!2A7V&F`u6c_{JrYI3DwIe4zOSz+Mb2CI@qJn>j|j7P$1~F zf%>|reH3thF7kZ9jsAep{suN!+I5-2>)&-+0_y~}{=R@tA!bYBWi+qcISpbD_8oER ztp9!W_M3~)9gN8#jgp<2AXk0_D=0Q8_FZIh_J6bXZ>CXI*Jm z?r*~@GkZ%st`%~rF5;yXgjfh+sm1dc2J$EMSd%C6jm$Uf2KK}Y#Qs{YBybhUX#_y8 zrm8Judt0p`8(IdrRl^SS|BN_E6@aa?M7%a4H7PFL5-V+ooD;7>6d-o7?ynk zm_%hG;pmoO%aBEN0ZC)=V+dY;ND+oj*wDPSkpW9z9O^jnN7TtWM~Rxs3{{Nw%Gh{{ zM$@2qUbZZnv1Y5eF^kCjL)b4hhSF5elhbVcqxp<$VH2{ZHCx;1utnV6((jEU zTN2H!d61ES^%vi(Sy50gyJ(RIujpKr0%U-kC@?H3#b@tzK~3GvOB^dI)q35n z(Nui>+}z0V?A1|M3=YCpyFckhL|SglnYeM~)^xTJ7KmlGqc{0^awRvLxD!isjdy2s z6{8OQy=0zZd8=?@PlKKWo>rz)Kxgmk`upDvHHrUiJ1)|K81Jncex*#z3uE!aLP3iz) zUZ1{lW2oDdXo)u$!pVoa99-^dxefDW?NEG3m4}FZysBK-ig7sMq>h@vW-DTgcZW8F z*#5(ogy^WJ49RsIg1V0(M4Hpx3h{o;PKzP7EZI@S-xTT27dl~MJ|rhVcToX&-)xWcMZq2;z3^?^<<5lez-7bxmCpHo+H8|SS zC~ZOOI5jp1Fst&E%EU$4kY=;c=4!oY-z;B%k!U0ygQtsjqO`ew-aIBzrv&`g?a?)4d#SH|_Ug>!|5xm$x^PmP+3* zm23Ldr^8yrqml#lD+b4Tg7@dGV3?u2lox=1p$ZEa#E%S%48yliL-FU|OIA#LSX4#C zi#8ghGg)!}Rg4kq1A*s)DHqQ_a2;B_=L|cmfGmvH>tc}J6vTppGo2*pG9cAwTSJCJ z5dJ6G$&bzzF?uW;1CWxMKxu3OtKrzQ3@DE8PkD z&Id4ZepFinYwY~aIxBxN7=)krsmA#FYa}9P6=0nB8iD>? zd|<#C?lCDKk;r zuc74k(l`lGdggV9|y~hQKV}{BU0Wi82J}DgblwPTI)W z1}K^vC>kIJ+LC$3KwtLo8Ji)H(=s|{V4)0*ZLPFQN)cn2qyq^0d4?8xXn<|Cvry;v zoCAgue0_Vp!`PM)!ig^U0KD? z{U5}Zq|~fx5EY41JW1=&36hH}CjZ;ob^l?&iLqMy)aAMH-qt%OXt@&VKBSYhV!Vlt z!HVO@r6K&aZ}caxNedWP-;R?5K~(=c#*aF| z{T@QtnES)LJ6|lQA5HS{*f3x6SpW1^p$q}w9`Omac!^3wd6lqlG@R_BT6GOKqdV^I zFMCNq{ZX4Qf5r!W+b1shDtiB=ik2^R51!xnX5B30?-jCTv@sc&C-13^IDP7DJ+lUi z>U%MEs;RR)vE*vYm&)JSldIl$sge-;@DextS;C29&#ph^q#fAe>rKR|r{>71*kc3- zpc@_!_nw5<0-UzVbbXGL*=T4Q__Dn=O>wYELGupr;?sRk-@VYt&TBZF6DGx2iIYJe zr9Tpim$x|_{D-!_2itx>X}gnSej6(&{SfyfBID4tKs;-N{@_>WKtbOqWD~RnD0XMdb0d@Pyyrd$ z?!MmhL__Q&5y#FYn}s0C!;>c9o+G*ntOijuNe5!nAlBY_MU;Hy@+TD@Y(TtmkgV3K}lC+D9X2K-x zZ#AMptS!&U1Vwl*NH8M489Ghyk*iD|pZ(=#fkIYPS;e+|8#g!ks4xY9&N}~kbw9 zFOzW9=Y_~2yPjWOyuG?#%x)+8S`NS4O9;{5<)mp?vGU~x0U7q?NRf+}sfc}=aM18y z^_zh@yCTC}f~gL0-{P>6>xzfxSZqmyJQn8_?;x?+<-s<_z18@vzV!YRVM{#ZSmqkPLNYW&p~YrBe5G>-;hAnL7c9`Zn=hAwr)^>)o`f3#{`och}04clQ_34Y{;ysu%-fq$0G(zB+&0}ubDmoASux2Y5 z6vK}yhBjc_YUY+=@EeJA*K;5C8XIs3I*fag0)Wva4+Ig!_nu2I78)z{YCMGulfN`i zNA0+vVV|ROBnM;E2Ej}5o6>%A5e%0jc|@^U?TerCUnTK>FtJG+G zk_JPQF=IGB;L1PI*!C71L{uV^fgVAmOQ7(rQEka+V$o(KL;#&uAHI;c#D=sd*~Dg^ zjy;=$3Ef3h;=5wh*qjaCny=xZ)zgwoUt7uUT>F597Kc(KR%zE!iWj zbr!6e6v~pN3l2tXX`DG6Ic93e%J*U9%9van&{sbpMJydn@u6Yb+a6*=_$DZ3<&7xs zomU*U#15OLB33j30iIl(SNz_LFXCX+{y?EM0*oDloezm#(GoKHZ#FLxo?1nGH2mcP z^9THYPL!<#sHhAi71rSiOSU7Kx7U5MtV#L7wy^ttsj?ysbgA85Sy^3okK@TEB+`np zv$Y%T#(J^I`|hIS09iXE?Z~)sLTHp@EbB-r;Qd+lT*VtP-~hY$r>dv@hz`jSd*u^(kEV~%yUAOw_PW+%%%8rK z((=Qp*#q?Ju^;vtO1V&}5G6GU0iOj<&$PhNcGAYfX7$@j)NdE-XCt`##(5g{FJ85e z_8khmJqxlO6r5KLptjQ3QR8gLgmpqHC!`ptuh6iDOLQ!g@3~fAIv{U)JMdY5ZexDo zxS0^3L>)2;q@tv3li#r#Ke_{O6s&Cak6!Sbu+MDD*qkN^I~lTP#M~5X^oV}w0uDK{ zqdVq9N`lc&!_0T-$|zHQh@l@Nhh)FnT<-jznS1Y@uP%N~HS= z+w}w3qP>|H_GBK8LwJ_mpR$zV=!)l7*r|Hp7*^u$^?mAqBmD?oYV~OU0&_GE-{SLS+ z9pSz)Z0s=-&4^P5SbCseqZk0xm5TIT*G!ovx3pw z;KadTH710J4&;fCkq0(7P;!%Pk|~UoxYl$P8lI`=UcRxs`L`^($S+iAsTfx?Rpe-Ikd8t$Y6U1vd79t~(au5Mqo|haS zqDCOkYs!CT00#kdZ&+m!me3P`g9AX}67-ZTzK4N6h{-B0T+iiFH7j*4!Ky^aS*Y+- z+LOr4fu-9KG1W?9ms!Y{e-1H|5&(!=W#e5jJypI^6s}H_^iqZ>d{{~(VM~RK z7_bJu(l?r`AY2U(<^W>!9@R(=)c_=Mt2!Y_)F3p+|ARh6SvI z&=v=8A{#bhRr!9YH4;>DGl6)FRph$48^Z0E>Z~5sDi&89D?UP^)H%@VDleHN;uYDo zNDjVi!9F*ArqYa6ff3a?RM+$HbuvVAwH*kVSvylxTkn0W#sNw?*^uE-e!CXwyHvke zb7n1W<5@Mzn!xASgdnal3rf7EA1=x}X!@`YLdA?K@yjx4z8YseC z+wUnhW%|6t$1N?4oTIuCj~I}S0B^)|Eda=n0J5OrUvc#@U|&&0D-7Di5C2o@iLx|= z`jTcniTi_zijF_EAQ!1cO(+~}V`z7XM_?YF5OZ0jzz1){k3oa>79f>Ug0nP45-Q8A zMW@d0g%|nl5VcK~4cjnR@M88Z&k^V_s>SYEVnmpMpeXs||kiOf}5nIBK`~>gR5d+=OB_+sl zwrjPdp@7!|EwnlIch5f#_!1^qU<(clehUmnG1x}unXB? zre*+<=boRU65e#U_VHSR-(-a@6FMdE=UEC^p6f#j*smhlOFgHW?4v{>=XO-S_It2&m|@*tY_aSV|N+@Gs6t$k)SOj8?#58Dxgr4{ULKBTmOtO z)kc34aX|AY3QRyNzyyGuzzRtRh1$AiM%pGidQ?3Fs*bUvk%_gbxxE?H!9vT(g<@c7 z?W}9&rf0cM&nD2oVT+9&&DNgg;N)iD#57zJx0<$gHOVhf72>y(wa4GwQJ5gZNX;GYuk(^+bmI5 zcFulw{=VFj0(qr8d2*?|(s3F2Dp`dFqI{Q>%qeNvb^@_iQu+#BatJ3efx%6oF|!D2 z5r*GD5Svq&mtRs?bf~DfEdOBT!P1H{Zgtt=25!ZXs_NP!H4U|O#~Y3{p5Qe%ojlcY zs^e7K>GqD!&aQ5L_qp@uFZNyRzkK=X`Ktq0uMM2NJ@cPnuz&PM@6_zz;K=Cc`0$O} zH*ek=nwYwMd-CSggIf>h?%usWJ^g6<(aht=a}!S%A3mLb`b_w6;pNQ2%O}s578jqt ze7W@I&D#$m(dW-!zJLGm_wQfu|G)mfzXP}x0O9`6^?2_8OSDCCD^c_AiS|;I@SUJp zq|i#qXrEDo`N;wSoP=56YKlB3IJVcXWLoitCshIIOsyshNavU^ZZJ>sUTxsm9=1eF zP)6#M;MR@dW04+oHMbOZTJEV1MiYGE3`C1Kv<~Qw;YtTEZx7lftp~J{uY@uPJk?q~ zHF~O4eB5*<$yk(EKV~BCt@ssNkYgDqtrM`WjcWDmSL2;GN~Y3@2T&H+XS6?=+lLs*QntiYJUU}Y zB{={4=D2#o*JJB`;6owma;$If1_D+#?*2QPG9;Z)4?6@bD=+2)3q(^+A zDnY_p1Bi8%gX(aLW-(2IWLZedqERPL0pN;JM#gyY(R=tSvPGM*U+6>)!)D@`N(MK+ zgVr|cXv}YDnp(U5@~0a_6+bzYmrC@Q&{ZXg)nRlx105Nt5%ZjmfE2%+5VaUmldEca z4TJ+(Qq@?sU6U2d9f&irNSScNf0Sb67uHG3D6MeCf7e>;N!c(rIjY(q(eK)GRbu$e z>1g+jV;$zSi7w?Kxic>kvba-b7oPsQ*{&TcwJT>FY{|Y({b0MIuxfik zRkyv%B|J?s+vpU-H4I0(*iO*(5|Ry!Es_e(52RcRIP_ziBguhP#X8owhWFX0zPY0kH@-%<<+tLIR0_ zShaJSghroW^!PnB$&v0QKZ;4YBz|5qev6c zEw0X$L@8#IfXYLhhoPRHOu9JqUjm%n!5>RNk@oMjQBS>q6p$36sT!8U6wwfTkFE2~1=xeDDa z*(G|vnFaSR$pOYAM8iPn=NuVWPl|g^N~o#wJN~KIzhQw)!9ywgQ|n)zV_H9HUyI;S z60gM>QR>MRZ^b!!mO>k%af9m#CgcuTzC9_~9tR>GM}+^`Y=Fvf404{FmnM;Scte5$7o8l;Cs-gk$1ZZ5Xp!FFI-z@=jq#9zrTd+? z(y*a6_HQ*|MHfSGjqFLIQKW5)qf}z60%?4~-pf^-Z|)(74HszUbvj1AiA+cOSey)nY3X)EKO;#DeBW=}??U%LTuNNc)ks7*v5u+ix+WRYAwCnWIHQf=d3;jYpZ55wH#)FfDtItgg=Oe{t z3ishgB8E!S+ST*yA1n~=>hIGQQj%SBFZI2<%+c9iwxcr#oo^8E_{67g7f**r zI&?cd{c1+EGFTW?j2HLD!j6irniRsVC~A=Nd2r?rO?OyubzB8 z-_}l9D<6+i$J7Bw8Y5FMctgJ=sMv!(N z%c5oVi|grWoFhs$FQWIs1L-%{8r?oBuUW)ViQ6X`XE>Ic1xJ{}{(Ij=?vwPuq_L4| z{k?mr;rp%|UfL8o`{_VC3{G9(ytT}c<>tnra`kp$zYXUY>+Ew#Q#sly|GIarv;p#T z&;Aaqb8WPO*Zztk>@!OohJmx@1_EIYsx8C)(Qd10Y*B=m!$g-*#Fs?qU6a_ec`+_I zNPjS5`M;V1I!6R&iY$_73_wM)C^U3L*#n zhASZ4lm%i&MdI?xI6@W8vDM;_xE_ZtqCvYSG2$R8iM=+T2o;Ilm6MpMuc$YX6*hc0 zmkZlMXhRk@ZiP}oYWI6_3Lcf`FywG1T-6Wca(KpF!U6NtbqagDWa%?N*+{tBqzbF^0wB z*7{v+#DtHFtSrNb@5(%DOeocVNKV|DnyWwS8XsZCf5sbLs=Vb;9Zr_tdFimASWbCG z`M7PHanKj|*w14{6hPg4G}sL@;9VD%eyq2u9yP_)B|@{cs=A&`e4k@)&SASrirMz! z`pw6X&|<@%m_)|$;#U}MA(B6ddFN^kr6O5uIG+W|@F0PW`9{K-iE4&6Hyp;Le#tz3 zY;&cG&t9e;5>ty!CgR@cAPEfoLj=q$9^XfU(z%!zhtw21;y#+Z`p*hQM@)0EXEXzQ z6kj^{1+|+Mg>dVuy@OUuG@tSI(5}PW{kq9C)uq=;&Ag`C-JT>N!o&*lkw*I2MqgC- zDTKpJqyif@R^tnt0hHz+V69(ZGAOqB*H7*BA?-4yd zok3OFB{KfI8|z1JEz;C?Nx`NNq20`FmPFjSU@>@#OH{z7Ft9r$aJwjW$4k~d#hPV` zFco#VVU6_1isc!y3BNzlLeIBKTxZn zG|~QYP#Z$zXYhP)_#jwe$LqyTSw6q3?o8D$Q+LH!)bG=_oQ{3s^L-U$4;3rHIO`aS z-7SF0PCdGkV7dfT{Hu%oxxL>T^XQk(v+ued&cN<+;}1D{xP*mENt&GvRhTyFow;#teZqOZ%K3r4;VFX74Ei~Pf!%orAe?k6YL|eW$a$yXSUUok!EL=WNPzX=mDVpNtcPW>B*vao zMwj=ndIE#MZg%&MUzi<^*n2B)FrVcU{;0%`&+=a0xZD*6c2XO6W6eBC*r^J>QGY#S zY71ci{vj#*WrX-Ei@1^?2RjQ;TLhSF0j@zHaYZ0CXJ~wJ)ugYi%$z_RZAqlSw;~5r zuKZugmY@Yckk z@2b;HX}}$TUa0UW(-W(ZbSs z7(K5NI(Yp=;;K|NokQ8<8U8mNtyLjaMSJ+V^`4t0*`pEJ@`<6uqdlWv{fX@p#4dkI z{vRKhK61(8e?(#iebk$diKVDW@P{65QDCr!tXoHJndr?y6B2@3zs0^*=bL!h8;*a} z$4fNSM@Oi0BRBkRHYFOaFQvH1-Htmm3V3?8I-#8ncO)3LvvapF^jjejMkXTtWa?F7 z#N?g;V$~MiDE~@l%{#>(^`AvvCmwag2$_flM$44=DSi;mF;5nuDlBauubdh7QVxj){2i2w`szZP{)cPS1mn%-C?;#_}_UkqiA%^lIU~HD1Xj~&BQoIV8449_YGPb z3EUHf+gjiU4LJ{nwDLxFnRr`R6S}QY99#?=TiK6|oit0LVdF%omLbEfR#g`QiN0=B zuL@rd58n4TCor7YRfi#RAKK(icSSyE@4;+LGLK`rvnex{f=97zjMZj{!^X@;V$LqY z2|~`Er-VXK{QF-}p(@n3R1t(#AwmiAn z`IH_0G)v=KpeP{yH8w3DVKBIx!q|lp={iSE{DN znpm}HEEB>F%e&gUx*DXp8lF=&dG4An?Z%vc1+Q>kvA35x4!bo=`lTK|ePS5HqI%2l z%~b2W6&2P6*eK=bguTw&oN)hjyQ_0Ti_$&@W`k^C$>^(K2Tg{PI! zFOfnHMq&N5!1IUU70K>X!y!txuUR5Y?lteFD9<<@^_}j^lgHmA^53|(ET6T&*;g)a zhQTGxgwt}5Bu=;c;uFbGQw#=g?n^yvL^c@XwLIPBi~4{xGBJYQ{; zl1!g}?^=?+;kDI<#(y6kpg-LI^}2QcyX@hcDv*Rz4bwVbgkEfKi4Lq*) z&Z6MIw|%*5pw3~yW;{Esvwm8E*jV1xOo8@B8K@)1RJ0Y+vQ>&=neYCt^WUp4F@m4F znWo*hP5II;EGA+lP|r{J8uJXR=pNRmtLz|;z17}x4ZUUXuvHWjvo3vj*QD|I*LZIW z;++@aIvc}>%!xPu3*T!c3QIq`7$g64C4Z}*JJe$z?SVqCpN}?Y?CYa=(>((0Rjqk`WunE_Kd~1(X|8 z+GVRrn54HqES`J$#EFDn7m2j#PFWbe*KI0svJu^|nnr&$u6D}g#@LSEw(sZcc5Eb@ z>THtsJjA?Y6g?J6pjeWA^8mjuVSXrjdGOGX77?6tm(&QFAk%K)V74M){M@B#FZ&B; zU%Pz*GT3P*Szy?kvhv`u)%S@ahSrCzpg*g1i*myre0TJJ@gwwxmbH$V2B(UL?%yW7 zHriZfksF5=auPgVX%WV+y4&rX&?d7J8lBDj}e6G zFJIGZpwjI83J7mISwE#|$^sD!;^OoWQI8zvwc7=`m;8v!%Xx0eXL>MWQR0aJCoUTM z#bB2y+vaE!9{gfPl#`dK%w|N+q3!xAh$_)8Ua&;~pq8m!F8|)GxqNjT*MROG61sRu z`cQLa!Khh6VmXW|V!4*gF~{>=CUVw0f?=uVJjciO^UZQOl2aJFX)liXfBe(DThMX) zqU-jW{1xKR>XpQ$M&(Z|)Oy-doEuwpPT;peHpvg^b=Klam%Y^R8b0K~x|mz$gyB%= zz-t|!a0D<3Z2A`dymW{{2!ZW*oYg2UxUN>%6rSla?OZaP_dv=+f-JPO9p!Nz#wyf6 z<*Im-;WIeV#i2H+OP+t3CN3#2{URidmpQU%4?#(s?oj7VgE`x|l(2<~X}XFlodXL) zhD>M}GxoNVtNXj9ty$_!qQnIIG+TZ+?R9AHjjIqlT*02QKItPaJp?MfJTKi+6B)o! zeQ~I2Yt21&NaQ|cn9OeIKV~#gRlM8&P;gj2AcCMrC*w9%Y;=@S%1JPI?LvOJqRdV* zyu4e+;H7<=UT^%gBeUyj?B%?`Ktdhm=xX_UQ6(YV+d)AoW7=F74AlQP>%^E+g0sE! zZ_j}g7vp7H#8z_gCaK(p0H-FKM1iMIb`&^y-roIi{8-C_<;Gc?(@7I+&Hf*X&c&ap z|BvHmSIjnZXNHl>Tq>8jU)q>k?w3SLb0?B(q4Yg7xs!Y7HkVwA8kH#3+%KtwB&inC zNRm{N^4sq(*mllipU*k(&+GMkKFELY-IAM7uQX)xgX;aY>b9RznC!O*NaN4YgA)VP zMGCr*z@5EuF0s2`uf}>MOHv~)SJ`lg$0Uym z;0z|sk<;_pZFo}%ztdzS29`O(N*eTV)QC@3B>pz7?^x#@FQ6l;1;Qhzd`aV7G7uIV zBw{X(26iF`gn*E6Ysf6HBUm1M3ver`n)n&RY~u{ONkS)xXuzVbi0v%2%SVB)F_t9< zi^OrR*4ORo`J$_#Q8Oo4f~Fw#w5^bO#9l&hDNEU70jUY3V*)u)xx;F>y`0`GvAzsF zHx-XPF;X@)GU|I~xOuqnUOE4l)#j>M(9~%UYfHThY&8+tah)U6fymq^1c(H?QPF77 zW@)UO9~cL)P>8tMecJ>FKgfB4(6ENLJJy)qtvn`}X-F7HjtA7r-Kyx%*gk<^+EXze z4x;Y+KM84c!0^XF0ZBMoR5ON22<1R!&)g`*4b3Y^4%^FdtKobThT=D!fhR;|1S4zm zYoXzjLp%QKdee7Q_rTh1+^%@!?gdl{Kz3GOw&<}pb*lIZ9t}YZL>>?CH49O3O{wp8 zwe1cgiT)lo%+hz+Wp0n38+2wM(^x<(pPX#^5ml2HjemKKw9zEwyvs3v-Tl?9-UXQhHHQ4q3A;^;%A`Qz zGO8Tp@)+1XYYPyGZd`_`7gCjHjj8hlsnbdpmNvsqkvT3fN(=u0xK9s5l=M(r*#SO_ zmpR>Ps4#BVbRDl#1ExS^N@^}T5f`phF2p#{9(Rb8Wi4y_V1NCklK@gs!R9BxfDD!0 zl)O(BB5~6!wc`OZ8wtYvowNp95RrG>iS7x_ly+;pS~=t8AfhZG1s}bMc7P>lz_WHV z`VmYSAY%*1l%B1I+d`V@=QGlZD!)Tc=vcRmS_#}9;(7{Qzva8QHVIMrj@=6Y((gHS z1((k&!(w0xaYIM{;FEyA$A^6^Kb9QLk9J5f1~Dr*0iEAPs;7Cq1TP%Jcs4*;-6snz z!B4>>()Jq`QVD6$HW8)};@fA}zGG+RciIC0^~PQ~t{C%x5N-gm)5->gv@-=O`p?Gz zKxsr6X{#BV5u59Ff=G0hA z-$$Pehe=!~3|iz?P1fJsNL_lxC6t@st`=JpUYgg)s|!9xgu@gpKL+5$Uk_!ttXKNZ zg^R+#{!=jwD7hudM8|K`(YIY#%%aHceZVMo#JX>UTYcF+X$BBn2h3YOnTP*Y{Z^8Q zf7DWfxmoqrb-Ggz;!$}K+F)VBI8{qnHYkLv4TyixO~fS)4*zk#|x!2 zS6DS?Ad}b<2%o#98MH4zLYDPK;}%dxMeVGcu}?RuTNVzbe>r|Dt`xGC(fE{#jN@~J z7u-OTE=FdA>vQ5^td^7;-n+!eaY5t1WS?y0_p%FT(zG8Zk7fFA)T*a0Iscb3+2H@< zMp)|SBReV6gZ@8nH>7?!?wVp;X^H84occ9#=lSW0TRPMS4MUK3K<4HV1S*B1fjc;j&~lX{T1utgcI@ zkz)P1dsvvZ?@ps2TUF=~=LL)^+H5WNI|xlImEap zQYK)LPOg~`q#_&^)w%mNxW9RFq#;<445TxMB#b%bPpx6R+AL!V%)5<2rNHz_msVRzoBY9z!Kd+# z9i@(+qAF0jOvxCCVG^u^+&^>p$$w3S%Mv&a8oS`dcwA><;sX=ZaVGO0T?ud!>G^gZ zW3IFS6n7K4{?vl^oV`#ckSh5icqC1RiTe1Q;>K9I2g3$Qm5k4NLWS3 zgn570S90!O3GF9+qf6n#W1pE89kSypY{3ecuDf`>iur9e49e&H3@OQ0800GN)#EB< z>)R|PF`K*y2d@eGlvNy-oLY<*Qmd5lY2{x^&D`PYMN(Ux-i;Mw z86<{YC(dp<`(GoB&wfN%>5win4jJ@G^O#R`K+|caPRgr^M$U=WgQFD;$fnfck4oOE zQ~jr*3YzIfqM#w?)$z-dpn;E{=&Q?u>HMQEx05Yu!rk3fjL@ZmAp51%b1SA z#Ys&45&)AsaT#;W+jY>(2TS=HX7Jy2fl{D-6b&f%@S6Uc(%wVbb3)2~f@q^M& z-2jPyF0unyQH9I@QJMXpWz2as#!bMhAxsrp=n`mjFB@oV1e@4dc-j07d)QqYhy#y# zU^eMrqW5d-NOK|3@6-n~Bv5iVP^89>qvFj>v}({#b$r`MPxv#6q2vlZrjab|&(yG!Xjbxmg*6xa0)_?pDrO~`tr!|XjB>}ruqh(^t3WmgU@ zJnjfiH^@c_24&E1sAs{{LO%|x%&E8>($xUY1&V#)NJ@l%+;`=WBNFRzWv(enJYY}Z zTk-M!ERLc3$BwZDn}0b$s~?TJ1LXGr`;9{_+jA*ybN@DjIVm8I9h?t-41Bb>M{c3W zCl{@mU7|C8G~!|!SIKW`N+tm9Q@>c>5pwEXmY>uo#&qZ_$|r4azyG8FPyrT!{8UGJ zRz&}_qbuwflU_M3xZ|i0^ssgQ)e)F6Ps}ccX-;8Ca+r$4Me<8b$wCNRB}9&tZbX8^ zSd2PS36vd-VoAwSCS(ekx^5B)HzVH4s%F$He(*d*V_ScVXKU)&#_E(o@haM zX!6YvJ?gZ1{nfQ<9+MHV-f>jI4er*|d<_W-P}1*r`d?NhC|FrU{Ssd(i&w^Uw(jn1^P=Ent2&B?ZPShH z;Sajyc9<`ZzxzkH2t+^d%~`lBKcIH_8S>)2BMPlqz?4h0;AZ(qNid>A>fO>oh^7p% z5i(SXD(ht8?bF}ZM@4DWJ{P@ktETGPo)aulsp_W69$lpy*+Ra^3TOpf45k}3{Pi#L z5{&c(?J+X|3hk^im$SY!l~p&ns7A`Z0|ef={m$@e1m z!fK-LMZpu1KP=abD`Ggqt7Ec&kJmEVd)#WOU%th{5s{`W1h zvQGSY^q)O}IX-Z?&1W0B#c!jri)W9NN`l%yuHFiPjqJ~niDA%;0r8OFEIRW@ehLCHGA`P?HST6bqP{>onIXcm$Y;E`N?xv z>q@}1Jw33Sxh-NK@Pq=w#8YeyKPhxyTaK4lyQNMmWEh6COYPuS zXg{BDAc=!L$Bo3QPBCkX>-__NfxnF<=?wFn1Jd?ek`*VTE~w0V9FhgFtAAGCI=!S$F!@@`_^goz$=lfPAOs&9aBVuxb>1k*8wbu9Xm!(hMxv)8N^Z2Wd zKlhF5{obmj4A`TJ%Go@(-524QBaAq;Ztl+<0<2X&86UrXH<5HG2n z`=e{TeL}hM`SYI+Tc=+b6%QO-|1%6DZ8~qpojScYiy5Z1(2+R3RjxOky<-@YE;t?c zz=ZK0yESs9*O13VfqxaQLhyxLt|kM*VK%EUCmZS)T~p1Do?)G08Va6emKE%Cz4fCw z^*k277R%WEy6w8*u72e+ckFISZk9(M-I-6(yuII_#%nhb#3{iAjbfofK;fW`0uVF~ zbm;To08MYELzYe~gaE)#bD6PvOpcW(ilDs);BsKV?42b~23lS^7vM0y11^<@_*3Jr zHJBe<0F&g^rf}Hv%hEs~A9hLPftIombQ06f)~(vqKrhkMYgZM8F4?vt+=_nU(cn15b)w6Waa(Nf9b`&MNg-?GBSa+4j^YC@biWec1t@Vf*1Ng@NWe2u#jB!x`_83sIsb6FhD!e4Nl_L0+vOw)`Qv$~!wF^mv6(SZ zTqQgB)OcOb?Q>5HaXBJGY&)gXU}E8kJtLyg&9UrdO*!P4t^m=u(=EtbRv@u;Kt4@lN#tp~=9cfBP~x;t7JygcBSYB1#+`Ox5%ZyCj!dPKkgmMWE-ABP}ax~#{7ZuW!70fXs>CGWxasRTUQiqFv0yxe7l^2Myd zWf5y{2i_q19(uw`W5fGgP(n>{pa8NyjXK@m8_%QLsdhFAST?4Eiw1D z3d{L&T*A)`xccO7f}B3LBIEF$x)&$iNVIon-ix30VnW>tbzd%{%iVmwKln;ET0o=x zZKL0WU9t?6E_DrN z_akU2t=81P-i=lnZhQvYlM!sEfN4*NOZ04@|9XyUPj2=JP2t0I*oC$ zlEqK{%wH55k|l+SVKUjj;TRmne^7$B2e`z-L<|w+ra8L}&-NJvJqg4|=-4il5lhXR zMLuCc*n^j>9eC?^8H|p>FsrZ)Q$OjW8NxE$_~0JhG~t6<1Ed_r*tqvIy{ZPqP5_{c ziQsq42b3+$I+SplI)VIcF;kL7#6@>7ZCRZq5pfe2$Gz0>Ky`^U`vWG^R2Dx_C}}(z zfWZxg;_K9vLqied(-jcRdW;L7Z-!ZLN;j~%kU^y=RSO?)K!p}&7^w6RBL!(F5-!W& zQV}N6r&oNW(~bFzQcHO1Wpn^=TaxUl(sKidQ*0R%C$Hnw>;{+6{m!m@lI4l4UJJQp zys`b9M)GF(B9`kUIvk&Nk^=X+UM1Cnw7CK?nDe?F=8~|UfFt2F9S`I@Ye<`HwBLD} z1HFmrv8?+Lfp4M^Q7|u{!=MR!Knw8E^eCRGZ`kvt(MNg)mua~7mapuJ?33k*t9s%B z*GI0qV1BgaYQe6(ZO2T8eVs zv6HP@g)IZ6vZXR(21?;Ry+n^OziU>8>{>CN3$oSt`N@?wpL|Hb3@eM=$n6+bjXoj( z78|mw@8UJ!_zvH+m^e*LHs60Sl8w9!C|8jqjtx}cFBV`ZVzF8Cwq_}r1|1MYa^aN( zW=|UB4z&_FF)cp}WJyocapDHxt}%X2RQvdYJ0JJQ?rJMFUtv%gBfpbF(swdE$2^CqV9zfk0kJ zjvq*87DL^;2qjcK< zaDl3yO6--r?Q5*s@{FFwEc!yQ(nR~A%)ucRq-qFxaT2G)G_Pn74EN5ZqjCbu-MXIR&r z6VI}wBPMfy58YdQP-5cvW(-woh~R+&sn`coZphoV9y>=ukIWONZg`Vr`@Zm=PT9V6 z1J+^W^z4VgF(1n~vp@AcKN@Qk zjQ#0va9bdzQGATSIA*0pT)dR%8L1*dCUKg-tYz$&Q$Vb>^j`UO3M5jP`@)#r>*p;m z2HfdLmUM)r@^HU$l&rp_6fgnvA3P~x`R@Z~!E(smW!RY~h-jZPJXmWQ!SU@6JflZp zWet#zlBmUB~LLSxR02t}kgJ0;zj2`{l z`tJ9)Lj*S<;|`UGwnB7u<4XZmLC#ShJj&imF?dtm7b3VD~h z&y-Uyy=%@U3aQyG;^+$DL{lXz|FIn)0)7q(w}LydG#oC9d59A?7?ig&q4uwU?Qq z1rlI14%m)R|5-rf7i4yszQ2B%Bj-hfnGU)`D+}HG#{mwyo_@s!te|$of6ObVR}-eG z#6CZ_F2<2ncydHKQ2LtaCNuM;55f+RH}4jfNGs?Lp(}(gLl#|14?{{l2TB>rK2;6u zZke$iN}#3{LFixr->Wl2QGi3c34G%NfFFV&$lNds*=riWz{*V2J4k`jBHTh2zrjeG zhFC95*V^O+SuI8mES`|6Iq}b>QW6?1Mfn_&J|~$G{!e0pk72gB_I4CdU|q5u7aJ{ntL4e5We6po-m=wrviRSnazRK?d%bts2>|> zj0xry?1`w9Bn2!^hZQj77d{naG-ii}r?o#RT&3n%hNrjBHE?7uSjy&NDPSmKx>fz+ z`V7%hR++?kT==oFhFXc@aOgutfi$IxZtXHqMWXs#qq9OEM_8o?0ziq|(#iL!b_bl$ zw#V!~0EDb1TRe@{0Z}#{%wM!6_~c;ti;?W86&I3buq^$h`}F=#wE+ju6+wgnjxr0P zEHUDWr#!5LD6B+WeixzG#jt#P?B@O^XsN86jFV8u+naA8M}k%Bb2LsE_f`CR<@&Ba zOUNe7HvMwj$NOJofE6|Gry&Zn^Pb!Jvf5UTl@NIVT9zFYHAz7MJ876M4r-E8iAWPR zrhBs)xe~9_LJ;>y{2o8^Q0yCkO_p%j6h1g`@$MK-d%vkaxUII$`GNaQ(`4`DXqo8KoV9?y^CZ`YKSUs4j{XMtiuO5eznW zn-T|Y!-Pr!xV8*xsGrb96(Vr@HT!!Wb;Ag|4VvaraHz2HSjpe(vP3KRWK_*F@Ig=H z%YipKbG9>S%|mBVz0bNa%^W8n&9v&vgMrV_Q${9icTXXYA71F@6dy-N9e;K65QNKc zY2Izn3ZmGQE*79H2oQ~{`I3aX*#PY9c~v4G;pCzCcUD?dYT zUc3U1&kYf|17A=zHj}*bMInFcsSCYeX3b5)Ds*oGz`TIW@lt>?iUr%htV1 zUUF~^{(K7wm4&bUIR06l)T3%q_2AIg>q%zz4YwN?^ zc>A@!9GR2-i&Hg4E?rSpdCSg))CdcI5r-EpEM51~{r}!V?lm7F@O-?S`To8>q5|&F zy{NXR?Qfo=-T8Tpw;#XduqeEzpr)gUnskoFpICX<_4u)uhqrZOcs5MIwsWubeSwsShw0QvSg-?qKdC*(O)SNQj7c5C9YZWIi9B?cym<9b zNY?20i#%P5fZg7~?5LHDBm4EO1rvn8*QP;&@*)(aAe7C8EW!p&&$W<{P9}+D(!Te_ z9{ygn0k=FsMzP5z`sus%+X(1qdgtThf zoVWdcMIO!e_Fe@pm5$5nW{EGH%*|M_C6FlY)(qk8gYZcPx^BZF|MPz@_=%Yjpt)PY zxWBZOtc?=o{!}-5Ki^)*h%AOkfVH5p=TT7g}d&js$pJz=|r|E#B&7#C6u| z@R+-mOGkv#6Rk%_{=@7zUCg(23erct?oLf|lui{!OjA+e_M0!#g()26j+~1=?XwHr z1Oj*n4Jt?eIQaaDEGG9z*7`u7)<}iZ6G4tvVqWamzPMZY@%xMt?CvGF8YQ~^`n$l; zvA%aiWB%kTGzvaGT*pYYxF?(N>uD+Vh1b25TSlkv-8=o*=*-}$fjW-(wpfCkv-cP$ zc-ZJU@80t=2m{^|9(tbj z>-_d5S2xhO;K5GFr(hd+DTCr}94l9BtKGRlF-{T&Fcl2b0nuV0)xz&JbMEpmAr;84X$?%QV8PE;vWKZAl-0e*WImqQO9MjH%k?Jh=j@ZInx~K>s11 zu$oh&rSp^)$1NA*O#D+IdKUPj*>OiWB`U$>vg>(a0(k9UaU^`}nie=8^(S4YBT6U5 z+viXEnVnzFf`ij5>Gv+AAlRpGeShgWMnU9JNpC3^YP-)Gi99&}r(*>1=lba$hI4Dl z`6~^1*Wa8U;3lD1zhx+psyDyPc6;hul%zU)y0kzH^Gi1-yX9C@fjQzPU*;{EJO^vd zkHzL3izth(cCrjXM@ha8m+ZkeGterN;nfhi3Q3>K_6lsqi0jLt=bJ8E2|2;5wr-p< z69HleBe!2NFrw90=x;6Y!b}(B><6ir7o?rXAaZ%9=-ofir4!*H=ce}|6xa`^BKz=@ z5II7zUO@cJL78tJ=l<(|foD_gew&ZgQk1v5-mY{FcKofzJ$y0GZ)5+}6aP1G?aZS6 zzv0;(Dt>bL>3sS8sXRS{)S%ZNnN*)ouTs6cD2j)2m4pv26Pn z(&iE~kig-8FC10lmP!^q|8aMD50X=P)|RN!hc9=!ZV(=?+MOlB7oR<*$Z@)>x3`Jt zIhQ)>0h7na!H1jk-b><3!?OUPOLFbj{MPSBgQN+=OU=yz>G{;i zaR7)>$K;Boq7BK$yIk@50MO#A&r-U=JOOlaKGZ7_jUvOlc(>KgTLa0VqtoBh#Lk&8 zz3R!NLe_p+#?&Z440}qHIDHrpOb8sso1coo>TzSjT!GVG_-33SC&$u=f~Tdg?+jy% zV^?HoLp8_vGr(yB5S3BD2wGP5G>CF3~AayI*K`1~G+6JBF$f~4rw`jLm z2#|P1XWyQyk6u{n0V}JQ&sqRmGQG&>i_=VWEfHj*$DR1&C3CcJ@IHiW`g0alSsX|+ zTLx$pD1`l?0ICxQ!Sw@4J*`&7;sAo;WUj3X6ZIPIp15+6v4fxxnqUtyeBj_+HTOeU;i{;`o(k;%i~)HG7c;C$UrgUBm1ghg%3OZ{fgS< zWhI{xA^Y8Q3t{U{I72|^Z~P;5?`y>`EHsRN>|a|rz2;ViEV`xCk4$AfhoZW}L6h%| z;ziIvZay+Vc#P~|zSbE3>f-*sAtyPh!0G}>X&$A(6qDBO3Mn|Oe@WCsXm2ih=dt=gmNmEur7~!>Zks>oIh;CHn#E5* zw-EA(V9j_?s_TlSKn)TRbMxcpKRTS|XFd28E=vePaDk0}G@^lZ3zCcQBF%_D>J>A`RynSt1Y} z#kw@}o|B@RD3`{+Kl`Cng&k)aK8}fzYW^jkBu7|+*_*Ot769g!Jp||$vZEGTQq1sL zAF}_OA%5(4arq_yk5Ll&O*+_phNsmx_4Cy}<>OfFx|aVJCza0G{O+@R#tEO;4G)Li zoe_vq5oIfBfhPg0t`B;Kmc0XEpZ?!n2A=>AcbTld1_V~{@Z{CH>mMY~GsCm25iJ)w z`crXQ)pEEhf07B%2x{WH=r5r@R-YiT*_v6ze^U3%u5aJUwwzSlS%B?h)&=0AbOgnY z8$b5)eL=i!Jr66Z=rtzsm9NcMV3b=Sy_|C}Tx?Dz$sW>N$3W_8ccV3`jo+dFUnb`4 zM6)x`;j$0D+kqVwbS4j3uxtvn1^W~he(qCH;2LXl>(B`BzAVe_T>K3`1wyr`)IxOv zvk)RL7DkZj`yf;d_xSD-bGO;xX(A4wp`v;!L@_m(bH*Hn)Chee6bG1w0SYNJhOK3s zXex>3&g01-vHV1+8$&r5bQI)%h)(|F`R`v{h1Dev#+4HE{H*t5a~hp|)e}&jW(&$q zD`67_Y1M@FjORyO^Z2-cB7dB2CV`dIlGDwONf@J&Cqt3P?J@l9XPRLhI!DKSdw>GF zXpn#PUmY;pd@xJEoCRn-Wd2}D36K*KSP?v?{4(g77AS&BB=+89O$wOwRTF<`S?|ct z&@-}|XCV=%_o}b-+2m$;RWTr3+QaIT}!-LBFCaU>DK*>m-A0E1uo^3fq^!u_r zJDpQMKY5GfrF6sBVFMf@)?l(aAUc>ikBWhzTYpv>Fk|-xLQK;wPqai__Fuw9wC}N& zK~O@3-Mmc5KU;IUHTYBwU+H8+kz^ZQ=y9mB3No=V+-f2MSj#Z*qGw2}7PLV0@tL#x|i36L?I>Kh$JaMk7FI2vXg6C(eUjG@yTprCGD^{q!L4}gP0@<)=tfCV zMWyQKh$|hR_iBG{5L4A25Fq-21IulwB2qU(ZY2 z&rzhYSh^Kc3*@VAP)U~GThWeRiCqti)ma=wlsyd9ES;6~pc|PqbQdbd$S|sdW8(Yn z-jqFF=p$V9ZaX&XQEMRcsB~T1p^y!_KK81^lieS|q@O6O0=1yq*)M~Lowuu7Vlr-= z(b}@1p{KdX5PiRP#Gv)#1Gr2hovwhrk2`xm<}+*QHjwJGl&6{g30M7|FSuBw+`b`y zG0pQW#_0U6TOI{b<?O&|=cB<+GQNx<1`B$wMtwVMivv z{&VOMVQrrLqxSL0RU^Xkk&((M0B?*IH>J;I^JUa5mRY2>FVb(M zF({7N8n`c*C^APp572=vuEvfR4(pQ!+dBq#XT#*6c}S%uvYRR;<2n`)Sfyhh)uWp+ zFi9?U1=zhxj~V9(7P;MDr(o~f3JkLn^kQK9bl~nREElj31skwzv`ftHm{2qd;D1;= zj|j!rh``kgkb}r7TnTp!du0w2%oSjcp>45*3x*Hhc&%#DJ zA_a@E9egOAI)W37l%Qi50P^Uvi2AXZK$auK+&4N*ehwFabE?4s zZY=#eVfaU;1SSyqktwA(Cw#C~lF^5P5Je)nlBqqypScJfck$!8@UT{?B?|u+LGKg_ z?+7t5Z{0+{auI4F(u#pF6H#brlM%p0P5?63BJoO~EOnQZBnQt}sJ{{=#(!Aq79FAV z5>sBt#C=Qgw)>9XbmiO42uMWg(*m6t?4pX#X|SR zVlR!E~EhHku2DtF&K3Dwsx(MQld_2TspD3eV}$c__aS8)SRPlJOc z27VI;N5u_;JPpH23?nBDPly}Edm5c8F>;8SpRS~o82e4A@n4BbJWVc?m|UDN zxhig2>uK6hVtV7Y$pHYhF<@HqUBARL5k*&4m;;_@B2wp&<18`>ut=Tb#}^Ecl*9r1 z6aFl89ornjve-AF?gFC6tSlyomiqCC)luY&+h(zVh0QbMQ!e~^oW+4aepsj z0=EzV5h`hK{;8PX2&BZ4^=PF?ICo!a4`Kw^yEWM!g{ zemt8*M=BpUB6*DcE(#sP0z$zz$QFsaHp;7LSKXJkPf&$AHNGAbvB3V|BCO(6sZgt0s(pa^nWflBv0ieU(?SK2d0ng%gQy81NBxLpS~CQw6|!}0%^CByyg#`fZvGrdZ*b{yEQgrWvBUy4oxCz8BX!}%>}s%i}E zSZ=e@JBpa?kkZW_qjA>mGq#ZvpH9U92r+7~Feem|hA}LRJZGg{|3Z5TI9MWe!e8nX z?IbWm6q%;)eo6_Nm7x5gri=@ujvI}IzosMk;-G?Z=V^L@k6rSI-e}kFVo~6KE)yx`i2rUW zYfZH&t+FszumU^FT)>OR8q>?Wq?~$M8d&fq^FXXA3>P{@K35+8Sv~e62mL6A^g!zS zJ5J~j_ms*hm2 zmj1OF!C>z0A8HjkMkkq0<3y1?Y_EZZ_>2&sFu}}nFojf+|6nsqIoLdwql>&YIuL?g zqkJdz>3D&Kvi%-OeRg1aFqde{`)5vrnt8k?Az8uD&NE_aCAxFQjZF6xS?m=e>hD6zex{n3ZN=+gQ!eF*)o&+Wht@Gt z<&H@`RGj9;h8BP0B2+pvyb)gEogyr5q{J~VQecq==#Hzr)T-GWjU9a1_nhU1te5w>Ru0p>?Ke>EOX*9#tyyX7)mNBbQ*}_)k0)CZ+gW`u zxZ?6_B;6!{kIMS|8)5&tR3L}S>#V)?`jYHxPD;PaDrVmoFa%B)QDKl=1`d0t>xc)R z533?IcG&P70n^G0jQf>8e1Jad>YuXaV+)t{^Y;B)Lz?&T|B@lgG|?6CKfT4i6-Vqc z5&8~1suBg1Bb-eKFxq>X6&waA&}I+D2URptO9qduGqo=_`}xHLc<9Y9Y5 zqO7Nwl;x<7&}?B^@-jV&Vs;!A85#J-B`^r#Vix*#(X4Hmw2b_{AG95|hs01KCcS>-5(^cMu`AJh*5f$eHL>Xl`=BdDFEh9MZ|Wz0ox)jP7kJf_+99_$Ql92 zkFqff^t0s`jXb7pY#-c#3FAi6ZnqY6wAS2HvVSB@={myd3_5voNRF(aF2eD%#1YE4 zZAyp!+&$9JoedW+%Pf7gbT8sghT3*g2PUsC{K11ic|Yk1Pts595W4^pQ z4p(4G9iI3R9tDiq0NqR8ZYAq1xP9G!fvI_o$`eNN-8<4j+v7utKj~JeYrJB(O2H3AzVjW zrcSf!N`3CPX*Q06YA6Vl$O5qM{#xd}jp%vbswFZCHdRzT_R7PsrrvvEkM-C*Mxx%t z8SB5htg#BpTgxpt>lD3z(uxIE3fS)kBQY}|X1fuNW6VZ!KETeRd4F4I|LPRduUo3y zdiXaa?SEYS2+IyDR}ZkU zmYHkS9$%jRZ9>1-^(bg>R{*vi-5+>3E0lY4DDaUz?`x&yoWWz{zbokk|8M&S*CTQ} zIRCyjD6Ia<&08@-9H2da?nGH`Q1Ve2C|4B;BqAlM&qU45N>V<^UZHvX+Y|;qW9Yw& zMoe;Y49Ume3cyu#j*BRBpG1E_64g0tz%I(dUL;*4nx&89UrnfU3=urQ-j38oEV4|K zdXY!~w@f(%@3D5+_Y(pY1>{bxj{Z0L?P6y!j%#{n7kCtCsl@XS9U6010+-E$j2ad!kz%5ip=I z%{<5jZTbNUf>fhV({8PT0q0OCf;AH4(XQfCy62KTtPp^{989_c*3rX4RRlFB$E&+k z(L^mXk(|I!6p52l+$oDJXo2~mrjh>YlWl-p*Q-XHqtfbYBh^6q)M4x0e$9ts??iY8 zx@BK;-fbu4Kj- z3y2Mt2~2!@jkCjm64Rh^@qO$b;d2~#Rd{D?u(ZF?EThM1A>^L7Uts4qWTry<&#JTa z)oSyZBG^ggQ+}5#0)4bO;nl}$ng(WKke+C`_ilq+j`&3 z0|3MI#=|lJVOXh>fN&9noe71R4_YPgEhz$cH?F&tk^ueslBc_c+fXn{J<&$vP5VsSJG5k`!I5upVAq-(O>_38UBaB^fQ((Qt#pH{ z)<=^Jc`Yz;P+XF>mL@MLNaI1gS0(HSB7I85exz73OfLG|sJr}Wjyor2GoUP!aIkd) z=NP*{<=6I#2=Ne733-SZ(Q)ajZ#Y*6lov3M6_7W~D;J=vj&-}EPI4Gx3aJ}K883Zc zz$zY!!%$EOv)`=j)Gvh$$^{#z+bcrHlm_K-)4E^e^eXZV0aB^0okBc`dny8?_4Q18 zJsrD^J{NU_!e!Qq{u-;U?A`xOdUqn%jLIZ9tEJUq-HOKr-XerYl_`P`Js=!a*TTx+- zeu{Hyz`(E*p<$jAwy|=l{CAk(tQwk-fRFwKm$Ve{fg3#OwNp6*{ z`|kVwFTU@e&ady=>%7i6&*$S=*Ev43O1F0Kw>NQ?K^_^kWtX@Ev1kV+DSMPqtH41_ z;t<9mx|IwIsg)D#dcvt^a^3)T|91dbj4q`_=!**pEZ83F_9FBGT|;7pZ&moE5WD#D zC>&+DP8bN^vG2<0@=c5Xga=}><16O$YApw|1`=+?S1w1?T7M88Ozw-X;=L9#e`n-y zx^SP+DYA;?m-;~{u;}VPL@1`8IFzQFP%Ro+XRm@8X1XR^lerUSaVN+#wh9RU*cG;G zx^38CM@yIxTKT{$env?dNZe^o5vU(2;AQ1GAc(REh})IOFrNzr$9xp^E>|hPR;>Qh z62zUJe`vzyT+!uT^5u3HDCDcc25UHCI>t*WW)G%1lq2PYpyQWG8s7??shFZMx8)R_MM#>Gp2xDUdrZ1*3Kjvv`5IlvAZ zoei8^_vMF~ztPuA{t|oeaqHkyvql$9gZ&=R_@$>Z+m_5;#Qb;2tFZoId)J-FxNVVbens`sQ)W~TCKY)>_F(73`(~n~-adR6_l6eA0vt*N9 zKTZ7%R~s`aZ|%J@lK*K6ye6iysHGUO z&Bn!)pLQ%F?4IuwG0Wg)wb*DOc-43$!zSj2MCHw6cowU`k`)Y;A6=}!xR|kv_9!QJ zSBJ<178QIh6lqR!z{fesDzqQTMS>FXn*U_=g8vST#*7iwjiK^l&2;_isdkUR*j)+IZ(5fXPFP(w&N*EN{Q_p*uf4qs<=pc)59DTvo zsOU|k);uPWpZR$R#b|dQT1~Rcg z$(fk2qvQI2zFR!I;=3MEoAWAa?5ETEM8=g%6M9lVK3-||S}*^d#*50h=L;EZ+FK}T z^JDYPoEDz+J7oKPK%TN_*IDttCDUMV%A@U&*PZT?QycFGgMY{Ww1FrUtiw^N_P73q z$x%4fV&+_XrKO>K)6wZSyR{kkfp?jdAyrs0_m_Zw`~!ooT8-+kYMuPI76HR+Djj?) z>O2t4%v9Xx-P@UoG=?IYc-Dm^n);{*bNyL>WTbz`XpZ!z+E2uaXe+PQ2`SY!bf&L*5P0**L9mKktwb!-HHLNp5k zhJYFx{7gOyJx{Z=7X%p-|9&F=>N)@K2O~Rxuv;41Rsc(c%ui8lKEI0^&`zJ!Mx}v3 zjFIrgIpoYiC-32OT(WfHDk14rDgtp zXulQ8^dV;)Ur0aT4Ha#v3Lw}ksPlCSU?cay>&xc1w)p4i@dr|U%-u`PQ(9dj19EQE zWke`2?a|pSqfVY%Inl9_{?+Lh=d2~A2;J11xi;bp+h8x*do20mDAwLXbYn9~COCpb62DoY$D%n(y^L%aykgr=wSgv%mHnaATUo zhENHR2gEkSpuo7Ibi*OJeH)0uS^@t*@ZjOzb4;vePg+W!2|;#Whlbn zOvcflXQNt?@gA@^`!XRTApX$h8=+;1^#F|mE*}DF9w@?juw9<%n^|bXd}K^a<$i{< zSlqGRL-6M*w9=n^U{e_7SMQ%!Mw$z+iW|Ag|EXj!6w#hi{W9dLET?j+j8!(K_0Bo; zTJ_}=3hToDO4O-iaq?30DOI4<)s3H3#ci;{pVc42S?6)3?Y-BnJum6x3uu+F<7`wA z#2O2a|M7+uXdxt~C~gWJ~!S*4HCq2imI9G+4BUTuT=2pD-$g zhpx)t>({N7lRT@NF+vH@X(Z-`_kDDDKFaB?(Ze2M&!O9q#JXQ`C6QjjdHMP~Z|PaE zDh*6e+k4793-rZ!pu<>bECQ84REU~JmWCMJETRc2{c4~b?X6?dDk4wa%byv(gfX0V zm5mBR$qaUdjh_rsCXkdla16Be4Ky}&gZO!KMI3a zz8fevVMm6t*?OQ{fic`zwfSX8)43K6Ei{Pr01(xYVrTN=<*q)qz@8%BOS^ZM%$B72 z16pUC9ef@0(6~r%U4O8rcCtkbkVIBlJ`ymxIN*CL-~$W znMc#aywyU0^9bj2`eKdVc2Rj02N}Uen^Sft5oJTD@`svHWFAb1EguLrMbYFeAH&4$ zWD~&dy%c%PxC2R8jgW$p^O6b}2$im?Y`_At{0&acA*0tdNNm_CA}WG{HxndQv5{`= z@(e5(fkQ=#5^rU|1hD4nvgaoodP2FdNB|$d2Y=`fOle&;odSm3kSoyxq9?YcKRQz$ z0KdEkgynaOc+s!5bEA2%IBj?)0hR18D8|oEOz`{a&%%B-D_E@cL|;3hnGX|$B;{^b zP4?{a>gaCrR03y+2+!`liKrI@R15_~vHBycwY;~hb^OtNx##7-pU5^*;=?_ML-jOr z_w><-KvHhch-jBxdkdrobpO$ofA%sGLh>aQ^njkth`~3@FF3gaPpM!B?IpnJwSzS^ zRVeUshl2c`KPSLz5HDXfy!;{B^S50(kUDU*UvsM+MrI99G(hyApuHeDijy2$>+x{+`JL(}pj(Y**(;Q4YS_hsiFfPeO;t*I=iq;$dx`9V@S z#8GufO z^&6}N4)o2yl2?(^^+ts0x6kbKBE4?;XuL>f4cax^hhuxUdsRpKV~w%s*i2ZRYEJ?I z%&J703?YlLr)qOYWGNurMJoQ%vl%KV;qj`sZzPAc(ve;y(I(nd|(x$sohOzxbb!pbg9 zBa<&ra>B0H%OfTod`&3orbz7VHb(TFq6zQm8MBrkZlu%rtVLAXOgMHn(Fj$Jm{G23 z@{Iqm9hg=!c9Fo&J`$VJY@A8)H?e7Sk)T+RJb7LQC=wMpwhyMB=jybA%_3T8M%fwJ zPmEVe4F!F?9RzoaGTA9JyMKxzAwx0p-SJ>Q?^WeaUvI$AeP;{>>Cm`Kbt=cM@%`C< z_7?6ApS9=yd-5sPU6!f!;n9}|2N_|98f}dYJ_ZEOeL9k|YjZvhE@U4Izdk!>8Fh=1 zrgK~s?t42%=;;!3h#&iV#%}QoEXn~)V!?{tX`Njc_ZIK8)I!}XR`~kGiUiG;JD`mH zQJ@-%H3LiH9BA*7?5f(Xvzos3wje%6Z!>b9G`-6l5c>P~gNHIGo7ZWwS|~uT|GZ-{ zXMn0+M8k>G$5+XzJlMIGX-Oj!$JZ#l-`wI9vh7@$gvWc8jYXKC8u`m48Z2DeRJOmb zA1&-|OkA5y*f-^{HdMT((!HkibxlQLUDa@1{eznsvB3zrHwb#Vfbf8oz)l%WuH8j` zgzeqSjukn+q0zKqJ-A`}bz`T*Ce?7SMY;b<8vGi~eMr-T-;F}Ux7Cfp-NSM6qZLNqd3`p5o3DRZNr zA=tpdIoML-PnGx9!TEx!bNoyfEQsQ_eO2nLN^gmZ(s`9F?*OIxW1Cn&=$+{nYV~`R z=g+l=`dL?xZxi}i_202u5ztVJfe$ogFlxbn;TjR_)Zb@z*>%l`_qC)1XSra5_} z*0r_;Br%?a=;giIZGEHjuEQEP-outQfnYt=?fs3VlW%`!QuTjpNLVdm4i69Al#9&o z7X4&hPIsf;_D`p?yqnUmrY*qeO2y@1J>g)RGRfiIOLt@ zaKW`z<%;5*5v(ZZjhNyJJ1EZ@aPAbhU~RpAM+RcW02?H_27$7DT~NKCq0?b-V1tB! zs9uUjFvadn;zMBs=MK>yi?G&_Q%Z=|szGu>6KGzgnKuS9g_qqCFrk$l;O zEGtnof^K((5H`lOLE>Q6PsQYcOjYGBf7-b_y*!(h(#{>g5)Tctf=M#PEMPU)x6zW3 zKz^$zEI2pqqnUah|7r2!F^jhf#E+kIr^?jpZmj%zDGs*M^5_v1dzuwOVK(IV5O?;8 z;%XfnCIjGzHz-JF`xDhg8@Mof=F^-gzS9+VXBOqrFJBh8QhKSm^fxCUgVrbKt93FK zfZ|?klvYE@pBjHJHEci65x07jbLxUcM5}uAgQRnGff}az^qToAGI8q3Z}-%1W1o|+ zLF9E#*qVjfi9eqmW|4(F3;I|0*Z|ER*w3XB>QlF+An~(CZyx_v%(PkOtesra$y3Bw z$^Z!H!B+))WS2WUywu-mAZlNRAPU!ylE*0 z?B#a;MqVbYC^-TjW{)|N*p@)cikNyk{FBkv4&lDrw5X1Wcbf-wg!I0qe5JAe>TQ|H z#N|QdyQf#PQP4ujh-yKEu-pD<1N6)04#Wc#5jm zIvI}!B-fX+!}_$KZV&@TE)Asfo3T31T`VZ zCQxMp%0!`A67fczA`+BLn=Ox=#5MK8Di~xrDs?%cov5e)-(Y9s0{wHj+bn1IkS4S;wO+g^AvhH+k) z2g0)$=T9HXEz2KXx|_#cM_GcC{qte3MfsxI3YFMuLGb?0*5_* zP~bx)Y``eBlOj@H_d~E-W>7jvlp$u?jB+GXw_brTPZ}sl_8PdQ1B^px%G|vg?fbe1 zgw)<*YG~q`Mxl6^F%vW4ef7`lF#CZmZmZO`J2v0KsWqn+9tEDn3I)|4Zn9EIUzbKf z(H}*W)hNU2cmP0Qf{9>iKzei(7emwi;xF~;fWC)uZ;G}=*dZtNz}+#$`DM3 zPpp+#&KTEuA+b_X`A=r`_LhxYy@(mK{-Me%Y1}T6N)AI2o7ODq5ex% zDGG3wzAf^LL#NCI2vO7-qJIuE<+-$Ya=wG>+1vX}1lpggdx46GO_2Z2ZUZps zhv^=ZmGrt9r}Op1fU11TfZobiV5=Td0o9P(JQ9=@#GnmF&WYnXX*elkhU9eVNmy*> zF-oP2+Vcji33P-iI+R&FIxTF1y`K3mZpl0Im$#B)>^?4w+1{Ni7F2qPg5h-)Ox-bG zrvu^+n@GYw;>eX1QzDTPaj)bsF@fXU@)%~kz1Ux(6UI@)#^3P^T@g;=M7Q?5oD29G zP}7)^=&J0xSpJ78mfEVZoM#JIE$rzFdFG54MD|KasEs9Y?Vn_gM!TW{C79UH!lM8x zazruiofH=ra5)Ch6L~N8&%7B4i#A^O4MlF^cT_OUVjUh9yob}Sk3e*C|riu&-p?>9D-mH{TMMmXcE z)!E8v7u5KNJAW(ozf7HpLs=QhD({pn-9r@3OA-qFo)P^Hl<>-VM~M^b-8gN@&ehqh^dQ{agO2r~Q2B7XU6XBs&r zPd4U0odWk_C^h4maBqURp0cnETF9Sav5;*7;OP*wBbh-55keeBuI}XwAWg6^l5Hgc z*#Le)7e%;@_}@Sg3VO6dxr-tu15ug68lpHwGR?|gz;FRCz2}${CFS>Y$a(`-Y)}UK zkm3UxEMLSa5S6`YScIZ%aEJNAgY8zJOe+HiRSNtsf<_m^%sRh;A$f0G#3QVPpS6~C zmMq!BWAdev!R=NwGpxN@8nB(8V76hGhG_vU^#sJNXQdoFeGRmQx5Lh}pll4yjtr=` zgYGu!yU)^J{89!X$Ac4;gGF!)GU!0ri6=0$a?3Qm!M5#8%$zW#w0kcC+P9}Wu!Z8a zd=V2?VC`Y0+hXV2(1922K5uMqfOuY`185Z3p?UWE9J-UMDIzlVCb?SHf7r${78G=k z`RK0tx@YvYXTCNVO231PX;Z|XZqZzvkDpWQZYKhX>tzbbpb_$QIf5kMSUo4W|MTQB zZgj9sHOYaP&>~$X7m?kV|?J2ERK1;;?c8eG+yZ+ zIO{28$J}GkY`L{8m_|o9WEFrnH7?tCJ+<=T8+XjNr31H-J3HvhZh)KIG}N}e~+Q1Urx~>f8;dac$Z7bP*tffMVrPxJMcvr(fwQd)Bl!L7ovEq~5 z|LAIpx7Tj8Fwpr7N*yCRX26O`n^@MJ-orq#;FetN`4eyuy1yfzV+W+|VCTu?!)dGT zG$K69vTrc~djs=~)OWO)(8|pCZj6CE=uQ#&c!!aYUe0XL8;ucpsY?8rb>Pn>3Z8(f1NpbfX zR~ngytSoy`a`p1;iPt+zjKVeLG4onhL)`~oGS+sFAn$`DHseb^LmoSb zLU-DdG2Z*%gxD&|GyBd9LD0PRQMXFV1$eRjFsOXyV5;Y@n)=YeyocBZS1PhI7@cCT64TC5?-V~ zUg1VF-^QDS-m_)jS2}{*iDk{H<6tsf zn|=MZk`}K4Gs3dv6lDv5$RHDVoQi=b)5~=*bPDZ@KbV&9OCNpz)NBHIdp|81B=N3s zDFT@aQ<42%o(Vp_FCiF!M!KcMoun{kyFMepiiyTf)wJT4q1=B$?UDy_@J5ijGBqQ7 zLI#=|Eh#gM^1Tw{hYvd@pC715oGLcDrAQJG4}3l2vq#;sCLCvp%D}4Thx<$b|5Wgx z2^h}N5hLD2DrQ>9<_*7;_QD~%G#PwD$+dK8;zEG+`ZUew&;$QNJYFgI^CE1KTy*Eo zr(}jjY2LfGyq^2 zXhN&M`Wx$j>gV?osK@){xJXj}~>b zuWQm2Cd0xfv^)H`V-eiS=t{zhM4GDPiMM(9(N8BvWMJHhJ^kV&&9R3lFSLv(4AK5FJn&;0odd!)@h0 zYZe?YJy&Gh`uRpiNVsyI(V|)?ZrO?jh4fyh*QvTJh#dcY*(x6%)3NQ~aCl=>2${vO zV9_{@pPl01raob3F5w2gM@YO5Au-DxlNn?k$Oaa6EQLW1lMFVHrk6&ZHjhNs=UY(5 z7Yb|z4ah(zBYUOL;ZWq`U1Xx?r<;Ln!_(Q3a_B%!CL>SOT;XQB@`X6zewhwjWi_Mf zC$5}-jsZ-oZ=Xg}NR0A)+79AG3KcH29k^8usf5G7SSmK+qO;4sp4^w660fpld8Osh zmBmq*<$z{Qzeuw`{3yP;A(gkLmQp zPcOy@WhzS^WXY9RH225e!7&v+D39Y}J~vXko2x8HaQ*+(jZqu&RvY53u_`B`0V#8X z^H;YlYs`VQnx8kcLX|`^*MD%AaYS9B^fkDPx(VL_NthONRQyO1#Hp?&wu7~v|{;iYh$H@cdGyJ_eo3@R)%6=(ocf|pn_9zE#?GM@*ozf9o-)&fK z!U0Y5lZDj3S>`h=m`bBr*Ugi&hec}@h_%V5844cSGs0Lj_Cm(9OBQ@qt!!QLI$+pj zG)CrC9KFV?l>TF2SfZtfAz=oYh&(N!|R2=IXgFgc%@2ZrLZi|IA_zc9{*_=zh%Sw zQ&C&XyDZ=L@T>*r?8>Y9se`xQ{ctb2cKSD`iGJvwlDh8dtuyQBKXZKk>5u%>-t2QC zM-4vy_qSEPLPhT!5qFME90A>jYAq_&@gapC@Z%FUwvh$pNLWRzLzn5$U{vtYy` z!=R64)oehC5+gGjBVe44HixgTOpd=H+5tsV%hj=+ZUL^T$axq0S_hS)a2~*h&jnw^h$uYdF^``s1c2QJ!EiIPnF7pa<7W`ffKK89@pz*0hh}&t6=#3XbLm%->rty&9TfGm#>qq z0a;0Nx0aH(Z~h9(Z&Slm)oIx(9`HQ%Oc9syww!$X!Q3=<9}!8m8gH$UyQ18prG_J< z%lGa!1oj#HZaf-yBjMFuEs*}KGzF;3XIRVf_KBtx6MKo06(_u11aO2N<+T}H$F0-0 zG4_5Tx4h@i1s24Pwh?pAIvpVYOprRFZAQ3slrPMtr+jx@r34s9|6D2*n!63K=9QEx5Ov?k(vQj8=E~l_u}${p!{Q?SB#>AU`xe31|+&-;ddGUA)PP=&J`=~;D1s4 z@#iBx5xggSWK~GKfh+Iy)WJfFE*cCOQ;CXKBNTb~V`b{x(MPzQ%OEk+x`HaafR$3s z*2qATKKulr|FjXFnJ&euVv^NR*R=ncm{?H2XrxLA^xoSQs=rp%If|ZG&52dLCV86# zU_&m(<}o;5;xcXKg>ErbaZ`&9olmt3jdNdKp~*FkBD?E7w=JFQ#08M-wP3EhHeEH; zTG5%5Y|jfQj@quYK>O3wS6R=C)GwK9E2_L*QXZyAUB3ol3*>r787Kc}dg)FC7l0_?5cE!I@V=x%TwJN8YAH7*-pD2svhyNeO=21jzys$4^RCfB)`KYt{=N{SE_2 z^!t`sC+(JlG8HH+@p$rZAYSBP8;o+-m}H=wXZ_ULf!O+4=ZkNU2pX@D)*#T>4N8Gb z(EDApPwL6D_h+WS*8viV5S04iM&3mF zHwnOPm*s2*(i;=fZy!Sw6@YI-fM?IE19%%+lSlv8b70KDM%Vo<@CQceLiqtTFv>IS zb-l3YKOcam%SM(f>>QQ;=2owdO|&=0d7=4)?T9fAN)_KYOB%7izVKaFRN}fa%Z4ZO zllI@2No;=?S}dHIk3HtCmuL!Qrx{9 zY(q(D-5DOgXB3lZY{L;>h`q*&WI=!Op0mvW3L>o`#A*6_Ym7iN=}Lre$Z>{)S|g)m zZC@i&EnrA?C*EB{POhJNzoBIKK;W~9N9; z%0x|Xk=spx1zH$Wp>!N@E)kDnbyVrZT>X%I;lH4x58YqCTsbA7vu!o$+GDqUUsGnS zKm3}q`0w|Zujk=5JeUf(LnLaIp>8vcIY#c}<5yz7PyC+#Y)MgE)mh6?sX&HsJH?H6 zgvgA7Hp(iNy}P5}+u>mIXw;k&YaS^g~G0ns`Bd2w>AY7)r_jGRiP@@fuMKi#+B zEvrZ}$)^I6lwU$(5WG@?_eqP|@uSNBje^XR#l4qb27O>gjSU)<03JT;>2@}J+{K$c~zVDM_zpPf7h#5d9?WPr%I&rc^byl*mNYaAn#b9UlCb`xmMTE*JS}|FPeZm39}7oQU-Qd*RRr zwutK9lt2?--`I)Gqt!US}E;!-Dp z5=tx)5#-RK%T%hmlvZRM{jd6LI@9+Q5thf=t7%R`OaX2V(GC!XI;w^btGs3%qhKBj zHl=oP#QJHB>1t)9CvnJ)C?2R&rRFC_&o>pC^j5=g)%4})L=*8Z6I>cJGpRA-!UJ2M zbP%ycCeLIEY%aJA60jEmXS!R(MWQ00M#;d!D5f_9KZ~gwuHcmR%nHlQRvi!AzuAFD zkK*eBZ5n&jrJgJL&kD1HxExR|p-*N>b!Xloqq``*DTt^`H}z5%_sDRW=-9an*j6~U z^Qo`>vkvdGq-PL z%*ZatZswYs`E0u??3|`g2JyOiTu@ky`q6?6yLoW0fx66)AgE_=Sbs24fKm0Zvp0Y# zRow77r+M@#<#p?{h3*S;DTxDxyho}|%4~q}=eR#cjVhPz0@M5us|l1+9rK2j_1{sH#m`>QD07b_&mi2m58)13X!w24*48V zs>^g4qVbOcFq}-fC_?;6zq9;}OSPVy$`%km#Eh+ShnhU3N;|+I zn*V%V%DCJ98{tRTFT)ek5{oH!39p??+JDjyu`)G{Auc}_e zlRrrhCR}LRY&JI62+JD7d_p0tv zO^GfdSNBF^=ow^KQzOcfj=aeR@PB&Fwbbo#F5J^fo0va*=xFZE@htoV4JM#hgwhV8jH3#9kI*u$0oZnChE(I~u`mH9bL#fB*4=r<5slE1$-1IM_!Lgb?BoKW zSrrHThGW-?3r!ZE&eSyC>gi$%DUm2R9+0JlxGyKpD%Zld%f>dk@bT@`dsgT>cYCWe z`(2pA_+>D)e?jnRE%bKO$z86YQd_LD!3amu31P$eF6UwgAwt@5D#Tb=Hk0bYaH+mJ zT?Es#J*~ck{#W)6X|4!;i+O~K!IvSsg|Sj9!sA);;$YnI4{aIGDqa@|1*=`Iv7V0Ag4o#DE5-AOQYu7Bx1{N=)2Wa1`O1-n1uu%6=%hqn!YI5Z05{H%g_`9v!)e=BQT&9RR~b&*+dN=v)v zfdU9oZ+B6M0Nt&w@z3iO7s?$6g#n_18_&uql)b_i@$CL82PoTg2-k3c&B(E-JC(^( z^D}^>z@kRroVc$~X#ev4W5-&b^Mw1H7Z<&ysQ+rEu{BQrOoRRu=1}shFI#DD+2Tnw&J9pN&m-fJS*@M7x<+luxA& zN^^~{Ar3)79PmMg5ZHEI(5sAW^<^gz1?zj8)z*h_8+8yt#eg3uoe9rBVNep$jjKDT zr(Zj&)8u91c3P5G{#cp+BM$ zL@B6)4bt;8`SV8K25$N>g`?Ov7Y2GoIkX#-9hhSpL|z0|!g!kPR7()SY0yPq)9_sC zY143Oc^AGIQ}Ei!)V1o|YEN}dTt@r4_WNKutJmH=AZ?Fxy7!Bbi5`$cs=|)sa55XY z!b*sEmZ)`<0?Jp~$2)C@hX%p@JbQgt3 z3!Qfal!|Et4Ifn&RrNK+QRYH!#MMi6+jCk#!U7uK-=i-b?;;9VpU)Z&j>Napc8R_) zHcY7Y#?NPf+yNZ6UafU5tXQrz#kpzq!5LRlB}x36YojZ#e{IIdrrO4tN& zxvwe(ea0`R1u!Kn-!(>UI)r|?Gqh<^o3OV%u89@D_ex*GpSw588~lcx%?w+dw%zw; z40;R-J2lFP_MM#t~HzR}?St%|mHpGcxxQ>J<@ z)zqZPp2D#h*>FJvB14D4WvUo<@bwVq!X9pAezzrz-k#+mDDQX&gsK-0vg(mpZK zJiCc|0auUPT<(yDb`h};0xDt+EJZ`*Gp<8{Jnv?QQ}-$rGJMZm@ZpM0Zejv+x+CDv zh#tFiuHJ=prSnt&BpRIQ-z{5NiiVWhOLpvpV9V6@sdAqLC*=fG06xjb|I{=gZ+9yV zPs(!`keoaF&~83so<$K2%fpaNLZ3%1)BuYMlPM_rnc9JrZNO(1)(j5dJHfLP;%Am5 zq-L@(txORXAhLx(gg;l_j#j)up}m+)ZNd^)FT&Cu&g7b&KA1<}zO~d&I~}}{nzPH1 z;8EZwiT8%Y#kLgKRKY+*ROH-aDh`qq?Hx~iKo;Uc%HQ0C+c_YKj<*?&PunZTmCZZv zAsmKOn%l51C(A&sdlyekMuQmaf!UH>Yj{UIB7Wu|s`SUH>nAfx zquXE}t!bZ9F62DFz!)yx`KUhZS04d56E}XOyzg4HgJ{jEOZVDlY^-km*l#ZV2r2Z- z{%FeOcOIIF!`Ejm9|ZQ+s629C7;aE$4YT=GeEw0|sjZxYryKB({5b9^XkTJG&8IFo zU*(;s2cW2dP)5+Jb8gnlBM-@(5N^L*&n|5Hb9?t@2PWb~-NlaP=P^ma2XPaUzG!?ZLH=0mPq{PeJ04*p}a^Qx!oE8Sjq{fZc_W*wZT1_Knag@ePwl>LiJO3NLp>ozwm?|;8l ze!U;(k31gySBwA=mKBG8+ZO$mpAI@0*P?*WzZeOko+m3t9+xY+@I!&gV)e?LCFjviY7GF@)8O$#2j0i`=M?bEbrLRu)t^G$GI{P-Y=5PGpG8d!&fYHvx5S#?NNKvP|2(90EEU8@c=Ls z1}DB`*@XkIso?NHl@<2DXjVLmB6d+oTuU!S%C6Ef3E;H`X(J(-cw$~z3Y=4J6BRhn zAS@ZE4Cs9?=1NO(sxnRj>|T2mP+^ng3babrEqGdatS?uC(9#9tB=Z4JXDN;n2z+ed zmX-)zvyB55TH>$I?`B4XvS`0yq2P6|`lLTw!F!dr2j9AZ8t z`M-Qn)Pn;G>kV<>l6yQlgm4i<9VpdYk7vR<4MWdF>ZrH{2!d zxyQ3x_E5xdw|qdZ=W|@R21ZCuaStsQ)Ft@m8jIF>a#geRUUAh6_IUMbmPNek)vnI< z>eH!jc-6P9x!>!Be%tb^7ls|8`}&Q0^+x))5AWIc(u_YAG4j%4I(OfInIqq;TndC^yrkZ+vKeMXDA5mg#=AG_W%>URlP&ah#3v4 z#xeJghu7apvO}gpVHiC&8%EP@-2xzqRxS+O#sPwIekx~aNI7pp&Tzd4T)VXBHY^Ak z-WpW{&wiVH;0sAE*yS2HP!1kpo(rQqjhKI-dibPH<7i;GQY&Tlg5^^RL`Z-ZFh&kr z9E_m=0g_wPGz#U!fH9pHsT$3*SSH_m^;yog)>hbYBdIMl5yP5Q+$3U`!B`l}9;=QJ z^UJ^i`A#=v0A_eykSP9c;nqx3hdAC-HJS8&`CbYU$`o<<2)lL~LEhrf0DqYB;$u`p?!9*5H6z#+PbxZu34fXZ`rz;fEKHaP~A(!fzMnVma+ z0?QfPlzB~5HDlsbrgLVNun*+LoZYbcw$^LN?|;Mh1<=eoF6tSim5HDB?|K$xDsyVA>eb0EO{C>z~X8Cad^WP~~h_ z=nl!e{ImmhAI_iUSiwsuVkfv4vk*MSFZ6WcY5;K0wbWi|1?yt#QJpGB0pxiQlT0oI z&^-kyhJcSEqr5q zA-82t)7VlSyLwwBL(f%nJI>WJZ{5z1MVH|PIT;idmfT6uO!KaPesG}h(*4&L#U}E1 zvqlRq3q9ErOaTDZp9sJj-~)O9APQ`Uj{zC#ng-j{_0+YDG`DTn*k(mCv>)=4!@Q|I6 z;f^X=F3OszGB`@~Norh_T~dt0nZ#Y^lg&;onkO%qpIO~SOL5Lfbzz=&&8F?i&u}kd zdR)%+D9`h(EZBFgc>m4P1GldDG+sT_#5&wkW)V@SQsesX*u*LJvLkP)!2T1Pus?2pSQUE+8lTQN`JfF?0RMqer)hs<-i;bPtT$I$S| z?a@19cgOGDfAH}B`Fv+vO142B>44Z%0Iqa7Pec0p;H^g=K7LyK{N?Mn??0AmJJqlN z!h@%g?O)7H5CJ$@Lq~5CH;kPltAVY7LiVAVvW61O_-3ss%`G>9+K_>k(>D&2H(lqJ zfEhQB$;CaKVFOZmFf~&apUl6SF0GE|6XLrNxc!;-@?n}pItI|t)1rHBtGe67?8#MI zalYOFi65!G44Zc6C1fpK5ABZ!L6$7YAX*Pk^nh}*6(6MuJzG%q!K-dr-e_;0y2jI9 zab8}C%2I{(Ck!$tdf*B>9R&P^2|8CnSq%>mySVI2(nQ4`k>-)f&7t})AK$#x_WH@D zZSms*5}bvWMx-n<9edakVq$4COjUq;5kRE%rvQj-o#mV@A&o8 z!qknEt2D+qUvca2KKbLy6=++Dkwh$ne1wRN)~gnA@HR7|bb=dcf-4&sF_EFTrFtS$ zb;ry^7BQ1FnXOY2F_}ZEs-Da>K0h;=NA4p{?D#nQ zvPmdrG$-hlkDhDp*Q=Xr8MJvhcXr6lXufqcFna#n_?Ei)^AC5toNp6l8og?tE{T41 zVWz6?)y28-^$Xp2+qs1v zg1hmXUfDG8q#_x1+)n(@0qtGhAp zZc?7tzq{r5Y3|)0RFuj4Ayjuy9Q*NJ#F>VV_hZ`TKR$@RX7cG_(%slkk5ZmDe0rSrY5vm_7Rq!{#8rq} zoXFO1T%63eeYH5nb2t4wUA!jl^V8C;jh~;D?R@q5IX}zv%S`3rIHDO=i_IA+r^|yCJ?q=WLkFMGF z{loayeN5snQ*8ay6x&0l7z1DdE@X;L$#$03PImTmXR15HckQ~+@XcFeIxly2b$4IwyEbtB#?4!|h9C z(8-@-0%-3un!iL;|0$v~BqA320no+(3@w{dCu-OSN3LI&66;F`@G8#vGi1%HId~(9 zjtFz@z6@ogm!bhJfck3y)1L#l{5eMe?FbEk5XF{tCBR|E7FUP-yEDYkWkg${wOp8w zd*@psumtniLQ3aB8)MmP%-0V;Oe_k@d)T!){lx^ruL1A=9MJv$KqASX19<->k@-)F z7G9Tht%D@G*tZZ2Nu*(u6!FK2?i}FbXBJRc-kS|~u*c^l@C`8}T?FEn816quVf-n^ z&$ZLlGbia=8Ivd$M$Xp8bc%_aquGjO=BudYt7zs7PwREQl<-ieM5c3cI5joKg`MP< zw|7Me$LCnqN`5}$%zoeMgR7ek`8WT|iwtQ(B<2R?_*9fOD3yRQI4dcg-ndEx`K7!h z28Iff)k05#-7^g#24UB!)&%-534dMm0e{WY@L$i9LeO%_kuqA@o8r644bpS=f9YBE z=LFaOIl**jf`-P{=9Uf=d%CwzKyXM{R7_&o@$##E{qNqr|7V#I`UH<0Lwk{b#YH;c zC(HemK?6$pS5xPEGZE&4_pMhM4Z_n!R08amsK35z{a>Qm{UvIz#4Bh??&n8biV{Vo zhy!9#fV^*Ow(lmF0wvq^_|LvZY^i%S%ze#RlMu8-UWaeh% z=H(X_79EC;4|H}K8k>I~jlTm2gdV};NR;6e%eQz<4liZ^1$vT;pP&PuQ5wy?#`?$3 z9rXXiVUmA;)ux|cwe>H-|ED@u`T6hBe@Y6VEune;tJ6hbIqfDW*?T|Vw4pCU4&_%? zU8vKaCGF?Mko$cmR{R_%{!cOf+>AyBwx)&-&}Lk28Q^WZewFP;KT3G8V_c|H0@FDO z+Jh1Fy)iDFG_OPZR+Msmj%TkbFYv1=^sE1Od+;wMAq^oz!q^91*;{F`Z#HJ9&}R|-`1NI2zo0B4IW zruuPeS-+Na&Cdy@{y9Mov>Ck}ErVUHi?=$R+P3WSKGz#r%b`68S3_er#d)K*&%!o) z@BVNe2Qg4V8P5U|Nh)ITyx;Jb@^hp;f6DSxRfYy;CPrjib8|;ab1z5BH7?fc-EE>) zQc?r$i#9tQi*|;-p>e*%)Tq;BkaFABSS*i_7W-{%f9Z+yw`C-e%*ZBY7N%sHjfI#cbf z=mOdD?8s%J-K)u3p>1MkOW-Y&+=Qdv8{;0@fve;wph}XLlUZ40Y zzW-<1)v%V0scml(o)>bx)PWu|st9e_t=Y2+jK!ydjr$8I?&~pgum9I>TmipF|czm^JGvqu5*mrxGZ@S zbth!8V_nmCxb0`HD1{(H9^*6w8H!dn9u7GB9~JfwhUuqO01t`#@SKzFMkADi{7?Y0CuM6Pv+ z{VnHK+U`jDUI^N-RvgJ($B{fw<)gZDedZ=)M< zRPQm8E39JaPq++YN;eTn*|L5*&`cr%V^F#3K44sAk9R#sJ#$Q*$Ps?jU zhTb25E(XZ{E~i+maI|DNTlv$hpsQzthfSEL%~l^uq>pW+Hzk%qNrWt3pxvGz`}B}M z?Ox$#r$Z6W&=pd?ZCQC7wKASspF|Vvq_w8fAj1vW!ydNl^$gcr*?*eEhdg&Ey>qJA zW9G2u%Ojrej{j@J_z$c34xl!D-#D|>`x9=CwVmlH;vctxG}%$il{0w zW+XePPCDjh+@(8ncSy+Hg&s|7auCsPZpZE=v$h$>k+rq;$q`x6hPiR3J5A$urNtTN z7)9%58YM-U=46>@>l`vp<8Wj2v`JY<^9oMyi)w1tH;XpP)HBJ~E{lyXFzTwv=}5@Y zHtEytFvyG$&Tb1V18p1T$>dpS&8kT1tG1_f$z^7XksCXy6QUum%!xVm@navNnq`!jjh~OT zlaAE{7?h3^<=wA)l!;(9QfK(^o|djU=yRY&l-u>mcu$ z94j2n{E*Wa>&G9W=IzJIs#A$;B+|_j#EoL^W{T;QQr&mQl!;*2w-oS5z{&YHFtmPr zcu}ZkF{5isd2-8AAMmW}npif83)~4iEq5+7RyJuYli~XL{Xw(=iF5~Lx**miCf`c* z-$!jnpcR4-_+s7&wZStz@W0uPGX$Zk0)6g4h5*1_^J<2qgJx zH!#_R9p^mhIQ19+RU~;o^=?9ot2Uj3OC8fl71=FSPbtB|eH`9@r20l&w@|$cW z4XJQy_&y~u1wJOUdB=mb=`o7YI5U49J6ffR7sAh6u6zTj62qq#MIn&z^`sDC7CMj2 zSbBbZYjQkHZ4I*e%;vI)JmaE0DMN=jXGd<_K)VHQ&$1;XG4v2-M2F~x7*|1N-OvRx z#I{Z@e}UGD`3mBi9+ytxI!)621+H3s61?5Tj0F@wIEP-WM;_=yS)BEx;Zkp5(rfFx zsqd5t+≷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 literal 0 HcmV?d00001 From 1b8dbe9f0d654c6174b6d995ff0e4c6212b9fd58 Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 08:44:53 +0300 Subject: [PATCH 32/92] Drop: change_to_root parm from from-xprivate_key and from_xpublic_key functions. --- hdwallet/hdwallet.py | 54 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 6813201..1c96472 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -1,5 +1,21 @@ #!/usr/bin/env python3 +""" +Copyright © 2021, Meheret Tesfaye Batu + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +""" + from ecdsa.curves import SECP256k1 from ecdsa.ellipticcurve import Point from ecdsa.keys import ( @@ -218,7 +234,7 @@ def from_seed(self, seed: str) -> "HDWallet": self.from_path(path=self._path_class) return self - def from_xprivate_key(self, xprivate_key: str, strict: bool = False, change_to_root: bool = False) -> "HDWallet": + def from_xprivate_key(self, xprivate_key: str, strict: bool = False) -> "HDWallet": """ Master from XPrivate Key. @@ -226,8 +242,6 @@ def from_xprivate_key(self, xprivate_key: str, strict: bool = False, change_to_r :type xprivate_key: str :param strict: Strict for must be root xprivate key, default to ``False``. :type strict: bool - :param change_to_root: Non root xprivate key change to root xprivate key, default to ``False``. - :type change_to_root: bool :returns: HDWallet -- Hierarchical Deterministic Wallet instance. @@ -243,16 +257,11 @@ def from_xprivate_key(self, xprivate_key: str, strict: bool = False, change_to_r raise ValueError("Invalid root xprivate key.") _deserialize_xprivate_key = self._deserialize_xprivate_key(xprivate_key=xprivate_key) - if change_to_root: - self._root_depth, self._root_parent_fingerprint, self._root_index = ( - 0, b"\0\0\0\0", 0 - ) - else: - self._root_depth, self._root_parent_fingerprint, self._root_index = ( - int.from_bytes(_deserialize_xprivate_key[1], "big"), - _deserialize_xprivate_key[2], - struct.unpack(">L", _deserialize_xprivate_key[3])[0] - ) + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + int.from_bytes(_deserialize_xprivate_key[1], "big"), + _deserialize_xprivate_key[2], + struct.unpack(">L", _deserialize_xprivate_key[3])[0] + ) self._depth, self._parent_fingerprint, self._index = ( int.from_bytes(_deserialize_xprivate_key[1], "big"), _deserialize_xprivate_key[2], @@ -270,7 +279,7 @@ def from_xprivate_key(self, xprivate_key: str, strict: bool = False, change_to_r self._public_key = self.compressed() return self - def from_xpublic_key(self, xpublic_key: str, strict: bool = False, change_to_root: bool = False) -> "HDWallet": + def from_xpublic_key(self, xpublic_key: str, strict: bool = False) -> "HDWallet": """ Master from XPublic Key. @@ -278,8 +287,6 @@ def from_xpublic_key(self, xpublic_key: str, strict: bool = False, change_to_roo :type xpublic_key: str :param strict: Strict for must be root xpublic key, default to ``False``. :type strict: bool - :param change_to_root: Non root xprivate key change to root xprivate key, default to ``False``. - :type change_to_root: bool :returns: HDWallet -- Hierarchical Deterministic Wallet instance. @@ -295,16 +302,11 @@ def from_xpublic_key(self, xpublic_key: str, strict: bool = False, change_to_roo raise ValueError("Invalid root xpublic key.") _deserialize_xpublic_key = self._deserialize_xpublic_key(xpublic_key=xpublic_key) - if change_to_root: - self._root_depth, self._root_parent_fingerprint, self._root_index = ( - 0, b"\0\0\0\0", 0 - ) - else: - self._root_depth, self._root_parent_fingerprint, self._root_index = ( - int.from_bytes(_deserialize_xpublic_key[1], "big"), - _deserialize_xpublic_key[2], - struct.unpack(">L", _deserialize_xpublic_key[3])[0] - ) + self._root_depth, self._root_parent_fingerprint, self._root_index = ( + int.from_bytes(_deserialize_xpublic_key[1], "big"), + _deserialize_xpublic_key[2], + struct.unpack(">L", _deserialize_xpublic_key[3])[0] + ) self._depth, self._parent_fingerprint, self._index = ( int.from_bytes(_deserialize_xpublic_key[1], "big"), _deserialize_xpublic_key[2], From 8eba5bde2016f3b9b4dd29ea1e8cf1ef2a7343ed Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 09:26:56 +0300 Subject: [PATCH 33/92] Add: click, click-aliases and tabulate packages. --- requirements.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 477b76d..855ec06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ ecdsa>=0.13,<1 mnemonic>=0.19,<1 pysha3>=1.0.2,<2 -base58>=2.0.1,<3 \ No newline at end of file +base58>=2.0.1,<3 +click>=8.0.3,<9 +click-aliases>=1.0.1,<2 +tabulate>=0.8.9,<1 \ No newline at end of file From 80725287e3df94d95af218a481e793ea278b21e7 Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 11:53:00 +0300 Subject: [PATCH 34/92] Update: cryptocurrencies.rst and added CLI documentation. --- docs/cli.rst | 24 ++++++++++++++++++++++++ docs/conf.py | 14 +++++++++----- docs/cryptocurrencies.rst | 6 +++--- docs/index.rst | 2 ++ docs/toctree.rst | 1 + 5 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 docs/cli.rst diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 0000000..c32061f --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,24 @@ +============================ +Command Line Interface (CLI) +============================ + +After you have installed, type ``hdwallet`` to verify that it worked: + +:: + + $ hdwallet + Usage: hdwallet [OPTIONS] COMMAND [ARGS]... + + Options: + -v, --version Show HDWallet version and exit. + -h, --help Show this message and exit. + + Commands: + generate (g) Select Generate for HDWallet. + list (l) Select List for HDWallet information. + + + +.. click:: hdwallet.cli.__main__:main + :prog: hdwallet + :show-nested: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 0c5a95b..ec1eb26 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,9 @@ import sys import datetime +from hdwallet import ( + __version__, __author__ +) sys.path.insert(0, os.path.abspath("../..")) sys.path.insert(1, os.path.abspath("./extensions")) @@ -22,11 +25,11 @@ # -- Project information ----------------------------------------------------- project = "HDWallet" -copyright = f"{datetime.datetime.now().year}, Meheret Tesfaye Batu" -author = "Meheret Tesfaye" +copyright = f"{datetime.datetime.now().year}, {__author__}" +author = __author__ # The full version, including alpha/beta/rc tags -release = "1.3.2" +release = __version__ # The master toctree document. master_doc = "toctree" @@ -37,7 +40,8 @@ # extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ - "sphinx.ext.autodoc" + "sphinx.ext.autodoc", + "sphinx_click.ext" ] # Add any paths that contain templates here, relative to this directory. @@ -76,7 +80,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["static", "static/css"] +html_static_path = ["static", "static/css", "static/gif"] # Autodoc member order autodoc_member_order = "bysource" diff --git a/docs/cryptocurrencies.rst b/docs/cryptocurrencies.rst index 8165035..3e1f988 100644 --- a/docs/cryptocurrencies.rst +++ b/docs/cryptocurrencies.rst @@ -977,9 +977,9 @@ This library simplifies the process of generating a new HDWallet's for: - 147 - m/44'/147'/0'/0/0 * - Zcash - - ZEC + - ZEC, ZECTEST + - Yes - Yes - - No - No - 133 - m/44'/133'/0'/0/0 @@ -991,4 +991,4 @@ This library simplifies the process of generating a new HDWallet's for: - 121 - m/44'/121'/0'/0/0 -**NOTICE:** All Cryptocurrencies testnet networks default paths are set to **`m/44'/1'/0'/0/0`** value. +**NOTICE:** All Cryptocurrencies testnet networks default paths are set to ``m/44'/1'/0'/0/0`` value. diff --git a/docs/index.rst b/docs/index.rst index 66289d8..a0062e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,6 +5,8 @@ Hierarchical Deterministic Wallet Python-based library for the implementation of a hierarchical deterministic wallet generator for over 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. +.. image:: static/gif/hdwallet.gif + :alt: HDWallet CLI For more info see the BIP specs. diff --git a/docs/toctree.rst b/docs/toctree.rst index 152b506..0dc203b 100644 --- a/docs/toctree.rst +++ b/docs/toctree.rst @@ -7,6 +7,7 @@ HDWallet Overview install.rst + cli.rst Cryptocurrencies .. toctree:: From f54091adcb785c6d3b81c6d13ed006ef3f69ff13 Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 12:01:30 +0300 Subject: [PATCH 35/92] Bump: HDWallet from v1.3.2 to v2.0.0 package. --- README.md | 5 +++-- hdwallet/__init__.py | 2 +- setup.py | 29 ++++++++++++++++++----------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7775906..2c9144d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Hierarchical Deterministic Wallet [![Build Status](https://travis-ci.org/meherett/python-hdwallet.svg?branch=master)](https://travis-ci.org/meherett/python-hdwallet?branch=master) @@ -10,6 +9,8 @@ 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 chain. + + For more info see the BIP specs. | BIP's | Titles | @@ -40,7 +41,7 @@ For the versions available, see the [tags on this repository](https://github.com ## Quick Usage -Simple Bitcoin mainnet hierarchical deterministic wallet generator: +Simple Bitcoin mainnet HDWallet generator: ```python #!/usr/bin/env python3 diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index 353c1e0..fea9620 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -5,7 +5,7 @@ ) # HDWallet Information's -__version__: str = "v2.0.0a1" +__version__: str = "v2.0.0" __license__: str = "ISCL" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett@zoho.com" diff --git a/setup.py b/setup.py index 8d17455..890e5d2 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ setup, find_packages ) +import hdwallet + # README.md with open("README.md", "r", encoding="utf-8") as readme: long_description: str = readme.read() @@ -14,27 +16,32 @@ setup( name="hdwallet", - version="1.3.2", - description="Python-based library for the implementation of a " - "hierarchical deterministic wallet generator for more than 140+ multiple cryptocurrencies.", + version=hdwallet.__version__, + description=hdwallet.__description__, long_description=long_description, long_description_content_type="text/markdown", - license="ISCL", - author="Meheret Tesfaye", - author_email="meherett@zoho.com", + license=hdwallet.__license__, + author=hdwallet.__author__, + author_email=hdwallet.__email__, url="https://github.com/meherett/python-hdwallet", - keywords=["cryptography", "hd", "bip32", "bitcoin", "bip44", "bip39", "wallet", "hdwallet", "cryptocurrencies"], + keywords=[ + "cryptography", "cli", "wallet", "bip32", "bip44", "bip39", "hdwallet", "cryptocurrencies", "bitcoin", "ethereum" + ], + entry_points={ + "console_scripts": ["hdwallet=hdwallet.cli.__main__:main"] + }, python_requires=">=3.6,<4", packages=find_packages(), install_requires=requirements, extras_require={ "tests": [ - "pytest>=6.2.2,<7", - "pytest-cov>=2.11.1,<3" + "pytest>=6.2.5,<7", + "pytest-cov>=3.0.0,<4" ], "docs": [ - "sphinx>=3.5.1,<4", - "sphinx-rtd-theme>=0.5.1,<1" + "sphinx>=4.2.0,<5", + "sphinx-rtd-theme>=1.0.0,<2", + "sphinx-click>=3.0.1,<4" ] }, classifiers=[ From 04d1287cb5cf6dd60798482d8cfd53f1d37cf392 Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 19 Oct 2021 12:27:07 +0300 Subject: [PATCH 36/92] Bump: HDWallet from v1.3.2 to v2.0.0 package. --- setup.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 890e5d2..bb9011f 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,6 @@ setup, find_packages ) -import hdwallet - # README.md with open("README.md", "r", encoding="utf-8") as readme: long_description: str = readme.read() @@ -16,13 +14,14 @@ setup( name="hdwallet", - version=hdwallet.__version__, - description=hdwallet.__description__, + version="v2.0.0", + description="Python-based library for the implementation of a hierarchical deterministic wallet " + "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, long_description_content_type="text/markdown", - license=hdwallet.__license__, - author=hdwallet.__author__, - author_email=hdwallet.__email__, + license="ISCL", + author="Meheret Tesfaye Batu", + author_email="meherett@zoho.com", url="https://github.com/meherett/python-hdwallet", keywords=[ "cryptography", "cli", "wallet", "bip32", "bip44", "bip39", "hdwallet", "cryptocurrencies", "bitcoin", "ethereum" From c682fc3cae7c913a0ca34ddb8c8cbd1bb3686253 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 23 Oct 2021 18:00:52 +0300 Subject: [PATCH 37/92] Add: get_semantic function for cryptocurrencies. --- hdwallet/utils.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/hdwallet/utils.py b/hdwallet/utils.py index 652e6c0..55c6b21 100644 --- a/hdwallet/utils.py +++ b/hdwallet/utils.py @@ -7,11 +7,15 @@ import string import os +import inspect import unicodedata import binascii +from hdwallet import cryptocurrencies from .exceptions import SemanticError -from .cryptocurrencies import get_cryptocurrency +from .cryptocurrencies import ( + get_cryptocurrency, Cryptocurrency +) from .libs.base58 import check_decode # Alphabet and digits. @@ -25,6 +29,20 @@ def _unhexlify(integer: int): return unhexlify("%x" % integer) +def get_semantic(_cryptocurrency: Cryptocurrency, version: bytes, key_type: str) -> str: + for name, cryptocurrency in inspect.getmembers(cryptocurrencies): + if inspect.isclass(cryptocurrency): + if issubclass(cryptocurrency, cryptocurrencies.Cryptocurrency) and cryptocurrency == _cryptocurrency: + if key_type == "private_key": + for key, value in inspect.getmembers(cryptocurrency.EXTENDED_PRIVATE_KEY): + if value == int(version.hex(), 16): + return key.lower() + elif key_type == "public_key": + for key, value in inspect.getmembers(cryptocurrency.EXTENDED_PUBLIC_KEY): + if value == int(version.hex(), 16): + return key.lower() + + def get_bytes(string: AnyStr) -> bytes: if isinstance(string, bytes): byte = string From 3700f1f4b3c94542f16b50eb4537e3ef5563bcce Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 23 Oct 2021 18:50:30 +0300 Subject: [PATCH 38/92] Add: Auto semantic selection for is_root_keys functions. --- hdwallet/utils.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/hdwallet/utils.py b/hdwallet/utils.py index 55c6b21..969c55f 100644 --- a/hdwallet/utils.py +++ b/hdwallet/utils.py @@ -12,7 +12,6 @@ import binascii from hdwallet import cryptocurrencies -from .exceptions import SemanticError from .cryptocurrencies import ( get_cryptocurrency, Cryptocurrency ) @@ -319,39 +318,31 @@ def mnemonic_to_entropy(mnemonic: str, language: Optional[str] = None) -> str: return Mnemonic(language=language).to_entropy(mnemonic).hex() -def is_root_xprivate_key(xprivate_key: str, symbol: str, semantic: str = "p2pkh") -> bool: - if semantic not in ["p2pkh", "p2sh", "p2wpkh", "p2wpkh_in_p2sh", "p2wsh", "p2wsh_in_p2sh"]: - raise SemanticError( - "Wrong extended semantic", - "choose only the following options 'p2pkh', 'p2sh', 'p2wpkh', 'p2wpkh_in_p2sh', 'p2wsh' or 'p2wsh_in_p2sh' semantics." - ) - decoded_xprivate_key = check_decode(xprivate_key).hex() - if len(decoded_xprivate_key) != 156: # 78 +def is_root_xprivate_key(xprivate_key: str, symbol: str) -> bool: + decoded_xprivate_key = check_decode(xprivate_key) + if len(decoded_xprivate_key) != 78: # 78, 156 raise ValueError("Invalid xprivate key.") cryptocurrency = get_cryptocurrency(symbol=symbol) + semantic = get_semantic(_cryptocurrency=cryptocurrency, version=decoded_xprivate_key[:4], key_type="private_key") version = cryptocurrency.EXTENDED_PRIVATE_KEY.__getattribute__( semantic.upper() ) if version is None: raise NotImplementedError(semantic) raw = f"{_unhexlify(version).hex()}000000000000000000" - return decoded_xprivate_key.startswith(raw) + return decoded_xprivate_key.hex().startswith(raw) -def is_root_xpublic_key(xpublic_key: str, symbol: str, semantic: str = "p2pkh") -> bool: - if semantic not in ["p2pkh", "p2sh", "p2wpkh", "p2wpkh_in_p2sh", "p2wsh", "p2wsh_in_p2sh"]: - raise SemanticError( - "Wrong extended semantic", - "choose only the following options 'p2pkh', 'p2sh', 'p2wpkh', 'p2wpkh_in_p2sh', 'p2wsh' or 'p2wsh_in_p2sh' semantics." - ) - decoded_xpublic_key = check_decode(xpublic_key).hex() - if len(decoded_xpublic_key) != 156: # 78 +def is_root_xpublic_key(xpublic_key: str, symbol: str) -> bool: + decoded_xpublic_key = check_decode(xpublic_key) + if len(decoded_xpublic_key) != 78: # 78, 156 raise ValueError("Invalid xpublic key.") cryptocurrency = get_cryptocurrency(symbol=symbol) + semantic = get_semantic(_cryptocurrency=cryptocurrency, version=decoded_xpublic_key[:4], key_type="public_key") version = cryptocurrency.EXTENDED_PUBLIC_KEY.__getattribute__( semantic.upper() ) if version is None: raise NotImplementedError(semantic) raw = f"{_unhexlify(version).hex()}000000000000000000" - return decoded_xpublic_key.startswith(raw) + return decoded_xpublic_key.hex().startswith(raw) From 79ee9dbeed613913909dd445cd21e8433468eb5b Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 09:09:09 +0300 Subject: [PATCH 39/92] Add: Required parameter on symbol command. --- hdwallet/cli/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hdwallet/cli/__main__.py b/hdwallet/cli/__main__.py index bf757c2..728afa1 100644 --- a/hdwallet/cli/__main__.py +++ b/hdwallet/cli/__main__.py @@ -34,7 +34,7 @@ def main(): @main.group("generate", aliases=["g"], cls=ClickAliasedGroup, options_metavar="[OPTIONS]", short_help="Select Generate for HDWallet.", invoke_without_command=True) -@click.option("-s", "--symbol", type=str, default="BTC", +@click.option("-s", "--symbol", type=str, required=True, help="Set Cryptocurrency ticker symbol.") @click.option("-sg", "--strength", type=int, default=128, help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) From 27b22de8feb779b2c0c351011a5a9a72ad50a998 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 09:20:49 +0300 Subject: [PATCH 40/92] Drop: Required parameter from symbol command. --- hdwallet/cli/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hdwallet/cli/__main__.py b/hdwallet/cli/__main__.py index 728afa1..71b1d60 100644 --- a/hdwallet/cli/__main__.py +++ b/hdwallet/cli/__main__.py @@ -34,7 +34,7 @@ def main(): @main.group("generate", aliases=["g"], cls=ClickAliasedGroup, options_metavar="[OPTIONS]", short_help="Select Generate for HDWallet.", invoke_without_command=True) -@click.option("-s", "--symbol", type=str, required=True, +@click.option("-s", "--symbol", type=str, default="BTC", help="Set Cryptocurrency ticker symbol.") @click.option("-sg", "--strength", type=int, default=128, help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) @@ -118,7 +118,7 @@ def generate( @generate.command("addresses", aliases=["a"], options_metavar="[OPTIONS]", short_help="Select Addresses for generation HDWallet addresses.") -@click.option("-s", "--symbol", type=str, required=True, +@click.option("-s", "--symbol", type=str, default="BTC", help="Set Cryptocurrency ticker symbol.") @click.option("-sg", "--strength", type=int, default=128, help="Set Strength for entropy, choose strength 128, 160, 192, 224 or 256 only.", show_default=True) From 6bb642792c0eeb83553e5299b1ad8e2802a23ae6 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 19:48:24 +0300 Subject: [PATCH 41/92] Fix: Semantic auto-selection for xprivate and xpublic keys. --- hdwallet/hdwallet.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 1c96472..ead4016 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -62,7 +62,7 @@ ) from .utils import ( get_bytes, is_entropy, is_mnemonic, get_entropy_strength, _unhexlify, is_root_xpublic_key, - get_mnemonic_language, is_root_xprivate_key, get_mnemonic_strength + get_mnemonic_language, is_root_xprivate_key, get_mnemonic_strength, get_semantic ) MIN_ENTROPY_LEN: int = 128 @@ -94,7 +94,7 @@ class HDWallet: """ def __init__(self, symbol: str = "BTC", cryptocurrency: Any = None, - semantic: str = "p2pkh", use_default_path: bool = False): + semantic: Optional[str] = None, use_default_path: bool = False): self._cryptocurrency: Any = None if cryptocurrency: if not issubclass(cryptocurrency, Cryptocurrency): @@ -167,6 +167,8 @@ def from_entropy(self, entropy: str, language: str = "english", passphrase: str mnemonic = Mnemonic(language=self._language).to_mnemonic(data=self._entropy) self._mnemonic = unicodedata.normalize("NFKD", mnemonic) self._seed = Mnemonic.to_seed(mnemonic=self._mnemonic, passphrase=self._passphrase) + if self._semantic is None: + self._semantic = "p2pkh" return self.from_seed(seed=hexlify(self._seed).decode()) def from_mnemonic(self, mnemonic: str, language: str = None, passphrase: str = None) -> "HDWallet": @@ -198,6 +200,8 @@ def from_mnemonic(self, mnemonic: str, language: str = None, passphrase: str = N self._entropy = Mnemonic(language=self._language).to_entropy(self._mnemonic) self._passphrase = str(passphrase) if passphrase else str() self._seed = Mnemonic.to_seed(mnemonic=self._mnemonic, passphrase=self._passphrase) + if self._semantic is None: + self._semantic = "p2pkh" return self.from_seed(seed=hexlify(self._seed).decode()) def from_seed(self, seed: str) -> "HDWallet": @@ -232,6 +236,8 @@ def from_seed(self, seed: str) -> "HDWallet": self._public_key = self.compressed() if self._from_class: self.from_path(path=self._path_class) + if self._semantic is None: + self._semantic = "p2pkh" return self def from_xprivate_key(self, xprivate_key: str, strict: bool = False) -> "HDWallet": @@ -277,6 +283,11 @@ def from_xprivate_key(self, xprivate_key: str, strict: bool = False) -> "HDWalle if self._from_class: self.from_path(path=self._path_class) self._public_key = self.compressed() + self._semantic = get_semantic( + _cryptocurrency=self._cryptocurrency, + version=_deserialize_xprivate_key[0], + key_type="private_key" + ) return self def from_xpublic_key(self, xpublic_key: str, strict: bool = False) -> "HDWallet": @@ -324,6 +335,11 @@ def from_xpublic_key(self, xpublic_key: str, strict: bool = False) -> "HDWallet" if self._from_class: self.from_path(path=self._path_class) self._public_key = self.compressed() + self._semantic = get_semantic( + _cryptocurrency=self._cryptocurrency, + version=_deserialize_xpublic_key[0], + key_type="public_key" + ) return self def from_wif(self, wif: str) -> "HDWallet": @@ -557,11 +573,15 @@ def root_xprivate_key(self, encoded: bool = True) -> Optional[str]: "xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF" """ + if self._semantic is None: + return None version = self._cryptocurrency.EXTENDED_PRIVATE_KEY.__getattribute__( self._semantic.upper() ) if version is None: - raise NotImplementedError(self) + raise NotImplementedError( + f"{self.__class__.__name__} is not implemented for {self._cryptocurrency.NAME} {self._cryptocurrency.NETWORK} cryptocurrency." + ) if not self._i: return None secret_key, chain_code = self._i[:32], self._i[32:] @@ -591,11 +611,15 @@ def root_xpublic_key(self, encoded: bool = True) -> Optional[str]: "xpub661MyMwAqRbcGSTjb2Mp3Sb4STUDhD2x986ubXKjQa2QsFTCVqzdA98qeZjcncHT1AaZcMSjiP1HJ16jH97q72RwyFfiNhmG8zQ6KBB5PaQ" """ + if self._semantic is None: + return None version = self._cryptocurrency.EXTENDED_PUBLIC_KEY.__getattribute__( self._semantic.upper() ) if version is None: - raise NotImplementedError(self) + raise NotImplementedError( + f"{self.__class__.__name__} is not implemented for {self._cryptocurrency.NAME} {self._cryptocurrency.NETWORK} cryptocurrency." + ) if self._root_public_key: data, chain_code = ( self._root_public_key[0], self._root_public_key[1] @@ -630,11 +654,15 @@ def xprivate_key(self, encoded=True) -> Optional[str]: "xprvA3BYGWQ9FmhyaNRRXB2f1LphNPnaY9T6gngw4BaTbkFtscSH4RCuJhgWUSKs9S6ciGioHd4TX4UeyUg53MkfN9Xh38xkS1j2Wb9YKsYpJHQ" """ + if self._semantic is None: + return None version = self._cryptocurrency.EXTENDED_PRIVATE_KEY.__getattribute__( self._semantic.upper() ) if version is None: - raise NotImplementedError(self) + raise NotImplementedError( + f"{self.__class__.__name__} is not implemented for {self._cryptocurrency.NAME} {self._cryptocurrency.NETWORK} cryptocurrency." + ) depth = bytes(bytearray([self._depth])) parent_fingerprint = self._parent_fingerprint index = struct.pack(">L", self._index) @@ -664,11 +692,15 @@ def xpublic_key(self, encoded: bool = True) -> Optional[str]: "xpub6GAtg1w369GGnrVtdCZfNUmRvRd4wcAx41cXrZz5A5nskQmRbxX9rVzzKiRU4JruirBrfm4KQXNSU7GfqL1tzZWpZYe9Zo4xKGJYohWoQe7" """ + if self._semantic is None: + return None version = self._cryptocurrency.EXTENDED_PUBLIC_KEY.__getattribute__( self._semantic.upper() ) if version is None: - raise NotImplementedError(self) + raise NotImplementedError( + f"{self.__class__.__name__} is not implemented for {self._cryptocurrency.NAME} {self._cryptocurrency.NETWORK} cryptocurrency." + ) depth = bytes(bytearray([self._depth])) parent_fingerprint = self._parent_fingerprint index = struct.pack(">L", self._index) From 3993880d53780455257a857937492f67215e3650 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 19:51:20 +0300 Subject: [PATCH 42/92] Update: All private and public tester components. --- tests/hdwallet/test_from_private_key.py | 3 ++- tests/hdwallet/test_from_public_key.py | 3 ++- tests/hdwallet/test_from_wif.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/hdwallet/test_from_private_key.py b/tests/hdwallet/test_from_private_key.py index 7ab9451..4251a0e 100644 --- a/tests/hdwallet/test_from_private_key.py +++ b/tests/hdwallet/test_from_private_key.py @@ -47,7 +47,7 @@ def test_from_private_key(): assert hdwallet.public_key() == _["bitcoin"]["mainnet"]["public_key"] assert hdwallet.wif() == _["bitcoin"]["mainnet"]["wif"] assert hdwallet.finger_print() == _["bitcoin"]["mainnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["mainnet"]["semantic"] + assert hdwallet.semantic() is None assert hdwallet.path() == None assert hdwallet.hash() == _["bitcoin"]["mainnet"]["hash"] assert hdwallet.p2pkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2pkh"] @@ -71,6 +71,7 @@ def test_from_private_key(): dumps["root_xpublic_key"] = None dumps["xprivate_key"] = None dumps["xpublic_key"] = None + dumps["semantic"] = None dumps["chain_code"] = None dumps["path"] = None del dumps["root_xprivate_key_hex"] diff --git a/tests/hdwallet/test_from_public_key.py b/tests/hdwallet/test_from_public_key.py index bdf5c7c..214857f 100644 --- a/tests/hdwallet/test_from_public_key.py +++ b/tests/hdwallet/test_from_public_key.py @@ -50,7 +50,7 @@ def test_from_public_key(): assert hdwallet.public_key(compressed=False, private_key=_["bitcoin"]["testnet"]["private_key"]) == _["bitcoin"]["testnet"]["uncompressed"] assert hdwallet.wif() is None assert hdwallet.finger_print() == _["bitcoin"]["testnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["testnet"]["semantic"] + assert hdwallet.semantic() is None assert hdwallet.path() == None assert hdwallet.hash() == _["bitcoin"]["testnet"]["hash"] assert hdwallet.p2pkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2pkh"] @@ -74,6 +74,7 @@ def test_from_public_key(): dumps["root_xpublic_key"] = None dumps["xprivate_key"] = None dumps["xpublic_key"] = None + dumps["semantic"] = None dumps["chain_code"] = None dumps["private_key"] = None dumps["wif"] = None diff --git a/tests/hdwallet/test_from_wif.py b/tests/hdwallet/test_from_wif.py index 65c112f..8a4d3a0 100644 --- a/tests/hdwallet/test_from_wif.py +++ b/tests/hdwallet/test_from_wif.py @@ -47,7 +47,7 @@ def test_from_wallet_important_format(): assert hdwallet.public_key() == _["bitcoin"]["mainnet"]["public_key"] assert hdwallet.wif() == _["bitcoin"]["mainnet"]["wif"] assert hdwallet.finger_print() == _["bitcoin"]["mainnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["mainnet"]["semantic"] + assert hdwallet.semantic() is None assert hdwallet.path() == None assert hdwallet.hash() == _["bitcoin"]["mainnet"]["hash"] assert hdwallet.p2pkh_address() == _["bitcoin"]["mainnet"]["addresses"]["p2pkh"] @@ -71,6 +71,7 @@ def test_from_wallet_important_format(): dumps["root_xpublic_key"] = None dumps["xprivate_key"] = None dumps["xpublic_key"] = None + dumps["semantic"] = None dumps["chain_code"] = None dumps["path"] = None del dumps["root_xprivate_key_hex"] From d38c77b2bcaa8872b0c6249a087f5d15c5d25a77 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 20:17:10 +0300 Subject: [PATCH 43/92] Fix: Semantics and update HDWallet example components. --- examples/from_private_key.py | 2 -- examples/from_public_key.py | 1 - examples/from_wif.py | 2 -- examples/from_xprivate_key.py | 6 +++--- examples/from_xpublic_key.py | 6 +++--- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/examples/from_private_key.py b/examples/from_private_key.py index 05f5480..45c2364 100644 --- a/examples/from_private_key.py +++ b/examples/from_private_key.py @@ -25,8 +25,6 @@ print("Public Key:", hdwallet.public_key()) print("Wallet Important Format:", hdwallet.wif()) print("Finger Print:", hdwallet.finger_print()) -print("Semantic:", hdwallet.semantic()) -print("Path:", hdwallet.path()) print("Hash:", hdwallet.hash()) print("P2PKH Address:", hdwallet.p2pkh_address()) print("P2SH Address:", hdwallet.p2sh_address()) diff --git a/examples/from_public_key.py b/examples/from_public_key.py index 2aee74d..34cb53a 100644 --- a/examples/from_public_key.py +++ b/examples/from_public_key.py @@ -23,7 +23,6 @@ print("Compressed:", hdwallet.compressed()) print("Public Key:", hdwallet.public_key()) print("Finger Print:", hdwallet.finger_print()) -print("Semantic:", hdwallet.semantic()) print("Hash:", hdwallet.hash()) print("P2PKH Address:", hdwallet.p2pkh_address()) print("P2SH Address:", hdwallet.p2sh_address()) diff --git a/examples/from_wif.py b/examples/from_wif.py index 2c27e7b..4695317 100644 --- a/examples/from_wif.py +++ b/examples/from_wif.py @@ -25,8 +25,6 @@ print("Public Key:", hdwallet.public_key()) print("Wallet Important Format:", hdwallet.wif()) print("Finger Print:", hdwallet.finger_print()) -print("Semantic:", hdwallet.semantic()) -print("Path:", hdwallet.path()) print("Hash:", hdwallet.hash()) print("P2PKH Address:", hdwallet.p2pkh_address()) print("P2SH Address:", hdwallet.p2sh_address()) diff --git a/examples/from_xprivate_key.py b/examples/from_xprivate_key.py index fe9e65c..2b0316c 100644 --- a/examples/from_xprivate_key.py +++ b/examples/from_xprivate_key.py @@ -12,12 +12,12 @@ XPRIVATE_KEY: str = "xprv9s21ZrQH143K24t96gCaezzt1QQmnqiEGm8m6TP8yb8e3TmGfkCgcLEVss" \ "kufMW9R4KH27pD1kyyEfJkYz1eiPwjhFzB4gtabH3PzMSmXSM" # Bitcoin non-root xprivate key -# XPRIVATE_KEY: str = "xprvA3KRgVDh45mbQT1VmWPx73YeAWM4629Q2D9pMuqjFMnjTqDGhKiww6H532rg" \ -# "YRNj37fngd4Mvp7GfUD8rKeQzUZjCWeisT92tX8FfjWx3BL" +# XPRIVATE_KEY: str = "yprvAMZNWbcSVmxMiVoKgQuKmemTpEz8dJs3v8hmgkRVUjncqkXsgoxyqZ8rDb" \ +# "eXzMqRQZEsTcB4T5iQQx7WazLyy3KiHZrdcHo6DmGAibeMxQV" if STRICT: # Check root xprivate key - assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xprivate key." + assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC), "Invalid Root XPrivate Key." # Initialize Bitcoin mainnet HDWallet hdwallet: HDWallet = HDWallet(symbol=BTC) diff --git a/examples/from_xpublic_key.py b/examples/from_xpublic_key.py index 7197b55..1650b28 100644 --- a/examples/from_xpublic_key.py +++ b/examples/from_xpublic_key.py @@ -12,12 +12,12 @@ XPUBLIC_KEY: str = "xpub661MyMwAqRbcEqD3v24ZWHGDMqqAfbDbmnUFJXfbpxGZaAshq7evA7fB75CHFbNHSot" \ "LadDZw6M6ic4ZkdN6jQ2KMGR66Z2EybgdLFjNrpf" # Bitcoin non-root xpublic key -# XPUBLIC_KEY: str = "xpub6FbWJtnc3eJHBwfTqhaE9yQNkmi56UDy9Rm1pbhvuSSigr6xKihuFpnnf4jz8G9ba2m3wFaF" \ -# "Gj7eH7FE451Jo5hPJhbaCdmxoBwWbFzk1Sn" +# XPUBLIC_KEY: str = "zpub6uxKjJ8pnanQKU2betFrDPVmcVUvVgyAhgWS74iaN7yUE8RADoRRnztyVEQtnzi9Fh1Vp" \ +# "6iJ8RT6mMqjGnS6AxGjud3P2DLzpMHUw2zT1n2" if STRICT: # Check root xpublic key - assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=BTC, semantic="p2pkh"), "Invalid root xpublic key." + assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=BTC), "Invalid Root XPublic Key." # Initialize Bitcoin mainnet HDWallet hdwallet: HDWallet = HDWallet(symbol=BTC) From 1ecf26cb92d522a3a92038d64f594b5d3dc76f84 Mon Sep 17 00:00:00 2001 From: meherett Date: Sun, 24 Oct 2021 20:24:16 +0300 Subject: [PATCH 44/92] Bump: HDWallet from v2.0.0 to v2.0.1 package. --- hdwallet/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index fea9620..fb14e45 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -5,7 +5,7 @@ ) # HDWallet Information's -__version__: str = "v2.0.0" +__version__: str = "v2.0.1" __license__: str = "ISCL" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett@zoho.com" diff --git a/setup.py b/setup.py index bb9011f..622d58e 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="hdwallet", - version="v2.0.0", + version="v2.0.1", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, From 2fdf74b38f3082d39108906a1ff44e1691223428 Mon Sep 17 00:00:00 2001 From: alimohammad Date: Tue, 7 Dec 2021 13:34:00 +0330 Subject: [PATCH 45/92] add tron --- hdwallet/cryptocurrencies.py | 42 ++++++++++++++++++++++++++++++++++++ hdwallet/hdwallet.py | 7 ++++++ hdwallet/symbols.py | 3 +++ tests/test_symbols.py | 1 + 4 files changed, 53 insertions(+) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index d54f644..18810e6 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -2114,6 +2114,48 @@ class EthereumMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x80 +class TronMainnet(Cryptocurrency): + + NAME = "Tron" + SYMBOL = "TRX" + NETWORK = "mainnet" + SOURCE_CODE = "https://github.com/tronprotocol/java-tron" + + COIN_TYPE = CoinType({ + "INDEX": 195, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x05 + PUBLIC_KEY_ADDRESS = 0x41 + + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": "bc", + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x0488ade4, + "P2SH": 0x0488ade4, + "P2WPKH": 0x04b2430c, + "P2WPKH_IN_P2SH": 0x049d7878, + "P2WSH": 0x02aa7a99, + "P2WSH_IN_P2SH": 0x0295b005 + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x0488b21e, + "P2SH": 0x0488b21e, + "P2WPKH": 0x04b24746, + "P2WPKH_IN_P2SH": 0x049d7cb2, + "P2WSH": 0x02aa7ed3, + "P2WSH_IN_P2SH": 0x0295b43f + }) + + MESSAGE_PREFIX = None + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0x80 + + class EuropeCoinMainnet(Cryptocurrency): NAME = "Europe Coin" diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index ead4016..ee056e5 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -1111,6 +1111,13 @@ def p2pkh_address(self) -> str: 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.update(unhexlify(self.compressed())) + keccak_256.update(unhexlify(self.uncompressed())) + address = keccak_256.hexdigest()[24:] + network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + bytearray.fromhex(address) + return ensure_string(base58.b58encode_check(network_hash160_bytes)) compressed_public_key = unhexlify(self.compressed()) public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).digest() diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index 0f3dcbf..c51d469 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -246,6 +246,8 @@ TOA = "TOA" # Thought AI THT = "THT" +# TRX +TRX = "TRX" # Twins TWINS, TWINSTEST = "TWINS", "TWINSTEST" # Ultimate Secure Cash @@ -404,6 +406,7 @@ "SYS", "TOA", "THT", + "TRX", "TWINS", "TWINSTEST", "USC", "UNO", diff --git a/tests/test_symbols.py b/tests/test_symbols.py index 9177211..4d9236d 100644 --- a/tests/test_symbols.py +++ b/tests/test_symbols.py @@ -143,6 +143,7 @@ def test_symbols(): assert SYS == "SYS" assert TOA == "TOA" assert THT == "THT" + assert TRX == "TRX" assert TWINS == "TWINS" assert TWINSTEST == "TWINSTEST" assert USC == "USC" From 2449c3b77bc84722034310a5017cb790515e32e6 Mon Sep 17 00:00:00 2001 From: meherett Date: Fri, 24 Dec 2021 21:24:15 +0300 Subject: [PATCH 46/92] Add: Tron mainnet cryptocurrency by @alimohammad1995 user. --- README.md | 6 ++- docs/cryptocurrencies.rst | 7 +++ hdwallet/cryptocurrencies.py | 84 ++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2c9144d..bf66874 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://travis-ci.org/meherett/python-hdwallet.svg?branch=master)](https://travis-ci.org/meherett/python-hdwallet?branch=master) [![PyPI Version](https://img.shields.io/pypi/v/hdwallet.svg?color=blue)](https://pypi.org/project/hdwallet) [![Documentation Status](https://readthedocs.org/projects/hdwallet/badge/?version=master)](https://hdwallet.readthedocs.io/en/master/?badge=master) +[![PyPI License](https://img.shields.io/pypi/l/hdwallet?color=black)](https://pypi.org/project/hdwallet) [![PyPI Python Version](https://img.shields.io/pypi/pyversions/hdwallet.svg)](https://pypi.org/project/hdwallet) [![Coverage Status](https://coveralls.io/repos/github/meherett/python-hdwallet/badge.svg?branch=master)](https://coveralls.io/github/meherett/python-hdwallet?branch=master) @@ -48,7 +49,7 @@ Simple Bitcoin mainnet HDWallet generator: from hdwallet import HDWallet from hdwallet.utils import generate_entropy -from hdwallet.symbols import BTC +from hdwallet.symbols import BTC as SYMBOL from typing import Optional import json @@ -63,7 +64,7 @@ ENTROPY: str = generate_entropy(strength=STRENGTH) PASSPHRASE: Optional[str] = None # "meherett" # Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL, use_default_path=False) # Get Bitcoin HDWallet from entropy hdwallet.from_entropy( entropy=ENTROPY, language=LANGUAGE, passphrase=PASSPHRASE @@ -343,6 +344,7 @@ This library simplifies the process of creating a new hierarchical deterministic | Syscoin | `SYS` | Yes | No | Yes | 57 | `m/44'/57'/0'/0/0` | | TOA Coin | `TOA` | Yes | No | No | 159 | `m/44'/159'/0'/0/0` | | Thought AI | `THT` | Yes | No | No | 502 | `m/44'/502'/0'/0/0` | +| [Tron](https://github.com/tronprotocol/java-tron) | `TRX` | Yes | No | No | 195 | `m/44'/195'/0'/0/0` | | Twins | `TWINS`, `TWINSTEST` | Yes | Yes | No | 970 | `m/44'/970'/0'/0/0` | | Ultimate Secure Cash | `USC` | Yes | No | No | 112 | `m/44'/112'/0'/0/0` | | Unobtanium | `UNO` | Yes | No | No | 92 | `m/44'/92'/0'/0/0` | diff --git a/docs/cryptocurrencies.rst b/docs/cryptocurrencies.rst index 3e1f988..0ba3d29 100644 --- a/docs/cryptocurrencies.rst +++ b/docs/cryptocurrencies.rst @@ -878,6 +878,13 @@ This library simplifies the process of generating a new HDWallet's for: - No - 502 - m/44'/502'/0'/0/0 + * - `Tron `_ + - TRX + - Yes + - No + - No + - 195 + - m/44'/195'/0'/0/0 * - Twins - TWINS, TWINSTEST - Yes diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 18810e6..9ba2f2f 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -2114,48 +2114,6 @@ class EthereumMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x80 -class TronMainnet(Cryptocurrency): - - NAME = "Tron" - SYMBOL = "TRX" - NETWORK = "mainnet" - SOURCE_CODE = "https://github.com/tronprotocol/java-tron" - - COIN_TYPE = CoinType({ - "INDEX": 195, - "HARDENED": True - }) - - SCRIPT_ADDRESS = 0x05 - PUBLIC_KEY_ADDRESS = 0x41 - - SEGWIT_ADDRESS = SegwitAddress({ - "HRP": "bc", - "VERSION": 0x00 - }) - - EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ - "P2PKH": 0x0488ade4, - "P2SH": 0x0488ade4, - "P2WPKH": 0x04b2430c, - "P2WPKH_IN_P2SH": 0x049d7878, - "P2WSH": 0x02aa7a99, - "P2WSH_IN_P2SH": 0x0295b005 - }) - EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ - "P2PKH": 0x0488b21e, - "P2SH": 0x0488b21e, - "P2WPKH": 0x04b24746, - "P2WPKH_IN_P2SH": 0x049d7cb2, - "P2WSH": 0x02aa7ed3, - "P2WSH_IN_P2SH": 0x0295b43f - }) - - MESSAGE_PREFIX = None - DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" - WIF_SECRET_KEY = 0x80 - - class EuropeCoinMainnet(Cryptocurrency): NAME = "Europe Coin" @@ -5636,6 +5594,48 @@ class ThoughtAIMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x7b +class TronMainnet(Cryptocurrency): + + NAME = "Tron" + SYMBOL = "TRX" + NETWORK = "mainnet" + SOURCE_CODE = "https://github.com/tronprotocol/java-tron" + + COIN_TYPE = CoinType({ + "INDEX": 195, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x05 + PUBLIC_KEY_ADDRESS = 0x41 + + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": "bc", + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x0488ade4, + "P2SH": 0x0488ade4, + "P2WPKH": 0x04b2430c, + "P2WPKH_IN_P2SH": 0x049d7878, + "P2WSH": 0x02aa7a99, + "P2WSH_IN_P2SH": 0x0295b005 + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x0488b21e, + "P2SH": 0x0488b21e, + "P2WPKH": 0x04b24746, + "P2WPKH_IN_P2SH": 0x049d7cb2, + "P2WSH": 0x02aa7ed3, + "P2WSH_IN_P2SH": 0x0295b43f + }) + + MESSAGE_PREFIX = None + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0x80 + + class TwinsMainnet(Cryptocurrency): NAME = "Twins" From 0725492db4c03d89a1eead9f2ad6443fc74d58e7 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 07:47:02 +0300 Subject: [PATCH 47/92] Fix: Default path class bug of from_xpublic_key function. --- hdwallet/hdwallet.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index ee056e5..05a9c19 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -333,7 +333,7 @@ def from_xpublic_key(self, xpublic_key: str, strict: bool = False) -> "HDWallet" if self._use_default_path: self.from_path(path=self._cryptocurrency.DEFAULT_PATH) if self._from_class: - self.from_path(path=self._path_class) + self.from_path(path=str(self._path_class).replace("'", "")) self._public_key = self.compressed() self._semantic = get_semantic( _cryptocurrency=self._cryptocurrency, @@ -730,15 +730,15 @@ def clean_derivation(self) -> "HDWallet": """ if self._root_private_key: - self._path, self._depth, self._parent_fingerprint, self._index = ( - "m", 0, b"\0\0\0\0", 0 + self._path, self._path_class, self._depth, self._parent_fingerprint, self._index = ( + "m", "m", 0, b"\0\0\0\0", 0 ) 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() elif self._root_public_key: - self._path, self._depth, self._parent_fingerprint, self._index = ( - "m", 0, b"\0\0\0\0", 0 + self._path, self._path_class, self._depth, self._parent_fingerprint, self._index = ( + "m", "m", 0, b"\0\0\0\0", 0 ) self._chain_code = self._root_public_key[1] self._verified_key = ecdsa.VerifyingKey.from_string( @@ -1101,19 +1101,16 @@ def p2pkh_address(self) -> str: if self._cryptocurrency.SYMBOL in ["ETH", "ETHTEST"]: keccak_256 = sha3.keccak_256() - # keccak_256.update(unhexlify(self.compressed())) 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.update(unhexlify(self.compressed())) 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.update(unhexlify(self.compressed())) keccak_256.update(unhexlify(self.uncompressed())) address = keccak_256.hexdigest()[24:] network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + bytearray.fromhex(address) From 1c1a3ca2fa058fc959163cd436541b8baad4558c Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 07:49:15 +0300 Subject: [PATCH 48/92] Include: The new Python version 3.10 package. --- .github/workflows/pythonpackage.yml | 2 +- tox.ini | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 0ddd342..673bf4b 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@master diff --git a/tox.ini b/tox.ini index 3c126b6..c99617c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = python36,python37,python38,python39 +envlist = python36,python37,python38,python39,python310 [travis] python = @@ -7,6 +7,7 @@ python = 3.7: python37 3.8: python38 3.9: python39 + 3.9: python310 [testenv:python36] install_command = @@ -27,3 +28,8 @@ commands = python -m pytest install_command = python -m pip install -e .[tests,docs] {opts} {packages} commands = python -m pytest + +[testenv:python310] +install_command = + python -m pip install -e .[tests,docs] {opts} {packages} +commands = python -m pytest From 0644caf32e1babc62a4e8a80ecad8690f24bc233 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 07:51:28 +0300 Subject: [PATCH 49/92] Change: The docs theme from sphinx_rtd_theme to furo template. --- docs/cli.rst | 18 +------ docs/conf.py | 20 +++++-- docs/cryptocurrencies.rst | 12 +++-- docs/index.rst | 109 ++++++++++++++++++++++++++++++++++++-- docs/install.rst | 14 +++++ docs/toctree.rst | 2 +- 6 files changed, 147 insertions(+), 28 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index c32061f..85f2c08 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2,22 +2,8 @@ Command Line Interface (CLI) ============================ -After you have installed, type ``hdwallet`` to verify that it worked: - -:: - - $ hdwallet - Usage: hdwallet [OPTIONS] COMMAND [ARGS]... - - Options: - -v, --version Show HDWallet version and exit. - -h, --help Show this message and exit. - - Commands: - generate (g) Select Generate for HDWallet. - list (l) Select List for HDWallet information. - - +.. image:: static/gif/hdwallet.gif + :alt: HDWallet CLI .. click:: hdwallet.cli.__main__:main :prog: hdwallet diff --git a/docs/conf.py b/docs/conf.py index ec1eb26..bed7e1f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,15 +56,15 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = "furo" # Product logo name # html_logo = "static/png/hdwallet.png" # Theme options html_theme_options = { # "canonical_url": "", # "analytics_id": "UA-XXXXXXX-1", # Provided by Google in your dashboard - "logo_only": False, - "display_version": True, + # "logo_only": False, + # "display_version": True, # "prev_next_buttons_location": "bottom", # "style_external_links": False, # "vcs_pageview_mode": "", @@ -75,6 +75,20 @@ # "navigation_depth": 4, # "includehidden": True, # "titles_only": False + + # "light_css_variables": { + # "color-brand-primary": "darkblue", + # "color-brand-content": "darkblue", + # "color-admonition-background": "black", + # }, + # "dark_css_variables": { + # "color-brand-primary": "green", + # "color-brand-content": "green", + # "color-admonition-background": "white", + # }, + # "sidebar_hide_name": True, + # "navigation_with_keys": True, + # "announcement": "Important announcement!", } # Add any paths that contain custom static files (such as style sheets) here, diff --git a/docs/cryptocurrencies.rst b/docs/cryptocurrencies.rst index 0ba3d29..ad7105f 100644 --- a/docs/cryptocurrencies.rst +++ b/docs/cryptocurrencies.rst @@ -1,11 +1,15 @@ :orphan: -========================== -Available Cryptocurrencies -========================== +================ +Cryptocurrencies +================ This library simplifies the process of generating a new HDWallet's for: +.. note:: + + All Cryptocurrencies testnet networks default paths are set to ``m/44'/1'/0'/0/0`` value. + .. list-table:: :widths: 25 25 25 25 15 25 50 :header-rows: 1 @@ -997,5 +1001,3 @@ This library simplifies the process of generating a new HDWallet's for: - No - 121 - m/44'/121'/0'/0/0 - -**NOTICE:** All Cryptocurrencies testnet networks default paths are set to ``m/44'/1'/0'/0/0`` value. diff --git a/docs/index.rst b/docs/index.rst index a0062e2..0a06737 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,16 +2,119 @@ Hierarchical Deterministic Wallet ================================= +|Build Status| |PyPI Version| |Documentation Status| |PyPI License| |PyPI Python Version| |Coverage Status| + +.. |Build Status| image:: https://travis-ci.org/meherett/python-hdwallet.svg?branch=master + :target: https://travis-ci.org/meherett/python-hdwallet?branch=master + +.. |PyPI Version| image:: https://img.shields.io/pypi/v/hdwallet.svg?color=blue + :target: https://pypi.org/project/hdwallet + +.. |Documentation Status| image:: https://readthedocs.org/projects/hdwallet/badge/?version=master + :target: https://hdwallet.readthedocs.io/en/master/?badge=master + +.. |PyPI License| image:: https://img.shields.io/pypi/l/hdwallet?color=black + :target: https://pypi.org/project/hdwallet + +.. |PyPI Python Version| image:: https://img.shields.io/pypi/pyversions/hdwallet.svg + :target: https://pypi.org/project/hdwallet + +.. |Coverage Status| image:: https://coveralls.io/repos/github/meherett/python-hdwallet/badge.svg?branch=master + :target: https://coveralls.io/github/meherett/python-hdwallet?branch=master + Python-based library for the implementation of a hierarchical deterministic wallet generator for over 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. -.. image:: static/gif/hdwallet.gif - :alt: HDWallet CLI +Simple Bitcoin mainnet HDWallet generator: + +:: + + #!/usr/bin/env python3 + + from hdwallet import HDWallet + from hdwallet.utils import generate_entropy + from hdwallet.symbols import BTC as SYMBOL + from typing import Optional + + import json + + # Choose strength 128, 160, 192, 224 or 256 + STRENGTH: int = 160 # Default is 128 + # Choose language english, french, italian, spanish, chinese_simplified, chinese_traditional, japanese or korean + LANGUAGE: str = "korean" # Default is english + # Generate new entropy hex string + ENTROPY: str = generate_entropy(strength=STRENGTH) + # Secret passphrase for mnemonic + PASSPHRASE: Optional[str] = None # "meherett" + + # Initialize Bitcoin mainnet HDWallet + hdwallet: HDWallet = HDWallet(symbol=SYMBOL, use_default_path=False) + # Get Bitcoin HDWallet from entropy + hdwallet.from_entropy( + entropy=ENTROPY, language=LANGUAGE, passphrase=PASSPHRASE + ) + + # Derivation from path + # hdwallet.from_path("m/44'/0'/0'/0/0") + # Or derivation from index + hdwallet.from_index(44, hardened=True) + hdwallet.from_index(0, hardened=True) + hdwallet.from_index(0, hardened=True) + hdwallet.from_index(0) + hdwallet.from_index(0) + + # Print all Bitcoin HDWallet information's + print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) + +.. raw:: html + +
+ Output + +.. code-block:: python + + { + "cryptocurrency": "Bitcoin", + "symbol": "BTC", + "network": "mainnet", + "strength": 160, + "entropy": "c5b0d0ee698f3f72b6265f1bc591f8f2d7afa6dd", + "mnemonic": "주일 액수 명단 천둥 해수욕장 전망 추천 직업 그룹 단위 신체 파란색 시청 천천히 스트레스", + "language": "korean", + "passphrase": null, + "seed": "5a9b9667ccd07b3c641b1ba95e9119dd1d5a3034fd46cd2f27fc1f160c7dcd824fc0ab4710a9ae90582dffc3b0803bcbc0a8160feeaab4c70511c5035859decf", + "root_xprivate_key": "xprv9s21ZrQH143K2qMHU8aghJ4MoQR5g5mowXbeP2vCP937bseZGX929dmJudL7u4xRxtKvh58pxz1PhtCbWW2yUH14jdduKVMV9FkBMpM2Hyw", + "root_xpublic_key": "xpub661MyMwAqRbcFKRkaA7h4S16MSFa5YVfJkXFBRKowUa6Ufyhp4TGhS5nkvkLXSmdNjoszzDkU26WW2rg1zBsQBt6Pv3T8oLEAExGHD3hcQs", + "xprivate_key": "xprvA2YyMZWyPK2xo4eZgyypp2CzcHnxNzGbruGg7vmgaAVCtBtrjwzuhXJBNM3FrwBh85ajxHErNR6ByN77WJARpC1HDC7kTwa2yr7Mu9Pz5Qq", + "xpublic_key": "xpub6FYKm53sDgbG1Yj2o1WqBA9jAKdSnSzTE8CGvKBJ8W2BkzE1HVKAFKcfDcCHKpL5BQRg2HjbNSt55jpFshY7W1KFtp7zjB3DhNAmiFv6kzB", + "uncompressed": "081016370b45d7e23bd89b07d6886036f5e4df9a129eee3b488c177ba7881856e24d337b280f9d32539a22445e567543b39b708edf5289442f36dcde958a3433", + "compressed": "03081016370b45d7e23bd89b07d6886036f5e4df9a129eee3b488c177ba7881856", + "chain_code": "cf9ee427ed8073e009a5743056e8cf19167f67ca5082c2c6635b391e9a4e0b0d", + "private_key": "f79495fda777197ce73551bcd8e162ceca19167575760d3cc2bced4bf2a213dc", + "public_key": "03081016370b45d7e23bd89b07d6886036f5e4df9a129eee3b488c177ba7881856", + "wif": "L5WyVfBu8Sz3iGZtrwJVSP2wDJmu7HThGd1EGekFBnviWgzLXpJd", + "finger_print": "ac13e305", + "semantic": "p2pkh", + "path": "m/44'/0'/0'/0/0", + "hash": "ac13e305a88bd9968f1c058fcf5d9a6b1b9ef484", + "addresses": { + "p2pkh": "1Ggs3kkNrPPWoW17iDFQWgMdw3CD8BzBiv", + "p2sh": "3GQVUFePz517Hf61Vsa9H2tHj5jw5y6ngV", + "p2wpkh": "bc1q4sf7xpdg30vedrcuqk8u7hv6dvdeaayy3uw5cj", + "p2wpkh_in_p2sh": "3JyV5aSgdVYEjQodPWHfvehQ5227EDr3sN", + "p2wsh": "bc1qnk0s9q4379n6v9vg0lnhdu5qhjyx99u2xm238pmckmjg9v29q54saddzp9", + "p2wsh_in_p2sh": "3MmsEoP7GLHzuLVgkAtcRtyXLTWh8zNAcd" + } + } + +.. raw:: html + +
For more info see the BIP specs. .. list-table:: - :widths: 15 80 + :widths: 10 185 :header-rows: 1 * - BIP's diff --git a/docs/install.rst b/docs/install.rst index 0a54b6d..5bfda95 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -15,6 +15,20 @@ If you want to run the latest version of the code, you can install from git: $ pip install git+git://github.com/meherett/python-hdwallet.git +After you have installed, type ``hdwallet`` to verify that it worked: + +:: + + $ hdwallet + Usage: hdwallet [OPTIONS] COMMAND [ARGS]... + + Options: + -v, --version Show HDWallet version and exit. + -h, --help Show this message and exit. + + Commands: + generate (g) Select Generate for HDWallet. + list (l) Select List for HDWallet information. For the versions available, see the `tags on this repository `_. diff --git a/docs/toctree.rst b/docs/toctree.rst index 0dc203b..4ff632c 100644 --- a/docs/toctree.rst +++ b/docs/toctree.rst @@ -8,7 +8,7 @@ HDWallet Overview install.rst cli.rst - Cryptocurrencies + Available Cryptocurrencies .. toctree:: :maxdepth: 3 From e0ec7e75cf0ebf8440038562bbdbcc1c676d02f1 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 07:54:34 +0300 Subject: [PATCH 50/92] Update: All HDWallet example components. --- examples/from_entropy.py | 4 ++-- examples/from_private_key.py | 4 ++-- examples/from_public_key.py | 4 ++-- examples/from_seed.py | 4 ++-- examples/from_wif.py | 4 ++-- examples/from_xprivate_key.py | 4 ++-- examples/from_xpublic_key.py | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/from_entropy.py b/examples/from_entropy.py index 3b83069..66d7817 100644 --- a/examples/from_entropy.py +++ b/examples/from_entropy.py @@ -2,7 +2,7 @@ from hdwallet import HDWallet from hdwallet.utils import generate_entropy -from hdwallet.symbols import BTC +from hdwallet.symbols import TRX as SYMBOL from typing import Optional import json @@ -17,7 +17,7 @@ PASSPHRASE: Optional[str] = None # "meherett" # Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Bitcoin HDWallet from entropy hdwallet.from_entropy( entropy=ENTROPY, language=LANGUAGE, passphrase=PASSPHRASE diff --git a/examples/from_private_key.py b/examples/from_private_key.py index 45c2364..421b7eb 100644 --- a/examples/from_private_key.py +++ b/examples/from_private_key.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from hdwallet import HDWallet -from hdwallet.symbols import QTUM +from hdwallet.symbols import QTUM as SYMBOL import json @@ -9,7 +9,7 @@ PRIVATE_KEY: str = "f86d5afe2a457c29357485ebf853a1e5ff5f6fcf1ba4d7d1412665e01449902e" # Initialize Qtum mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=QTUM) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Qtum HDWallet from private key hdwallet.from_private_key(private_key=PRIVATE_KEY) diff --git a/examples/from_public_key.py b/examples/from_public_key.py index 34cb53a..cf76dc6 100644 --- a/examples/from_public_key.py +++ b/examples/from_public_key.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from hdwallet import HDWallet -from hdwallet.symbols import ETH +from hdwallet.symbols import ETH as SYMBOL import json @@ -9,7 +9,7 @@ PUBLIC_KEY = "034f6922d19e8134de23eb98396921c02cdcf67e8c0ff23dfd955839cd557afd10" # Initialize Ethereum mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=ETH) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Ethereum HDWallet from public key hdwallet.from_public_key(public_key=PUBLIC_KEY) diff --git a/examples/from_seed.py b/examples/from_seed.py index 18766b1..9104176 100644 --- a/examples/from_seed.py +++ b/examples/from_seed.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from hdwallet import HDWallet -from hdwallet.symbols import DOGE +from hdwallet.symbols import DOGE as SYMBOL import json @@ -10,7 +10,7 @@ "8723bc545a4bd51f5cd29a3e8bd1433bd1d26e6bf866ff53d1493f" # Initialize Dogecoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=DOGE) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Dogecoin HDWallet from seed hdwallet.from_seed(seed=SEED) diff --git a/examples/from_wif.py b/examples/from_wif.py index 4695317..517ffc4 100644 --- a/examples/from_wif.py +++ b/examples/from_wif.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from hdwallet import HDWallet -from hdwallet.symbols import BTCTEST +from hdwallet.symbols import BTCTEST as SYMBOL import json @@ -9,7 +9,7 @@ WALLET_IMPORTANT_FORMAT: str = "cVpnZ6XRfL5VVggwZDyndAU5KGVdT2TP1j1HB3td6ZKWCbh5wYvf" # Initialize Bitcoin testnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTCTEST) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Bitcoin HDWallet from wallet important format hdwallet.from_wif(wif=WALLET_IMPORTANT_FORMAT) diff --git a/examples/from_xprivate_key.py b/examples/from_xprivate_key.py index 2b0316c..ff4d245 100644 --- a/examples/from_xprivate_key.py +++ b/examples/from_xprivate_key.py @@ -2,7 +2,7 @@ from hdwallet import HDWallet as HDWallet from hdwallet.utils import is_root_xprivate_key -from hdwallet.symbols import BTC +from hdwallet.symbols import BTC as SYMBOL import json @@ -20,7 +20,7 @@ assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC), "Invalid Root XPrivate Key." # Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Bitcoin HDWallet from xprivate key hdwallet.from_xprivate_key(xprivate_key=XPRIVATE_KEY, strict=STRICT) diff --git a/examples/from_xpublic_key.py b/examples/from_xpublic_key.py index 1650b28..04e9079 100644 --- a/examples/from_xpublic_key.py +++ b/examples/from_xpublic_key.py @@ -2,7 +2,7 @@ from hdwallet import HDWallet as HDWallet from hdwallet.utils import is_root_xpublic_key -from hdwallet.symbols import BTC +from hdwallet.symbols import BTC as SYMBOL import json @@ -17,10 +17,10 @@ if STRICT: # Check root xpublic key - assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=BTC), "Invalid Root XPublic Key." + assert is_root_xpublic_key(xpublic_key=XPUBLIC_KEY, symbol=SYMBOL), "Invalid Root XPublic Key." # Initialize Bitcoin mainnet HDWallet -hdwallet: HDWallet = HDWallet(symbol=BTC) +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) # Get Bitcoin HDWallet from xpublic key hdwallet.from_xpublic_key(xpublic_key=XPUBLIC_KEY, strict=STRICT) From 814e3b7323fd7da7142aeee9f89688cb75af1d69 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 07:57:42 +0300 Subject: [PATCH 51/92] Bump: Python-HDWallet from v2.0.1 to v2.1.0 package. --- hdwallet/__init__.py | 2 +- setup.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index fb14e45..4d2281e 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -5,7 +5,7 @@ ) # HDWallet Information's -__version__: str = "v2.0.1" +__version__: str = "v2.1.0" __license__: str = "ISCL" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett@zoho.com" diff --git a/setup.py b/setup.py index 622d58e..d7acc2b 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,12 @@ setup, find_packages ) +# Project URLs +project_urls = { + "Tracker": "https://github.com/meherett/python-hdwallet/issues", + "Documentation": "https://hdwallet.readthedocs.io" +} + # README.md with open("README.md", "r", encoding="utf-8") as readme: long_description: str = readme.read() @@ -14,7 +20,7 @@ setup( name="hdwallet", - version="v2.0.1", + version="v2.1.0", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, @@ -23,6 +29,7 @@ author="Meheret Tesfaye Batu", author_email="meherett@zoho.com", url="https://github.com/meherett/python-hdwallet", + project_urls=project_urls, keywords=[ "cryptography", "cli", "wallet", "bip32", "bip44", "bip39", "hdwallet", "cryptocurrencies", "bitcoin", "ethereum" ], @@ -38,9 +45,9 @@ "pytest-cov>=3.0.0,<4" ], "docs": [ - "sphinx>=4.2.0,<5", - "sphinx-rtd-theme>=1.0.0,<2", - "sphinx-click>=3.0.1,<4" + "sphinx>=4.3.2,<5", + "furo==2021.11.23" + "sphinx-click>=3.0.2,<4" ] }, classifiers=[ @@ -50,6 +57,7 @@ "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Libraries :: Python Modules" ] ) From dd0e60692a39c4408be87865dda6996798f713b6 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 08:17:44 +0300 Subject: [PATCH 52/92] Fix: Python version 3.10 and setup.py furo package. --- .github/workflows/pythonpackage.yml | 2 +- .travis.yml | 2 ++ setup.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 673bf4b..2015c69 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@master diff --git a/.travis.yml b/.travis.yml index 32023d4..b911b95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,5 +16,7 @@ matrix: python: 3.8 - name: "3.9" python: 3.9 + - name: "3.10" + python: 3.10 script: pytest after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.9" ]; then coveralls; fi; \ No newline at end of file diff --git a/setup.py b/setup.py index d7acc2b..cfd379a 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ ], "docs": [ "sphinx>=4.3.2,<5", - "furo==2021.11.23" + "furo==2021.11.23", "sphinx-click>=3.0.2,<4" ] }, From f5a3fe37b5c27f46f012ef41d9091de9a390bb37 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 08:52:38 +0300 Subject: [PATCH 53/92] Drop: Python version 3.10 package. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b911b95..32023d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,5 @@ matrix: python: 3.8 - name: "3.9" python: 3.9 - - name: "3.10" - python: 3.10 script: pytest after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.9" ]; then coveralls; fi; \ No newline at end of file From 2439a29e2ff4fdb7f7a554a65eacbadcb7e9e6f9 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 25 Dec 2021 09:15:31 +0300 Subject: [PATCH 54/92] Modify: Derivation BIP's initializers tester component. --- tests/test_derivations.py | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test_derivations.py b/tests/test_derivations.py index 27c81f6..07bd1be 100644 --- a/tests/test_derivations.py +++ b/tests/test_derivations.py @@ -62,6 +62,17 @@ def test_derivations(): assert bip32_derivation.address() == "0" assert bip32_derivation.SEMANTIC == "p2pkh" + bip32_derivation: BIP32Derivation = BIP32Derivation( + purpose=(44, True), coin_type=(0, True), account=(0, True), change=False, address=(0, False) + ) + assert str(bip32_derivation) == "m/44'/0'/0'/0/0" + assert bip32_derivation.purpose() == "44'" + assert bip32_derivation.coin_type() == "0'" + assert bip32_derivation.account() == "0'" + assert bip32_derivation.change() == 0 + assert bip32_derivation.address() == "0" + assert bip32_derivation.SEMANTIC == "p2pkh" + bip44_derivation: BIP44Derivation = BIP44Derivation( cryptocurrency=BitcoinMainnet ).from_account( @@ -79,6 +90,17 @@ def test_derivations(): assert bip44_derivation.address() == "0" assert bip44_derivation.SEMANTIC == "p2pkh" + bip44_derivation: BIP44Derivation = BIP44Derivation( + cryptocurrency=BitcoinMainnet, account=(0, True), change=False, address=(0, False) + ) + assert str(bip44_derivation) == "m/44'/0'/0'/0/0" + assert bip44_derivation.purpose() == "44'" + assert bip44_derivation.coin_type() == "0'" + assert bip44_derivation.account() == "0'" + assert bip44_derivation.change() == 0 + assert bip44_derivation.address() == "0" + assert bip44_derivation.SEMANTIC == "p2pkh" + bip49_derivation: BIP49Derivation = BIP49Derivation( cryptocurrency=BitcoinMainnet ).from_account( @@ -96,6 +118,17 @@ def test_derivations(): assert bip49_derivation.address() == "0" assert bip49_derivation.SEMANTIC == "p2wpkh_in_p2sh" + bip49_derivation: BIP49Derivation = BIP49Derivation( + cryptocurrency=BitcoinMainnet, account=(0, True), change=False, address=(0, False) + ) + assert str(bip49_derivation) == "m/49'/0'/0'/0/0" + assert bip49_derivation.purpose() == "49'" + assert bip49_derivation.coin_type() == "0'" + assert bip49_derivation.account() == "0'" + assert bip49_derivation.change() == 0 + assert bip49_derivation.address() == "0" + assert bip49_derivation.SEMANTIC == "p2wpkh_in_p2sh" + bip84_derivation: BIP84Derivation = BIP84Derivation( cryptocurrency=BitcoinMainnet ).from_account( @@ -113,6 +146,17 @@ def test_derivations(): assert bip84_derivation.address() == "0" assert bip84_derivation.SEMANTIC == "p2wpkh" + bip84_derivation: BIP84Derivation = BIP84Derivation( + cryptocurrency=BitcoinMainnet, account=(0, True), change=False, address=(0, False) + ) + assert str(bip84_derivation) == "m/84'/0'/0'/0/0" + assert bip84_derivation.purpose() == "84'" + assert bip84_derivation.coin_type() == "0'" + assert bip84_derivation.account() == "0'" + assert bip84_derivation.change() == 0 + assert bip84_derivation.address() == "0" + assert bip84_derivation.SEMANTIC == "p2wpkh" + bip141_derivation: BIP141Derivation = BIP141Derivation( cryptocurrency=BitcoinMainnet, semantic="p2wsh_in_p2sh" ) From 56f76422942956602668c37c0efd1dfcf0004c49 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 17:38:52 +0300 Subject: [PATCH 55/92] Update: Installation documentation for CLI. --- README.md | 16 +++++++++++----- docs/install.rst | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bf66874..b430564 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Hierarchical Deterministic Wallet -[![Build Status](https://travis-ci.org/meherett/python-hdwallet.svg?branch=master)](https://travis-ci.org/meherett/python-hdwallet?branch=master) +[![Build Status](https://app.travis-ci.com/meherett/python-hdwallet.svg?branch=master)](https://app.travis-ci.com/meherett/python-hdwallet) [![PyPI Version](https://img.shields.io/pypi/v/hdwallet.svg?color=blue)](https://pypi.org/project/hdwallet) -[![Documentation Status](https://readthedocs.org/projects/hdwallet/badge/?version=master)](https://hdwallet.readthedocs.io/en/master/?badge=master) +[![Documentation Status](https://readthedocs.org/projects/hdwallet/badge/?version=master)](https://hdwallet.readthedocs.io) [![PyPI License](https://img.shields.io/pypi/l/hdwallet?color=black)](https://pypi.org/project/hdwallet) [![PyPI Python Version](https://img.shields.io/pypi/pyversions/hdwallet.svg)](https://pypi.org/project/hdwallet) -[![Coverage Status](https://coveralls.io/repos/github/meherett/python-hdwallet/badge.svg?branch=master)](https://coveralls.io/github/meherett/python-hdwallet?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/meherett/python-hdwallet/badge.svg?branch=master)](https://coveralls.io/github/meherett/python-hdwallet) 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 chain. @@ -26,12 +26,18 @@ For more info see the BIP specs. ## Installation -PIP to install `hdwallet` globally, for Linux `sudo` may be required: +The easiest way to install `hdwallet` is via pip: ``` pip install hdwallet ``` +To install `hdwallet` command line interface globally, for Linux `sudo` may be required: + +``` +pip install hdwallet[cli] +``` + If you want to run the latest version of the code, you can install from the git: ``` @@ -192,7 +198,7 @@ Base HD Path: m/44'/60'/0'/0/{address_index} To get started, just fork this repo, clone it locally, and run: ``` -pip install -e .[tests,docs] -r requirements.txt +pip install -e .[cli,tests,docs] -r requirements.txt ``` ## Testing diff --git a/docs/install.rst b/docs/install.rst index 5bfda95..e23b637 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -2,18 +2,19 @@ Installing HDWallet =================== -The easiest way to install HDWallet is via pip: +The easiest way to install ``hdwallet`` is via pip: :: $ pip install hdwallet -If you want to run the latest version of the code, you can install from git: +To install ``hdwallet`` command line interface globally, for Linux `sudo` may be required: :: - $ pip install git+git://github.com/meherett/python-hdwallet.git + $ pip install hdwallet[cli] + After you have installed, type ``hdwallet`` to verify that it worked: @@ -30,6 +31,14 @@ After you have installed, type ``hdwallet`` to verify that it worked: generate (g) Select Generate for HDWallet. list (l) Select List for HDWallet information. + +If you want to run the latest version of the code, you can install from git: + +:: + + $ pip install git+git://github.com/meherett/python-hdwallet.git + + For the versions available, see the `tags on this repository `_. Development @@ -39,4 +48,4 @@ We welcome pull requests. To get started, just fork this `github repository Date: Sat, 19 Feb 2022 17:40:07 +0300 Subject: [PATCH 56/92] Add: Extras CLI requirements and bump docs packages. --- requirements.txt | 5 +---- setup.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 855ec06..477b76d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ ecdsa>=0.13,<1 mnemonic>=0.19,<1 pysha3>=1.0.2,<2 -base58>=2.0.1,<3 -click>=8.0.3,<9 -click-aliases>=1.0.1,<2 -tabulate>=0.8.9,<1 \ No newline at end of file +base58>=2.0.1,<3 \ No newline at end of file diff --git a/setup.py b/setup.py index cfd379a..9b70d38 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="hdwallet", - version="v2.1.0", + version="v2.1.1", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, @@ -40,14 +40,19 @@ packages=find_packages(), install_requires=requirements, extras_require={ + "cli": [ + "click>=8.0.3,<9", + "click-aliases>=1.0.1,<2", + "tabulate>=0.8.9,<1" + ], "tests": [ "pytest>=6.2.5,<7", "pytest-cov>=3.0.0,<4" ], "docs": [ - "sphinx>=4.3.2,<5", - "furo==2021.11.23", - "sphinx-click>=3.0.2,<4" + "sphinx>=4.4.0,<5", + "furo==2022.2.14.1", + "sphinx-click>=3.1.0,<4" ] }, classifiers=[ From 206aba9293baf7705434084e422f7f97d02bff16 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 17:46:51 +0300 Subject: [PATCH 57/92] Add: Python version 3.10 package for testing. --- .travis.yml | 8 +++++--- tox.ini | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32023d4..6afbd47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ install: - sudo add-apt-repository -y ppa:deadsnakes/ppa - sudo apt-get update - sudo apt-get install -y python$TRAVIS_PYTHON_VERSION-dev - - pip install -e .[tests,docs] tox-travis coveralls + - pip install -e .[cli,tests,docs] tox-travis coveralls matrix: include: - name: "3.6" @@ -16,5 +16,7 @@ matrix: python: 3.8 - name: "3.9" python: 3.9 -script: pytest -after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.9" ]; then coveralls; fi; \ No newline at end of file + - name: "3.10" + python: 3.10 +script: tox +after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.10" ]; then coveralls; fi; \ No newline at end of file diff --git a/tox.ini b/tox.ini index c99617c..1307a76 100644 --- a/tox.ini +++ b/tox.ini @@ -7,29 +7,39 @@ python = 3.7: python37 3.8: python38 3.9: python39 - 3.9: python310 + 3.10: python310 [testenv:python36] install_command = - python -m pip install -e .[tests,docs] {opts} {packages} + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest [testenv:python37] install_command = - python -m pip install -e .[tests,docs] {opts} {packages} + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest [testenv:python38] install_command = - python -m pip install -e .[tests,docs] {opts} {packages} + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest [testenv:python39] install_command = - python -m pip install -e .[tests,docs] {opts} {packages} + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest [testenv:python310] install_command = - python -m pip install -e .[tests,docs] {opts} {packages} + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest From 2a0f7101b29316d25af5a073e3872050941611e2 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 17:47:29 +0300 Subject: [PATCH 58/92] Bump: Python-HDWallet from v2.1.0 to v2.1.1 package. --- examples/from_xprivate_key.py | 2 +- hdwallet/__init__.py | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/examples/from_xprivate_key.py b/examples/from_xprivate_key.py index ff4d245..2af64e1 100644 --- a/examples/from_xprivate_key.py +++ b/examples/from_xprivate_key.py @@ -17,7 +17,7 @@ if STRICT: # Check root xprivate key - assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=BTC), "Invalid Root XPrivate Key." + assert is_root_xprivate_key(xprivate_key=XPRIVATE_KEY, symbol=SYMBOL), "Invalid Root XPrivate Key." # Initialize Bitcoin mainnet HDWallet hdwallet: HDWallet = HDWallet(symbol=SYMBOL) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index 4d2281e..dd35aca 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -1,11 +1,16 @@ #!/usr/bin/env python3 from .hdwallet import ( - HDWallet, BIP32HDWallet, BIP44HDWallet, BIP49HDWallet, BIP84HDWallet, BIP141HDWallet + HDWallet, + BIP32HDWallet, + BIP44HDWallet, + BIP49HDWallet, + BIP84HDWallet, + BIP141HDWallet ) # HDWallet Information's -__version__: str = "v2.1.0" +__version__: str = "v2.1.1" __license__: str = "ISCL" __author__: str = "Meheret Tesfaye Batu" __email__: str = "meherett@zoho.com" @@ -13,6 +18,15 @@ "generator for more than 140+ multiple cryptocurrencies." __all__: list = [ - "__version__", "__license__", "__author__", "__email__", "__description__", - "HDWallet", "BIP32HDWallet", "BIP44HDWallet", "BIP49HDWallet", "BIP84HDWallet", "BIP141HDWallet" + "__version__", + "__license__", + "__author__", + "__email__", + "__description__", + "HDWallet", + "BIP32HDWallet", + "BIP44HDWallet", + "BIP49HDWallet", + "BIP84HDWallet", + "BIP141HDWallet" ] From 05cb2c1763709d85c1e1c6a9cc2595a02fea8663 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 18:01:17 +0300 Subject: [PATCH 59/92] Fix: pythonpackage.yml of pip installation for CLI. --- .github/workflows/pythonpackage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 2015c69..b247009 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -25,6 +25,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[tests] + pip install -e .[cli,tests,docs] - name: Test with pytest run: pytest From 7686b85ef3550116a8dda1b69c0ded0c0f5ab502 Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 19:14:06 +0300 Subject: [PATCH 60/92] Modify: Python packages and removed some bugs. --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6afbd47..8fd60e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,7 @@ language: python -dist: xenial -sudo: true +before_install: + - python -m pip install --upgrade pip install: - - sudo add-apt-repository -y ppa:deadsnakes/ppa - - sudo apt-get update - - sudo apt-get install -y python$TRAVIS_PYTHON_VERSION-dev - pip install -e .[cli,tests,docs] tox-travis coveralls matrix: include: From 84bf856801f95fe258bf73cfd1ab6360e3eca24f Mon Sep 17 00:00:00 2001 From: meherett Date: Sat, 19 Feb 2022 19:25:46 +0300 Subject: [PATCH 61/92] Change: Dist from xenial to bionic package. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8fd60e4..9c4e24b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +dist: bionic before_install: - python -m pip install --upgrade pip install: From 09a7b5df03e4f3f5fbea0c8886f4433326e65982 Mon Sep 17 00:00:00 2001 From: XmasApple Date: Wed, 2 Mar 2022 03:01:55 +0300 Subject: [PATCH 62/92] Fix: missing quote in language error --- hdwallet/hdwallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 05a9c19..97e7d04 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -159,7 +159,7 @@ def from_entropy(self, entropy: str, language: str = "english", passphrase: str if language and language not in ["english", "french", "italian", "japanese", "chinese_simplified", "chinese_traditional", "korean", "spanish"]: raise ValueError("Invalid language, choose only the following options 'english', 'french', 'italian', " - "'spanish', 'chinese_simplified', 'chinese_traditional', 'japanese or 'korean' languages.") + "'spanish', 'chinese_simplified', 'chinese_traditional', 'japanese' or 'korean' languages.") self._strength = get_entropy_strength(entropy=entropy) self._entropy, self._language = unhexlify(entropy), language From e524fda0299ba123becfaac9bb033db3b964c284 Mon Sep 17 00:00:00 2001 From: abraksas Date: Tue, 12 Jul 2022 23:48:58 +0200 Subject: [PATCH 63/92] Fix python3.10 not exists Ripemd160 in hashlib.py --- hdwallet/hdwallet.py | 19 ++--- hdwallet/libs/ripemd160.py | 141 +++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 hdwallet/libs/ripemd160.py diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 97e7d04..85448c6 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -44,6 +44,7 @@ import hashlib import base58 +from .libs.ripemd160 import ripemd160 from .libs.ecc import S256Point, N, G from .libs.bech32 import ( bech32_encode, encode, bech32_decode, decode @@ -1063,9 +1064,9 @@ def hash(self, private_key: str = None): "4d887566d408dfe5ea8090f2b716f9639523ca89" """ - return hashlib.new("ripemd160", sha256(unhexlify(self.public_key( + return hexlify(ripemd160(sha256(unhexlify(self.public_key( private_key=private_key if private_key else self.private_key() - ))).digest()).hexdigest() + ))).digest())) def finger_print(self) -> str: """ @@ -1117,7 +1118,7 @@ def p2pkh_address(self) -> str: return ensure_string(base58.b58encode_check(network_hash160_bytes)) compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).digest() + public_key_hash = ripemd160(sha256(compressed_public_key).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.PUBLIC_KEY_ADDRESS) + public_key_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1137,9 +1138,9 @@ def p2sh_address(self) -> str: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).hexdigest() + public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())) public_key_hash_script = unhexlify("76a914" + public_key_hash + "88ac") - script_hash = hashlib.new("ripemd160", sha256(public_key_hash_script).digest()).digest() + script_hash = ripemd160(sha256(public_key_hash_script).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash return ensure_string(base58.b58encode_check(network_hash160_bytes)) @@ -1159,7 +1160,7 @@ def p2wpkh_address(self) -> Optional[str]: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hashlib.new("ripemd160", sha256(compressed_public_key).digest()).digest() + public_key_hash = ripemd160(sha256(compressed_public_key).digest()) if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None return ensure_string(encode(self._cryptocurrency.SEGWIT_ADDRESS.HRP, 0, public_key_hash)) @@ -1180,8 +1181,8 @@ def p2wpkh_in_p2sh_address(self) -> Optional[str]: """ 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() + public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())) + script_hash = ripemd160(sha256(unhexlify("0014" + public_key_hash)).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None @@ -1225,7 +1226,7 @@ def p2wsh_in_p2sh_address(self) -> Optional[str]: 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() + script_hash = ripemd160(sha256(script_hash).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: return None diff --git a/hdwallet/libs/ripemd160.py b/hdwallet/libs/ripemd160.py new file mode 100644 index 0000000..496ed6a --- /dev/null +++ b/hdwallet/libs/ripemd160.py @@ -0,0 +1,141 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# source: https://github.com/richardkiss/pycoin/blob/main/pycoin/contrib/ripemd160.py +# Grudgingly ported to python2 compatibility by Richard Kiss + +import binascii +import struct +import unittest + + +if struct.calcsize("> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [struct.unpack("> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + args = list(state) + [data[64*b:64*(b+1)]] + state = compress(*args) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + struct.pack("> 6): + args = list(state) + [fin[64*b:64*(b+1)]] + state = compress(*args) + # Produce output. + return b"".join(struct.pack(" Date: Sat, 16 Jul 2022 15:28:52 +0200 Subject: [PATCH 64/92] Update hdwallet.py Fix ripemd160 in python3.10 --- hdwallet/hdwallet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 85448c6..daffaec 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -1066,7 +1066,7 @@ def hash(self, private_key: str = None): return hexlify(ripemd160(sha256(unhexlify(self.public_key( private_key=private_key if private_key else self.private_key() - ))).digest())) + ))).digest())).decode("utf-8") def finger_print(self) -> str: """ @@ -1138,7 +1138,7 @@ def p2sh_address(self) -> str: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())) + public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())).decode("utf-8") public_key_hash_script = unhexlify("76a914" + public_key_hash + "88ac") script_hash = ripemd160(sha256(public_key_hash_script).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash @@ -1181,7 +1181,7 @@ def p2wpkh_in_p2sh_address(self) -> Optional[str]: """ compressed_public_key = unhexlify(self.compressed()) - public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())) + public_key_hash = hexlify(ripemd160(sha256(compressed_public_key).digest())).decode("utf-8") script_hash = ripemd160(sha256(unhexlify("0014" + public_key_hash)).digest()) network_hash160_bytes = _unhexlify(self._cryptocurrency.SCRIPT_ADDRESS) + script_hash if self._cryptocurrency.SEGWIT_ADDRESS.HRP is None: From ea25ef283ef9c73a0e63423479c6f38596e8e40b Mon Sep 17 00:00:00 2001 From: nultinator <72562693+nultinator@users.noreply.github.com> Date: Sat, 6 Aug 2022 15:07:36 -0400 Subject: [PATCH 65/92] Update symbols.py added ycash --- hdwallet/symbols.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index c51d469..811098e 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -274,6 +274,8 @@ XUEZ = "XUEZ" # XinFin XDC = "XDC" +#Ycash +YEC = "YEC" # ZClassic ZCL = "ZCL" # Zcash @@ -420,6 +422,7 @@ "WC", "XUEZ", "XDC", + "YEC" "ZCL", "ZEC", "ZECTEST", "ZEN" From a2cdccd49ce60fd94fbadb997d0e34217bcc9a8c Mon Sep 17 00:00:00 2001 From: nultinator <72562693+nultinator@users.noreply.github.com> Date: Sat, 6 Aug 2022 15:12:20 -0400 Subject: [PATCH 66/92] Update cryptocurrencies.py add ycash mainnet --- hdwallet/cryptocurrencies.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 9ba2f2f..9c90d17 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -6234,6 +6234,45 @@ class XinFinMainnet(Cryptocurrency): MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 + +class YcashMainnet(Cryptocurrency): + + NAME = "Ycash" + SYMBOL = "YEC" + NETWORK = "mainnet" + SOURCE_CODE = "https://github.com/ycashfoundation/ycash" + COIN_TYPE = CoinType({ + "INDEX": 133, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x1c2c + PUBLIC_KEY_ADDRESS = 0x1c28 + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": None, + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x488ade4, + "P2SH": 0x488ade4, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x488b21e, + "P2SH": 0x488b21e, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + + MASSAGE_PREFIX = "\x18Ycash Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0x80 class ZClassicMainnet(Cryptocurrency): From 15e32d44c2e520a40353734a21990ece5c862ea0 Mon Sep 17 00:00:00 2001 From: Perry Kundert Date: Mon, 7 Nov 2022 16:41:40 -0800 Subject: [PATCH 67/92] Update from pysha3 (deprecated) to pycryptodome for Keccak-256 --- GNUmakefile | 37 ++++++++++++++++++++++++++++++++++ hdwallet/__init__.py | 2 +- hdwallet/hdwallet.py | 10 +++++----- hdwallet/libs/base58.py | 8 ++++---- requirements.txt | 4 ++-- setup.py | 2 +- tests/test_base58.py | 44 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 GNUmakefile diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..adac4f8 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,37 @@ +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 + +.PHONY: all test build build-check wheel install-dev install clean FORCE + +all: build + +test: + $(PY3) -m pytest + +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..43006ec 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_pycryptodome.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..6728319 100644 --- a/tests/test_base58.py +++ b/tests/test_base58.py @@ -35,3 +35,47 @@ def test_base58(): assert encode(decode("111233QC4")) == "111233QC4" + + +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' + From 918e33fee103c6f44e99cc2378b8a9d44af5a53a Mon Sep 17 00:00:00 2001 From: Perry Kundert Date: Tue, 8 Nov 2022 06:13:12 -0800 Subject: [PATCH 68/92] Add test of checksum_encode, using Keccak for ETH addr. upper/lower case --- GNUmakefile | 11 ++++++++++- hdwallet/libs/base58.py | 2 +- tests/test_base58.py | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index adac4f8..73be7d4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -3,12 +3,21 @@ 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: - $(PY3) -m pytest + $(PY3TEST) + +# Run only tests with a prefix containing the target string, eg test-blah +test-%: + $(PY3TEST) *$*_test.py + +unit-%: + $(PY3TEST) -k $* build: clean wheel diff --git a/hdwallet/libs/base58.py b/hdwallet/libs/base58.py index 43006ec..cc94f13 100644 --- a/hdwallet/libs/base58.py +++ b/hdwallet/libs/base58.py @@ -13,7 +13,7 @@ def checksum_encode(address, crypto="eth"): out = "" - keccak_256 = keccak_pycryptodome.new(digest_bits=256) + keccak_256 = keccak.new(digest_bits=256) addr = address.lower().replace("0x", "") if crypto == "eth" else address.lower().replace("xdc", "") keccak_256.update(addr.encode("ascii")) hash_addr = keccak_256.hexdigest() diff --git a/tests/test_base58.py b/tests/test_base58.py index 6728319..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 ) @@ -36,6 +36,13 @@ 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 From ca5abd15216d893fbdd752c31da6a7f61b4b686b Mon Sep 17 00:00:00 2001 From: meherett Date: Wed, 9 Nov 2022 12:27:06 +0300 Subject: [PATCH 69/92] Add: Python version 3.11 package for testing. --- .github/workflows/pythonpackage.yml | 2 +- .travis.yml | 4 +++- setup.py | 1 + tox.ini | 10 +++++++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index b247009..3b4a0bd 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@master diff --git a/.travis.yml b/.travis.yml index 9c4e24b..0ccfdca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,5 +16,7 @@ matrix: python: 3.9 - name: "3.10" python: 3.10 + - name: "3.11" + python: 3.11 script: tox -after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.10" ]; then coveralls; fi; \ No newline at end of file +after_success: if [ "${TRAVIS_PYTHON_VERSION}" == "3.11" ]; then coveralls; fi; \ No newline at end of file diff --git a/setup.py b/setup.py index 74afc4e..411c043 100644 --- a/setup.py +++ b/setup.py @@ -63,6 +63,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Libraries :: Python Modules" ] ) diff --git a/tox.ini b/tox.ini index 1307a76..c400b8f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = python36,python37,python38,python39,python310 +envlist = python36,python37,python38,python39,python310,python311 [travis] python = @@ -8,6 +8,7 @@ python = 3.8: python38 3.9: python39 3.10: python310 + 3.11: python311 [testenv:python36] install_command = @@ -43,3 +44,10 @@ install_command = pip install -e . {opts} {packages} extras = cli,tests,docs commands = python -m pytest + +[testenv:python311] +install_command = + python -m pip install --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs +commands = python -m pytest From 3effa60521e68a92b87fbd5fab17523ee47d432d Mon Sep 17 00:00:00 2001 From: David White Date: Thu, 1 Dec 2022 11:43:51 +1300 Subject: [PATCH 70/92] Add Flux Cryptocurrency, fix prefix naming error --- hdwallet/cryptocurrencies.py | 318 +++++++++++++++++++---------------- 1 file changed, 177 insertions(+), 141 deletions(-) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 9ba2f2f..748d6ea 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -109,7 +109,7 @@ class AnonMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18ANON Signed Message:\n" + MESSAGE_PREFIX = "\x18ANON Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -149,7 +149,7 @@ class ArgoneumMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -189,7 +189,7 @@ class ArtaxMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Artax Signed Message:\n" + MESSAGE_PREFIX = "\x18Artax Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x97 @@ -229,7 +229,7 @@ class AryacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Aryacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Aryacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x97 @@ -269,7 +269,7 @@ class AsiacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18AsiaCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18AsiaCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x97 @@ -309,7 +309,7 @@ class AtomMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bitcoin Atom Signed Message:\n" + MESSAGE_PREFIX = "\x18Bitcoin Atom Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -349,7 +349,7 @@ class AuroracoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18AuroraCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18AuroraCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x97 @@ -389,7 +389,7 @@ class AxeMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xcc @@ -429,7 +429,7 @@ class BataMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bata Signed Message:\n" + MESSAGE_PREFIX = "\x18Bata Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa4 @@ -469,7 +469,7 @@ class BeetleCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19Beetlecoin Signed Message:\n" + MESSAGE_PREFIX = "\x19Beetlecoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -509,7 +509,7 @@ class BelaCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BelaCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18BelaCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -549,7 +549,7 @@ class BitCloudMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BitCloud Signed Message:\n" + MESSAGE_PREFIX = "\x18BitCloud Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -589,7 +589,7 @@ class BitSendMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bitsend Signed Message:\n" + MESSAGE_PREFIX = "\x18Bitsend Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xcc @@ -749,7 +749,7 @@ class BitcoinPlusMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BitcoinPlus Signed Message:\n" + MESSAGE_PREFIX = "\x18BitcoinPlus Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -789,7 +789,7 @@ class BitcoinSVMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -869,7 +869,7 @@ class BitcoinZMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BitcoinZ Signed Message:\n" + MESSAGE_PREFIX = "\x18BitcoinZ Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -909,7 +909,7 @@ class BitcoreMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BitCore Signed Message:\n" + MESSAGE_PREFIX = "\x18BitCore Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -949,7 +949,7 @@ class BlackcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BlackCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18BlackCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -989,7 +989,7 @@ class BlockStampMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BlockStamp Signed Message:\n" + MESSAGE_PREFIX = "\x18BlockStamp Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -1029,7 +1029,7 @@ class BlocknodeMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Blocknode Signed Message:\n" + MESSAGE_PREFIX = "\x18Blocknode Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x4b @@ -1069,7 +1069,7 @@ class BlocknodeTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Blocknode Testnet Signed Message:\n" + MESSAGE_PREFIX = "\x18Blocknode Testnet Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x89 @@ -1109,7 +1109,7 @@ class BolivarcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "Bolivarcoin Signed Message:\n" + MESSAGE_PREFIX = "Bolivarcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xd5 @@ -1149,7 +1149,7 @@ class BritCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18BritCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18BritCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -1189,7 +1189,7 @@ class CPUChainMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x1DCPUchain Signed Message:\n" + MESSAGE_PREFIX = "\x1DCPUchain Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -1229,7 +1229,7 @@ class CanadaECoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Canada eCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Canada eCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9c @@ -1269,7 +1269,7 @@ class CannacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Cannacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Cannacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9c @@ -1309,7 +1309,7 @@ class ClamsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x85 @@ -1349,7 +1349,7 @@ class ClubCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18ClubCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18ClubCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -1389,7 +1389,7 @@ class CompcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18CompCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18CompCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9c @@ -1429,7 +1429,7 @@ class CranePayMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x7b @@ -1469,7 +1469,7 @@ class CraveMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18DarkNet Signed Message:\n" + MESSAGE_PREFIX = "\x18DarkNet Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x99 @@ -1589,7 +1589,7 @@ class DeepOnionMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18DeepOnion Signed Message:\n" + MESSAGE_PREFIX = "\x18DeepOnion Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9f @@ -1629,7 +1629,7 @@ class DefcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18defcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18defcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9e @@ -1669,7 +1669,7 @@ class DenariusMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19Denarius Signed Message:\n" + MESSAGE_PREFIX = "\x19Denarius Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9e @@ -1709,7 +1709,7 @@ class DiamondMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Diamond Signed Message:\n" + MESSAGE_PREFIX = "\x18Diamond Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xda @@ -1749,7 +1749,7 @@ class DigiByteMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19DigiByte Signed Message:\n" + MESSAGE_PREFIX = "\x19DigiByte Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -1789,7 +1789,7 @@ class DigitalcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Digitalcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Digitalcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9e @@ -1909,7 +1909,7 @@ class EDRCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18EDRcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18EDRcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xdd @@ -1949,7 +1949,7 @@ class EcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18eCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18eCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xdc @@ -1989,7 +1989,7 @@ class EinsteiniumMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Einsteinium Signed Message:\n" + MESSAGE_PREFIX = "\x18Einsteinium Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa1 @@ -2029,7 +2029,7 @@ class ElastosMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -2069,7 +2069,7 @@ class EnergiMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "DarkCoin Signed Message:\n" + MESSAGE_PREFIX = "DarkCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x6a @@ -2149,7 +2149,7 @@ class EuropeCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa8 @@ -2189,7 +2189,7 @@ class ExclusiveCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18ExclusiveCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18ExclusiveCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa1 @@ -2229,7 +2229,7 @@ class FIXMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x3c @@ -2269,7 +2269,7 @@ class FIXTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xed @@ -2309,7 +2309,7 @@ class FeathercoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Feathercoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Feathercoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x8e @@ -2349,7 +2349,7 @@ class FirstcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18FirstCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18FirstCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa3 @@ -2389,10 +2389,47 @@ class FlashcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Flashcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Flashcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc4 +class FluxMainnet(Cryptocurrency): + + NAME = "Flux" + SYMBOL = "FLUX" + NETWORK = "mainnet" + SOURCE_CODE = "https://github.com/RunOnFlux/fluxd" + COIN_TYPE = CoinType({ + "INDEX": 19167, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x1cbd + PUBLIC_KEY_ADDRESS = 0x1cb8 + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": None, + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x0488ade4, + "P2SH": 0x0488ade4, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x0488b21e, + "P2SH": 0x0488b21e, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + MESSAGE_PREFIX = "\x18Zelcash Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0x80 class FujiCoinMainnet(Cryptocurrency): @@ -2429,7 +2466,7 @@ class FujiCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19FujiCoin Signed Message:\n" + MESSAGE_PREFIX = "\x19FujiCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa4 @@ -2469,7 +2506,7 @@ class GCRCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18GCR Signed Message:\n" + MESSAGE_PREFIX = "\x18GCR Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9a @@ -2509,7 +2546,7 @@ class GameCreditsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa6 @@ -2549,7 +2586,7 @@ class GoByteMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18DarkCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18DarkCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc6 @@ -2589,7 +2626,7 @@ class GridcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Gridcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Gridcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbe @@ -2629,7 +2666,7 @@ class GroestlCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19GroestlCoin Signed Message:\n" + MESSAGE_PREFIX = "\x19GroestlCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -2669,7 +2706,7 @@ class GroestlCoinTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19GroestlCoin Signed Message:\n" + MESSAGE_PREFIX = "\x19GroestlCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -2709,7 +2746,7 @@ class GuldenMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Guldencoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Guldencoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x62 @@ -2749,7 +2786,7 @@ class HushMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Hush Signed Message:\n" + MESSAGE_PREFIX = "\x18Hush Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -2789,7 +2826,7 @@ class HelleniccoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18helleniccoin Signed Message:\n" + MESSAGE_PREFIX = "\x18helleniccoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb0 @@ -2829,7 +2866,7 @@ class HempcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Hempcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Hempcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa8 @@ -2869,7 +2906,7 @@ class IXCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Ixcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Ixcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -2909,7 +2946,7 @@ class InsaneCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18INSaNe Signed Message:\n" + MESSAGE_PREFIX = "\x18INSaNe Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x37 @@ -2949,7 +2986,7 @@ class InternetOfPeopleMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18IoP Signed Message:\n" + MESSAGE_PREFIX = "\x18IoP Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x31 @@ -2989,7 +3026,7 @@ class JumbucksMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x19Jumbucks Signed Message:\n" + MESSAGE_PREFIX = "\x19Jumbucks Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xab @@ -3029,7 +3066,7 @@ class KobocoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Kobocoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Kobocoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xa3 @@ -3069,7 +3106,7 @@ class KomodoMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Komodo Signed Message:\n" + MESSAGE_PREFIX = "\x18Komodo Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbc @@ -3109,7 +3146,7 @@ class LBRYCreditsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18LBRYcrd Signed Message:\n" + MESSAGE_PREFIX = "\x18LBRYcrd Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x1c @@ -3149,7 +3186,7 @@ class LinxMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18LinX Signed Message:\n" + MESSAGE_PREFIX = "\x18LinX Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xcb @@ -3189,7 +3226,7 @@ class LitecoinCashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Litecoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Litecoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb0 @@ -3309,7 +3346,7 @@ class LitecoinZMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18LitecoinZ Signed Message:\n" + MESSAGE_PREFIX = "\x18LitecoinZ Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -3349,7 +3386,7 @@ class LkrcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18LKRcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18LKRcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb0 @@ -3389,7 +3426,7 @@ class LynxMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Lynx Signed Message:\n" + MESSAGE_PREFIX = "\x18Lynx Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xad @@ -3429,7 +3466,7 @@ class MazacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xe0 @@ -3469,7 +3506,7 @@ class MegacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Megacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Megacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb2 @@ -3509,7 +3546,7 @@ class MinexcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Bitcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -3549,7 +3586,7 @@ class MonacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Monacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Monacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb0 @@ -3589,7 +3626,7 @@ class MonkeyProjectMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "Monkey Signed Message:\n" + MESSAGE_PREFIX = "Monkey Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x37 @@ -3629,7 +3666,7 @@ class MyriadcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb2 @@ -3669,7 +3706,7 @@ class NIXMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Nix Signed Message:\n" + MESSAGE_PREFIX = "\x18Nix Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -3709,7 +3746,7 @@ class NamecoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -3789,7 +3826,7 @@ class NeblioMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Neblio Signed Message:\n" + MESSAGE_PREFIX = "\x18Neblio Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb5 @@ -3829,7 +3866,7 @@ class NeoscoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18NeosCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18NeosCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb1 @@ -3869,7 +3906,7 @@ class NeurocoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18PPCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18PPCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb5 @@ -3909,7 +3946,7 @@ class NewYorkCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18newyorkc Signed Message:\n" + MESSAGE_PREFIX = "\x18newyorkc Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbc @@ -3949,7 +3986,7 @@ class NovacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18NovaCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18NovaCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x88 @@ -3989,7 +4026,7 @@ class NuBitsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Nu Signed Message:\n" + MESSAGE_PREFIX = "\x18Nu Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x96 @@ -4029,7 +4066,7 @@ class NuSharesMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Nu Signed Message:\n" + MESSAGE_PREFIX = "\x18Nu Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x95 @@ -4069,7 +4106,7 @@ class OKCashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18OKCash Signed Message:\n" + MESSAGE_PREFIX = "\x18OKCash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x3 @@ -4189,7 +4226,7 @@ class OnixCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "ONIX Signed Message:\n" + MESSAGE_PREFIX = "ONIX Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xcb @@ -4229,7 +4266,7 @@ class PeercoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb7 @@ -4269,7 +4306,7 @@ class PesobitMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Pesobit Signed Message:\n" + MESSAGE_PREFIX = "\x18Pesobit Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb7 @@ -4309,7 +4346,7 @@ class PhoreMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Phore Signed Message:\n" + MESSAGE_PREFIX = "\x18Phore Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xd4 @@ -4349,7 +4386,7 @@ class PinkcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Pinkcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Pinkcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x83 @@ -4389,7 +4426,7 @@ class PivxMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xd4 @@ -4429,7 +4466,7 @@ class PivxTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -4469,7 +4506,7 @@ class PoswCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Poswcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Poswcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb7 @@ -4509,7 +4546,7 @@ class PotcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Potcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Potcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb7 @@ -4549,7 +4586,7 @@ class ProjectCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18ProjectCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18ProjectCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x75 @@ -4589,7 +4626,7 @@ class PutincoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18PutinCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18PutinCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xb7 @@ -4709,7 +4746,7 @@ class RSKMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18RSK Signed Message:\n" + MESSAGE_PREFIX = "\x18RSK Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -4749,7 +4786,7 @@ class RSKTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18RSK Testnet Signed Message:\n" + MESSAGE_PREFIX = "\x18RSK Testnet Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -4789,7 +4826,7 @@ class RapidsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "DarkNet Signed Message:\n" + MESSAGE_PREFIX = "DarkNet Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x2e @@ -4829,7 +4866,7 @@ class RavencoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "Raven Signed Message:\n" + MESSAGE_PREFIX = "Raven Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -4869,7 +4906,7 @@ class ReddcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Reddcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Reddcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbd @@ -4909,7 +4946,7 @@ class RubycoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Rubycoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Rubycoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbc @@ -4949,7 +4986,7 @@ class SafecoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Safecoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Safecoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbd @@ -4989,7 +5026,7 @@ class SaluscoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Salus Signed Message:\n" + MESSAGE_PREFIX = "\x18Salus Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -5029,7 +5066,7 @@ class ScribeMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x6e @@ -5069,7 +5106,7 @@ class ShadowCashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -5109,7 +5146,7 @@ class ShadowCashTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xff @@ -5149,7 +5186,7 @@ class SlimcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x46 @@ -5189,7 +5226,7 @@ class SlimcoinTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x57 @@ -5229,7 +5266,7 @@ class SmileycoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Smileycoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Smileycoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x5 @@ -5269,7 +5306,7 @@ class SolarcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18SolarCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18SolarCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x92 @@ -5309,7 +5346,7 @@ class StashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Stash Signed Message:\n" + MESSAGE_PREFIX = "\x18Stash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xcc @@ -5349,7 +5386,7 @@ class StratisMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Stratis Signed Message:\n" + MESSAGE_PREFIX = "\x18Stratis Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -5389,7 +5426,7 @@ class StratisTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Stratis Test Signed Message:\n" + MESSAGE_PREFIX = "\x18Stratis Test Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -5429,7 +5466,7 @@ class SugarchainMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Sugarchain Signed Message:\n" + MESSAGE_PREFIX = "\x18Sugarchain Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -5469,7 +5506,7 @@ class SugarchainTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Sugarchain Signed Message:\n" + MESSAGE_PREFIX = "\x18Sugarchain Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -5509,7 +5546,7 @@ class SyscoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Syscoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Syscoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -5549,7 +5586,7 @@ class TOACoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18TOA Signed Message:\n" + MESSAGE_PREFIX = "\x18TOA Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc1 @@ -5589,7 +5626,7 @@ class ThoughtAIMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x7b @@ -5671,7 +5708,7 @@ class TwinsMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x42 @@ -5711,7 +5748,7 @@ class TwinsTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xed @@ -5751,7 +5788,7 @@ class UltimateSecureCashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18UltimateSecureCash Signed Message:\n" + MESSAGE_PREFIX = "\x18UltimateSecureCash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xbf @@ -5791,7 +5828,7 @@ class UnobtaniumMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Unobtanium Signed Message:\n" + MESSAGE_PREFIX = "\x18Unobtanium Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xe0 @@ -5831,7 +5868,7 @@ class VPNCoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18VpnCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18VpnCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc7 @@ -5871,7 +5908,7 @@ class VcashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Vcash Signed Message:\n" + MESSAGE_PREFIX = "\x18Vcash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc7 @@ -5911,7 +5948,7 @@ class VergeCurrencyMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18VERGE Signed Message:\n" + MESSAGE_PREFIX = "\x18VERGE Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x9e @@ -5951,7 +5988,7 @@ class VertcoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Vertcoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Vertcoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -5991,7 +6028,7 @@ class ViacoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Viacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Viacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc7 @@ -6031,7 +6068,7 @@ class ViacoinTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Viacoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Viacoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xff @@ -6071,7 +6108,7 @@ class VivoMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18DarkCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18DarkCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc6 @@ -6111,7 +6148,7 @@ class WhitecoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Whitecoin Signed Message:\n" + MESSAGE_PREFIX = "\x18Whitecoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc9 @@ -6151,7 +6188,7 @@ class WincoinMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18WinCoin Signed Message:\n" + MESSAGE_PREFIX = "\x18WinCoin Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xc9 @@ -6191,7 +6228,7 @@ class XUEZMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = None + MESSAGE_PREFIX = None DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xd4 @@ -6271,7 +6308,7 @@ class ZClassicMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + MESSAGE_PREFIX = "\x18Zcash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -6311,7 +6348,7 @@ class ZcashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + MESSAGE_PREFIX = "\x18Zcash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 @@ -6351,7 +6388,7 @@ class ZcashTestnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + MESSAGE_PREFIX = "\x18Zcash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0xef @@ -6391,11 +6428,10 @@ class ZencashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + MESSAGE_PREFIX = "\x18Zcash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 - def get_cryptocurrency(symbol: str) -> Any: for _, cryptocurrency in inspect.getmembers(sys.modules[__name__]): From 886e28bdb41c32bb5b5ceb83e64790ee0443c51f Mon Sep 17 00:00:00 2001 From: David White Date: Thu, 1 Dec 2022 11:55:55 +1300 Subject: [PATCH 71/92] Add Flux Symbols --- hdwallet/symbols.py | 3 +++ tests/test_symbols.py | 1 + 2 files changed, 4 insertions(+) diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index c51d469..009897d 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -106,6 +106,8 @@ FRST = "FRST" # Flashcoin FLASH = "FLASH" +# Flux +FLUX = "FLUX" # Fuji Coin FJC = "FJC" # GCR Coin @@ -333,6 +335,7 @@ "ERC", "EXCL", "FIX", "FIXTEST", + "FLUX", "FTC", "FRST", "FLASH", diff --git a/tests/test_symbols.py b/tests/test_symbols.py index 4d9236d..5d9f77c 100644 --- a/tests/test_symbols.py +++ b/tests/test_symbols.py @@ -63,6 +63,7 @@ def test_symbols(): assert FTC == "FTC" assert FRST == "FRST" assert FLASH == "FLASH" + assert FLUX == "FLUX" assert FJC == "FJC" assert GCR == "GCR" assert GAME == "GAME" From f9d577ce26144c8e2f0483552101d718ffb8907e Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 2 Dec 2022 06:27:51 +1300 Subject: [PATCH 72/92] Add Flux to available cryptocurrencies --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b430564..8ec92f2 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,7 @@ This library simplifies the process of creating a new hierarchical deterministic | Feathercoin | `FTC` | Yes | No | No | 8 | `m/44'/8'/0'/0/0` | | Firstcoin | `FRST` | Yes | No | No | 167 | `m/44'/167'/0'/0/0` | | Flashcoin | `FLASH` | Yes | No | No | 120 | `m/44'/120'/0'/0/0` | +| [Flux](https://github.com/RunOnFlux/fluxd) | `FLUX` | Yes | No | No | 19167 | `m/44'/19167'/0'/0/0`| | Fuji Coin | `FJC` | Yes | No | Yes | 75 | `m/44'/75'/0'/0/0` | | GCR Coin | `GCR` | Yes | No | No | 49 | `m/44'/49'/0'/0/0` | | Game Credits | `GAME` | Yes | No | No | 101 | `m/44'/101'/0'/0/0` | From 9d15ab79e378f15af396f7de127d3c71a315484d Mon Sep 17 00:00:00 2001 From: Jake Nulty Date: Tue, 6 Dec 2022 10:16:29 -0500 Subject: [PATCH 73/92] fix typo --- hdwallet/symbols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hdwallet/symbols.py b/hdwallet/symbols.py index 811098e..bd6c75d 100644 --- a/hdwallet/symbols.py +++ b/hdwallet/symbols.py @@ -422,7 +422,7 @@ "WC", "XUEZ", "XDC", - "YEC" + "YEC", "ZCL", "ZEC", "ZECTEST", "ZEN" From 4d4779d4cf334ea5960a803681d56bb580522609 Mon Sep 17 00:00:00 2001 From: Meheret Tesfaye Date: Tue, 6 Dec 2022 21:56:11 +0300 Subject: [PATCH 74/92] Fix: typo error --- hdwallet/cryptocurrencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 9c90d17..5e83f01 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -6270,7 +6270,7 @@ class YcashMainnet(Cryptocurrency): "P2WSH_IN_P2SH": None }) - MASSAGE_PREFIX = "\x18Ycash Signed Message:\n" + MESSAGE_PREFIX = "\x18Ycash Signed Message:\n" DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 From 3f9448030581d1e84c58a3ee0377df24ee78320f Mon Sep 17 00:00:00 2001 From: Meheret Tesfaye Date: Tue, 6 Dec 2022 22:07:02 +0300 Subject: [PATCH 75/92] Drop: Python v3.6 package. --- .github/workflows/pythonpackage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3b4a0bd..3fce9cd 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@master From c0adb5010ae54c0f02356d4ab23ab2e5dbe4733a Mon Sep 17 00:00:00 2001 From: meherett Date: Tue, 6 Dec 2022 22:48:49 +0300 Subject: [PATCH 76/92] Fix: unsupported hash type of `ripemd160` package. --- tests/test_base58.py | 52 +++++++++++--------------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/tests/test_base58.py b/tests/test_base58.py index 4c696b5..a1848ef 100644 --- a/tests/test_base58.py +++ b/tests/test_base58.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 +from Crypto.Hash import keccak from binascii import ( hexlify, unhexlify ) import pytest +import hashlib +from hdwallet.libs.ripemd160 import ripemd160 from hdwallet.libs.base58 import ( checksum_encode, check_encode, check_decode, decode, encode, string_to_int ) @@ -30,59 +33,30 @@ 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" # 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 ) + 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 + data = 'hello'.encode("utf8") - """ - 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' + assert hexlify(hashlib.sha256(data).digest()) == b"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + assert hexlify(hashlib.sha3_256(data).digest()) == b"3338be694f50c5f338814986cdf0686453a888b84f424d792af4b9202398f392" + assert hexlify(hashlib.new('blake2s', data).digest()) == b"19213bacc58dee6dbde3ceb9a47cbb330b3d86f8cca8997eb00be456f140ca25" + + assert hexlify(ripemd160(data)) == b"108f07b8382412612c048d07d13f814118445acd" + + assert hexlify(keccak.new(data=data, digest_bits=256).digest()) == b'1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8' From 8a9e5ca84741417ae2abfb1b6d86967d142b622d Mon Sep 17 00:00:00 2001 From: meherett Date: Wed, 7 Dec 2022 16:17:19 +0300 Subject: [PATCH 77/92] Change: Ycash (YEC) cryptocurrency coin type into `347` index. --- README.md | 287 ++++++++++++++++++----------------- hdwallet/cryptocurrencies.py | 3 +- tests/test_symbols.py | 1 + 3 files changed, 147 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 8ec92f2..6100051 100644 --- a/README.md +++ b/README.md @@ -225,149 +225,150 @@ For more information, see the [CONTRIBUTING.md](https://github.com/meherett/hdwa This library simplifies the process of creating a new hierarchical deterministic wallets for: -| Cryptocurrencies | Symbols | Mainnet | Testnet | Segwit | Coin Type | Default Paths | -| :---------------------------------------------------------------- | :------------------: | :-----: | :-----: | :----: | :-------: | :------------------: | -| Anon | `ANON` | Yes | No | No | 220 | `m/44'/220'/0'/0/0` | -| Argoneum | `AGM` | Yes | No | No | 421 | `m/44'/421'/0'/0/0` | -| Artax | `XAX` | Yes | No | No | 219 | `m/44'/219'/0'/0/0` | -| Aryacoin | `AYA` | Yes | No | No | 357 | `m/44'/357'/0'/0/0` | -| Asiacoin | `AC` | Yes | No | No | 51 | `m/44'/51'/0'/0/0` | -| Atom | `ATOM` | Yes | No | Yes | 118 | `m/44'/118'/0'/0/0` | -| Auroracoin | `AUR` | Yes | No | No | 85 | `m/44'/85'/0'/0/0` | -| Axe | `AXE` | Yes | No | No | 4242 | `m/44'/4242'/0'/0/0` | -| Bata | `BTA` | Yes | No | No | 89 | `m/44'/89'/0'/0/0` | -| Beetle Coin | `BEET` | Yes | No | No | 800 | `m/44'/800'/0'/0/0` | -| Bela Coin | `BELA` | Yes | No | No | 73 | `m/44'/73'/0'/0/0` | -| Bit Cloud | `BTDX` | Yes | No | No | 218 | `m/44'/218'/0'/0/0` | -| Bit Send | `BSD` | Yes | No | No | 91 | `m/44'/91'/0'/0/0` | -| [Bitcoin Cash](https://github.com/bitcoincashorg/bitcoincash.org) | `BCH` | Yes | No | Yes | 145 | `m/44'/145'/0'/0/0` | -| [Bitcoin Gold](https://github.com/BTCGPU/BTCGPU) | `BTG` | Yes | No | Yes | 156 | `m/44'/156'/0'/0/0` | -| [Bitcoin](https://github.com/bitcoin/bitcoin) | `BTC`, `BTCTEST` | Yes | Yes | Yes | 0 | `m/44'/0'/0'/0/0` | -| Bitcoin Plus | `XBC` | Yes | No | No | 65 | `m/44'/65'/0'/0/0` | -| Bitcoin SV | `BSV` | Yes | No | No | 236 | `m/44'/236'/0'/0/0` | -| BitcoinZ | `BTCZ` | Yes | No | No | 177 | `m/44'/177'/0'/0/0` | -| Bitcore | `BTX` | Yes | No | Yes | 160 | `m/44'/160'/0'/0/0` | -| Blackcoin | `BLK` | Yes | No | No | 10 | `m/44'/10'/0'/0/0` | -| Block Stamp | `BST` | Yes | No | Yes | 254 | `m/44'/254'/0'/0/0` | -| Blocknode | `BND`, `BNDTEST` | Yes | Yes | No | 2941 | `m/44'/2941'/0'/0/0` | -| Bolivarcoin | `BOLI` | Yes | No | No | 278 | `m/44'/278'/0'/0/0` | -| Brit Coin | `BRIT` | Yes | No | No | 70 | `m/44'/70'/0'/0/0` | -| CPU Chain | `CPU` | Yes | No | Yes | 363 | `m/44'/363'/0'/0/0` | -| Canada eCoin | `CDN` | Yes | No | No | 34 | `m/44'/34'/0'/0/0` | -| Cannacoin | `CCN` | Yes | No | No | 19 | `m/44'/19'/0'/0/0` | -| Clams | `CLAM` | Yes | No | No | 23 | `m/44'/23'/0'/0/0` | -| Club Coin | `CLUB` | Yes | No | No | 79 | `m/44'/79'/0'/0/0` | -| Compcoin | `CMP` | Yes | No | No | 71 | `m/44'/71'/0'/0/0` | -| Crane Pay | `CRP` | Yes | No | Yes | 2304 | `m/44'/2304'/0'/0/0` | -| Crave | `CRAVE` | Yes | No | No | 186 | `m/44'/186'/0'/0/0` | -| [Dash](https://github.com/dashpay/dash) | `DASH`, `DASHTEST` | Yes | Yes | No | 5 | `m/44'/5'/0'/0/0` | -| Deep Onion | `ONION` | Yes | No | Yes | 305 | `m/44'/305'/0'/0/0` | -| Defcoin | `DFC` | Yes | No | No | 1337 | `m/44'/1337'/0'/0/0` | -| Denarius | `DNR` | Yes | No | No | 116 | `m/44'/116'/0'/0/0` | -| Diamond | `DMD` | Yes | No | No | 152 | `m/44'/152'/0'/0/0` | -| Digi Byte | `DGB` | Yes | No | Yes | 20 | `m/44'/20'/0'/0/0` | -| Digitalcoin | `DGC` | Yes | No | No | 18 | `m/44'/18'/0'/0/0` | -| [Dogecoin](https://github.com/dogecoin/dogecoin) | `DOGE`, `DOGETEST` | Yes | Yes | No | 3 | `m/44'/3'/0'/0/0` | -| EDR Coin | `EDRC` | Yes | No | No | 56 | `m/44'/56'/0'/0/0` | -| Ecoin | `ECN` | Yes | No | No | 115 | `m/44'/115'/0'/0/0` | -| Einsteinium | `EMC2` | Yes | No | No | 41 | `m/44'/41'/0'/0/0` | -| Elastos | `ELA` | Yes | No | No | 2305 | `m/44'/2305'/0'/0/0` | -| Energi | `NRG` | Yes | No | No | 9797 | `m/44'/9797'/0'/0/0` | -| [Ethereum](https://github.com/ethereum/go-ethereum) | `ETH` | Yes | No | Yes | 60 | `m/44'/60'/0'/0/0` | -| Europe Coin | `ERC` | Yes | No | No | 151 | `m/44'/151'/0'/0/0` | -| Exclusive Coin | `EXCL` | Yes | No | No | 190 | `m/44'/190'/0'/0/0` | -| FIX | `FIX`, `FIXTEST` | Yes | Yes | No | 336 | `m/44'/336'/0'/0/0` | -| Feathercoin | `FTC` | Yes | No | No | 8 | `m/44'/8'/0'/0/0` | -| Firstcoin | `FRST` | Yes | No | No | 167 | `m/44'/167'/0'/0/0` | -| Flashcoin | `FLASH` | Yes | No | No | 120 | `m/44'/120'/0'/0/0` | -| [Flux](https://github.com/RunOnFlux/fluxd) | `FLUX` | Yes | No | No | 19167 | `m/44'/19167'/0'/0/0`| -| Fuji Coin | `FJC` | Yes | No | Yes | 75 | `m/44'/75'/0'/0/0` | -| GCR Coin | `GCR` | Yes | No | No | 49 | `m/44'/49'/0'/0/0` | -| Game Credits | `GAME` | Yes | No | No | 101 | `m/44'/101'/0'/0/0` | -| Go Byte | `GBX` | Yes | No | No | 176 | `m/44'/176'/0'/0/0` | -| Gridcoin | `GRC` | Yes | No | No | 84 | `m/44'/84'/0'/0/0` | -| Groestl Coin | `GRS`, `GRSTEST` | Yes | Yes | Yes | 17 | `m/44'/17'/0'/0/0` | -| Gulden | `NLG` | Yes | No | No | 87 | `m/44'/87'/0'/0/0` | -| Helleniccoin | `HNC` | Yes | No | No | 168 | `m/44'/168'/0'/0/0` | -| Hempcoin | `THC` | Yes | No | No | 113 | `m/44'/113'/0'/0/0` | -| Hush | `HUSH` | Yes | No | No | 197 | `m/44'/197'/0'/0/0` | -| IX Coin | `IXC` | Yes | No | No | 86 | `m/44'/86'/0'/0/0` | -| Insane Coin | `INSN` | Yes | No | No | 68 | `m/44'/68'/0'/0/0` | -| Internet Of People | `IOP` | Yes | No | No | 66 | `m/44'/66'/0'/0/0` | -| Jumbucks | `JBS` | Yes | No | No | 26 | `m/44'/26'/0'/0/0` | -| Kobocoin | `KOBO` | Yes | No | No | 196 | `m/44'/196'/0'/0/0` | -| Komodo | `KMD` | Yes | No | No | 141 | `m/44'/141'/0'/0/0` | -| LBRY Credits | `LBC` | Yes | No | No | 140 | `m/44'/140'/0'/0/0` | -| Linx | `LINX` | Yes | No | No | 114 | `m/44'/114'/0'/0/0` | -| Litecoin Cash | `LCC` | Yes | No | No | 192 | `m/44'/192'/0'/0/0` | -| [Litecoin](https://github.com/litecoin-project/litecoin) | `LTC`, `LTCTEST` | Yes | Yes | Yes | 2 | `m/44'/2'/0'/0/0` | -| LitecoinZ | `LTZ` | Yes | No | No | 221 | `m/44'/221'/0'/0/0` | -| Lkrcoin | `LKR` | Yes | No | No | 557 | `m/44'/557'/0'/0/0` | -| Lynx | `LYNX` | Yes | No | No | 191 | `m/44'/191'/0'/0/0` | -| Mazacoin | `MZC` | Yes | No | No | 13 | `m/44'/13'/0'/0/0` | -| Megacoin | `MEC` | Yes | No | No | 217 | `m/44'/217'/0'/0/0` | -| Minexcoin | `MNX` | Yes | No | No | 182 | `m/44'/182'/0'/0/0` | -| Monacoin | `MONA` | Yes | No | Yes | 22 | `m/44'/22'/0'/0/0` | -| Monkey Project | `MONK` | Yes | No | Yes | 214 | `m/44'/214'/0'/0/0` | -| Myriadcoin | `XMY` | Yes | No | No | 90 | `m/44'/90'/0'/0/0` | -| NIX | `NIX` | Yes | No | Yes | 400 | `m/44'/400'/0'/0/0` | -| Namecoin | `NMC` | Yes | No | No | 7 | `m/44'/7'/0'/0/0` | -| [Navcoin](https://github.com/navcoin/navcoin-core) | `NAV` | Yes | No | No | 130 | `m/44'/130'/0'/0/0` | -| Neblio | `NEBL` | Yes | No | No | 146 | `m/44'/146'/0'/0/0` | -| Neoscoin | `NEOS` | Yes | No | No | 25 | `m/44'/25'/0'/0/0` | -| Neurocoin | `NRO` | Yes | No | No | 110 | `m/44'/110'/0'/0/0` | -| New York Coin | `NYC` | Yes | No | No | 179 | `m/44'/179'/0'/0/0` | -| Novacoin | `NVC` | Yes | No | No | 50 | `m/44'/50'/0'/0/0` | -| NuBits | `NBT` | Yes | No | No | 12 | `m/44'/12'/0'/0/0` | -| NuShares | `NSR` | Yes | No | No | 11 | `m/44'/11'/0'/0/0` | -| OK Cash | `OK` | Yes | No | No | 69 | `m/44'/69'/0'/0/0` | -| [Omni](https://github.com/omnilayer/omnicore) | `OMNI`, `OMNITEST` | Yes | Yes | No | 200 | `m/44'/200'/0'/0/0` | -| Onix Coin | `ONX` | Yes | No | No | 174 | `m/44'/174'/0'/0/0` | -| Peercoin | `PPC` | Yes | No | No | 6 | `m/44'/6'/0'/0/0` | -| Pesobit | `PSB` | Yes | No | No | 62 | `m/44'/62'/0'/0/0` | -| Phore | `PHR` | Yes | No | No | 444 | `m/44'/444'/0'/0/0` | -| Pinkcoin | `PINK` | Yes | No | No | 117 | `m/44'/117'/0'/0/0` | -| Pivx | `PIVX`, `PIVXTEST` | Yes | Yes | No | 119 | `m/44'/119'/0'/0/0` | -| Posw Coin | `POSW` | Yes | No | No | 47 | `m/44'/47'/0'/0/0` | -| Potcoin | `POT` | Yes | No | No | 81 | `m/44'/81'/0'/0/0` | -| Project Coin | `PRJ` | Yes | No | No | 533 | `m/44'/533'/0'/0/0` | -| Putincoin | `PUT` | Yes | No | No | 122 | `m/44'/122'/0'/0/0` | -| [Qtum](https://github.com/qtumproject/qtum) | `QTUM`, `QTUMTEST` | Yes | Yes | Yes | 2301 | `m/44'/2301'/0'/0/0` | -| RSK | `RBTC`, `RBTCTEST` | Yes | Yes | No | 137 | `m/44'/137'/0'/0/0` | -| Rapids | `RPD` | Yes | No | No | 320 | `m/44'/320'/0'/0/0` | -| Ravencoin | `RVN` | Yes | No | No | 175 | `m/44'/175'/0'/0/0` | -| Reddcoin | `RDD` | Yes | No | No | 4 | `m/44'/4'/0'/0/0` | -| Rubycoin | `RBY` | Yes | No | No | 16 | `m/44'/16'/0'/0/0` | -| Safecoin | `SAFE` | Yes | No | No | 19165 | `m/44'/19165'/0'/0/0` | -| Saluscoin | `SLS` | Yes | No | No | 572 | `m/44'/572'/0'/0/0` | -| Scribe | `SCRIBE` | Yes | No | No | 545 | `m/44'/545'/0'/0/0` | -| [Shadow Cash](https://github.com/shadowproject/shadow) | `SDC`, `SDCTEST` | Yes | Yes | No | 35 | `m/44'/35'/0'/0/0` | -| Slimcoin | `SLM`, `SLMTEST` | Yes | Yes | No | 63 | `m/44'/63'/0'/0/0` | -| Smileycoin | `SMLY` | Yes | No | No | 59 | `m/44'/59'/0'/0/0` | -| Solarcoin | `SLR` | Yes | No | No | 58 | `m/44'/58'/0'/0/0` | -| Stash | `STASH` | Yes | No | No | 49344 | `m/44'/49344'/0'/0/0` | -| Stratis | `STRAT`, `STRATTEST` | Yes | Yes | No | 105 | `m/44'/105'/0'/0/0` | -| Sugarchain | `SUGAR`, `SUGARTEST` | Yes | Yes | Yes | 408 | `m/44'/408'/0'/0/0` | -| Syscoin | `SYS` | Yes | No | Yes | 57 | `m/44'/57'/0'/0/0` | -| TOA Coin | `TOA` | Yes | No | No | 159 | `m/44'/159'/0'/0/0` | -| Thought AI | `THT` | Yes | No | No | 502 | `m/44'/502'/0'/0/0` | -| [Tron](https://github.com/tronprotocol/java-tron) | `TRX` | Yes | No | No | 195 | `m/44'/195'/0'/0/0` | -| Twins | `TWINS`, `TWINSTEST` | Yes | Yes | No | 970 | `m/44'/970'/0'/0/0` | -| Ultimate Secure Cash | `USC` | Yes | No | No | 112 | `m/44'/112'/0'/0/0` | -| Unobtanium | `UNO` | Yes | No | No | 92 | `m/44'/92'/0'/0/0` | -| Virtual Cash | `VASH` | Yes | No | No | 33 | `m/44'/33'/0'/0/0` | -| Vcash | `VC` | Yes | No | No | 127 | `m/44'/127'/0'/0/0` | -| Verge Currency | `XVG` | Yes | No | No | 77 | `m/44'/77'/0'/0/0` | -| Vertcoin | `VTC` | Yes | No | Yes | 28 | `m/44'/28'/0'/0/0` | -| [Viacoin](https://github.com/viacoin/viacore-viacoin) | `VIA`, `VIATEST` | Yes | Yes | Yes | 14 | `m/44'/14'/0'/0/0` | -| Vivo | `VIVO` | Yes | No | No | 166 | `m/44'/166'/0'/0/0` | -| Whitecoin | `XWC` | Yes | No | No | 559 | `m/44'/559'/0'/0/0` | -| Wincoin | `WC` | Yes | No | No | 181 | `m/44'/181'/0'/0/0` | -| 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](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` | +| Cryptocurrencies | Symbols | Mainnet | Testnet | Segwit | Coin Type | Default Paths | +|:------------------------------------------------------------------|:--------------------:|:-------:|:-------:|:------:|:---------:|:---------------------:| +| Anon | `ANON` | Yes | No | No | 220 | `m/44'/220'/0'/0/0` | +| Argoneum | `AGM` | Yes | No | No | 421 | `m/44'/421'/0'/0/0` | +| Artax | `XAX` | Yes | No | No | 219 | `m/44'/219'/0'/0/0` | +| Aryacoin | `AYA` | Yes | No | No | 357 | `m/44'/357'/0'/0/0` | +| Asiacoin | `AC` | Yes | No | No | 51 | `m/44'/51'/0'/0/0` | +| Atom | `ATOM` | Yes | No | Yes | 118 | `m/44'/118'/0'/0/0` | +| Auroracoin | `AUR` | Yes | No | No | 85 | `m/44'/85'/0'/0/0` | +| Axe | `AXE` | Yes | No | No | 4242 | `m/44'/4242'/0'/0/0` | +| Bata | `BTA` | Yes | No | No | 89 | `m/44'/89'/0'/0/0` | +| Beetle Coin | `BEET` | Yes | No | No | 800 | `m/44'/800'/0'/0/0` | +| Bela Coin | `BELA` | Yes | No | No | 73 | `m/44'/73'/0'/0/0` | +| Bit Cloud | `BTDX` | Yes | No | No | 218 | `m/44'/218'/0'/0/0` | +| Bit Send | `BSD` | Yes | No | No | 91 | `m/44'/91'/0'/0/0` | +| [Bitcoin Cash](https://github.com/bitcoincashorg/bitcoincash.org) | `BCH` | Yes | No | Yes | 145 | `m/44'/145'/0'/0/0` | +| [Bitcoin Gold](https://github.com/BTCGPU/BTCGPU) | `BTG` | Yes | No | Yes | 156 | `m/44'/156'/0'/0/0` | +| [Bitcoin](https://github.com/bitcoin/bitcoin) | `BTC`, `BTCTEST` | Yes | Yes | Yes | 0 | `m/44'/0'/0'/0/0` | +| Bitcoin Plus | `XBC` | Yes | No | No | 65 | `m/44'/65'/0'/0/0` | +| Bitcoin SV | `BSV` | Yes | No | No | 236 | `m/44'/236'/0'/0/0` | +| BitcoinZ | `BTCZ` | Yes | No | No | 177 | `m/44'/177'/0'/0/0` | +| Bitcore | `BTX` | Yes | No | Yes | 160 | `m/44'/160'/0'/0/0` | +| Blackcoin | `BLK` | Yes | No | No | 10 | `m/44'/10'/0'/0/0` | +| Block Stamp | `BST` | Yes | No | Yes | 254 | `m/44'/254'/0'/0/0` | +| Blocknode | `BND`, `BNDTEST` | Yes | Yes | No | 2941 | `m/44'/2941'/0'/0/0` | +| Bolivarcoin | `BOLI` | Yes | No | No | 278 | `m/44'/278'/0'/0/0` | +| Brit Coin | `BRIT` | Yes | No | No | 70 | `m/44'/70'/0'/0/0` | +| CPU Chain | `CPU` | Yes | No | Yes | 363 | `m/44'/363'/0'/0/0` | +| Canada eCoin | `CDN` | Yes | No | No | 34 | `m/44'/34'/0'/0/0` | +| Cannacoin | `CCN` | Yes | No | No | 19 | `m/44'/19'/0'/0/0` | +| Clams | `CLAM` | Yes | No | No | 23 | `m/44'/23'/0'/0/0` | +| Club Coin | `CLUB` | Yes | No | No | 79 | `m/44'/79'/0'/0/0` | +| Compcoin | `CMP` | Yes | No | No | 71 | `m/44'/71'/0'/0/0` | +| Crane Pay | `CRP` | Yes | No | Yes | 2304 | `m/44'/2304'/0'/0/0` | +| Crave | `CRAVE` | Yes | No | No | 186 | `m/44'/186'/0'/0/0` | +| [Dash](https://github.com/dashpay/dash) | `DASH`, `DASHTEST` | Yes | Yes | No | 5 | `m/44'/5'/0'/0/0` | +| Deep Onion | `ONION` | Yes | No | Yes | 305 | `m/44'/305'/0'/0/0` | +| Defcoin | `DFC` | Yes | No | No | 1337 | `m/44'/1337'/0'/0/0` | +| Denarius | `DNR` | Yes | No | No | 116 | `m/44'/116'/0'/0/0` | +| Diamond | `DMD` | Yes | No | No | 152 | `m/44'/152'/0'/0/0` | +| Digi Byte | `DGB` | Yes | No | Yes | 20 | `m/44'/20'/0'/0/0` | +| Digitalcoin | `DGC` | Yes | No | No | 18 | `m/44'/18'/0'/0/0` | +| [Dogecoin](https://github.com/dogecoin/dogecoin) | `DOGE`, `DOGETEST` | Yes | Yes | No | 3 | `m/44'/3'/0'/0/0` | +| EDR Coin | `EDRC` | Yes | No | No | 56 | `m/44'/56'/0'/0/0` | +| Ecoin | `ECN` | Yes | No | No | 115 | `m/44'/115'/0'/0/0` | +| Einsteinium | `EMC2` | Yes | No | No | 41 | `m/44'/41'/0'/0/0` | +| Elastos | `ELA` | Yes | No | No | 2305 | `m/44'/2305'/0'/0/0` | +| Energi | `NRG` | Yes | No | No | 9797 | `m/44'/9797'/0'/0/0` | +| [Ethereum](https://github.com/ethereum/go-ethereum) | `ETH` | Yes | No | Yes | 60 | `m/44'/60'/0'/0/0` | +| Europe Coin | `ERC` | Yes | No | No | 151 | `m/44'/151'/0'/0/0` | +| Exclusive Coin | `EXCL` | Yes | No | No | 190 | `m/44'/190'/0'/0/0` | +| FIX | `FIX`, `FIXTEST` | Yes | Yes | No | 336 | `m/44'/336'/0'/0/0` | +| Feathercoin | `FTC` | Yes | No | No | 8 | `m/44'/8'/0'/0/0` | +| Firstcoin | `FRST` | Yes | No | No | 167 | `m/44'/167'/0'/0/0` | +| Flashcoin | `FLASH` | Yes | No | No | 120 | `m/44'/120'/0'/0/0` | +| [Flux](https://github.com/RunOnFlux/fluxd) | `FLUX` | Yes | No | No | 19167 | `m/44'/19167'/0'/0/0` | +| Fuji Coin | `FJC` | Yes | No | Yes | 75 | `m/44'/75'/0'/0/0` | +| GCR Coin | `GCR` | Yes | No | No | 49 | `m/44'/49'/0'/0/0` | +| Game Credits | `GAME` | Yes | No | No | 101 | `m/44'/101'/0'/0/0` | +| Go Byte | `GBX` | Yes | No | No | 176 | `m/44'/176'/0'/0/0` | +| Gridcoin | `GRC` | Yes | No | No | 84 | `m/44'/84'/0'/0/0` | +| Groestl Coin | `GRS`, `GRSTEST` | Yes | Yes | Yes | 17 | `m/44'/17'/0'/0/0` | +| Gulden | `NLG` | Yes | No | No | 87 | `m/44'/87'/0'/0/0` | +| Helleniccoin | `HNC` | Yes | No | No | 168 | `m/44'/168'/0'/0/0` | +| Hempcoin | `THC` | Yes | No | No | 113 | `m/44'/113'/0'/0/0` | +| Hush | `HUSH` | Yes | No | No | 197 | `m/44'/197'/0'/0/0` | +| IX Coin | `IXC` | Yes | No | No | 86 | `m/44'/86'/0'/0/0` | +| Insane Coin | `INSN` | Yes | No | No | 68 | `m/44'/68'/0'/0/0` | +| Internet Of People | `IOP` | Yes | No | No | 66 | `m/44'/66'/0'/0/0` | +| Jumbucks | `JBS` | Yes | No | No | 26 | `m/44'/26'/0'/0/0` | +| Kobocoin | `KOBO` | Yes | No | No | 196 | `m/44'/196'/0'/0/0` | +| Komodo | `KMD` | Yes | No | No | 141 | `m/44'/141'/0'/0/0` | +| LBRY Credits | `LBC` | Yes | No | No | 140 | `m/44'/140'/0'/0/0` | +| Linx | `LINX` | Yes | No | No | 114 | `m/44'/114'/0'/0/0` | +| Litecoin Cash | `LCC` | Yes | No | No | 192 | `m/44'/192'/0'/0/0` | +| [Litecoin](https://github.com/litecoin-project/litecoin) | `LTC`, `LTCTEST` | Yes | Yes | Yes | 2 | `m/44'/2'/0'/0/0` | +| LitecoinZ | `LTZ` | Yes | No | No | 221 | `m/44'/221'/0'/0/0` | +| Lkrcoin | `LKR` | Yes | No | No | 557 | `m/44'/557'/0'/0/0` | +| Lynx | `LYNX` | Yes | No | No | 191 | `m/44'/191'/0'/0/0` | +| Mazacoin | `MZC` | Yes | No | No | 13 | `m/44'/13'/0'/0/0` | +| Megacoin | `MEC` | Yes | No | No | 217 | `m/44'/217'/0'/0/0` | +| Minexcoin | `MNX` | Yes | No | No | 182 | `m/44'/182'/0'/0/0` | +| Monacoin | `MONA` | Yes | No | Yes | 22 | `m/44'/22'/0'/0/0` | +| Monkey Project | `MONK` | Yes | No | Yes | 214 | `m/44'/214'/0'/0/0` | +| Myriadcoin | `XMY` | Yes | No | No | 90 | `m/44'/90'/0'/0/0` | +| NIX | `NIX` | Yes | No | Yes | 400 | `m/44'/400'/0'/0/0` | +| Namecoin | `NMC` | Yes | No | No | 7 | `m/44'/7'/0'/0/0` | +| [Navcoin](https://github.com/navcoin/navcoin-core) | `NAV` | Yes | No | No | 130 | `m/44'/130'/0'/0/0` | +| Neblio | `NEBL` | Yes | No | No | 146 | `m/44'/146'/0'/0/0` | +| Neoscoin | `NEOS` | Yes | No | No | 25 | `m/44'/25'/0'/0/0` | +| Neurocoin | `NRO` | Yes | No | No | 110 | `m/44'/110'/0'/0/0` | +| New York Coin | `NYC` | Yes | No | No | 179 | `m/44'/179'/0'/0/0` | +| Novacoin | `NVC` | Yes | No | No | 50 | `m/44'/50'/0'/0/0` | +| NuBits | `NBT` | Yes | No | No | 12 | `m/44'/12'/0'/0/0` | +| NuShares | `NSR` | Yes | No | No | 11 | `m/44'/11'/0'/0/0` | +| OK Cash | `OK` | Yes | No | No | 69 | `m/44'/69'/0'/0/0` | +| [Omni](https://github.com/omnilayer/omnicore) | `OMNI`, `OMNITEST` | Yes | Yes | No | 200 | `m/44'/200'/0'/0/0` | +| Onix Coin | `ONX` | Yes | No | No | 174 | `m/44'/174'/0'/0/0` | +| Peercoin | `PPC` | Yes | No | No | 6 | `m/44'/6'/0'/0/0` | +| Pesobit | `PSB` | Yes | No | No | 62 | `m/44'/62'/0'/0/0` | +| Phore | `PHR` | Yes | No | No | 444 | `m/44'/444'/0'/0/0` | +| Pinkcoin | `PINK` | Yes | No | No | 117 | `m/44'/117'/0'/0/0` | +| Pivx | `PIVX`, `PIVXTEST` | Yes | Yes | No | 119 | `m/44'/119'/0'/0/0` | +| Posw Coin | `POSW` | Yes | No | No | 47 | `m/44'/47'/0'/0/0` | +| Potcoin | `POT` | Yes | No | No | 81 | `m/44'/81'/0'/0/0` | +| Project Coin | `PRJ` | Yes | No | No | 533 | `m/44'/533'/0'/0/0` | +| Putincoin | `PUT` | Yes | No | No | 122 | `m/44'/122'/0'/0/0` | +| [Qtum](https://github.com/qtumproject/qtum) | `QTUM`, `QTUMTEST` | Yes | Yes | Yes | 2301 | `m/44'/2301'/0'/0/0` | +| RSK | `RBTC`, `RBTCTEST` | Yes | Yes | No | 137 | `m/44'/137'/0'/0/0` | +| Rapids | `RPD` | Yes | No | No | 320 | `m/44'/320'/0'/0/0` | +| Ravencoin | `RVN` | Yes | No | No | 175 | `m/44'/175'/0'/0/0` | +| Reddcoin | `RDD` | Yes | No | No | 4 | `m/44'/4'/0'/0/0` | +| Rubycoin | `RBY` | Yes | No | No | 16 | `m/44'/16'/0'/0/0` | +| Safecoin | `SAFE` | Yes | No | No | 19165 | `m/44'/19165'/0'/0/0` | +| Saluscoin | `SLS` | Yes | No | No | 572 | `m/44'/572'/0'/0/0` | +| Scribe | `SCRIBE` | Yes | No | No | 545 | `m/44'/545'/0'/0/0` | +| [Shadow Cash](https://github.com/shadowproject/shadow) | `SDC`, `SDCTEST` | Yes | Yes | No | 35 | `m/44'/35'/0'/0/0` | +| Slimcoin | `SLM`, `SLMTEST` | Yes | Yes | No | 63 | `m/44'/63'/0'/0/0` | +| Smileycoin | `SMLY` | Yes | No | No | 59 | `m/44'/59'/0'/0/0` | +| Solarcoin | `SLR` | Yes | No | No | 58 | `m/44'/58'/0'/0/0` | +| Stash | `STASH` | Yes | No | No | 49344 | `m/44'/49344'/0'/0/0` | +| Stratis | `STRAT`, `STRATTEST` | Yes | Yes | No | 105 | `m/44'/105'/0'/0/0` | +| Sugarchain | `SUGAR`, `SUGARTEST` | Yes | Yes | Yes | 408 | `m/44'/408'/0'/0/0` | +| Syscoin | `SYS` | Yes | No | Yes | 57 | `m/44'/57'/0'/0/0` | +| TOA Coin | `TOA` | Yes | No | No | 159 | `m/44'/159'/0'/0/0` | +| Thought AI | `THT` | Yes | No | No | 502 | `m/44'/502'/0'/0/0` | +| [Tron](https://github.com/tronprotocol/java-tron) | `TRX` | Yes | No | No | 195 | `m/44'/195'/0'/0/0` | +| Twins | `TWINS`, `TWINSTEST` | Yes | Yes | No | 970 | `m/44'/970'/0'/0/0` | +| Ultimate Secure Cash | `USC` | Yes | No | No | 112 | `m/44'/112'/0'/0/0` | +| Unobtanium | `UNO` | Yes | No | No | 92 | `m/44'/92'/0'/0/0` | +| Virtual Cash | `VASH` | Yes | No | No | 33 | `m/44'/33'/0'/0/0` | +| Vcash | `VC` | Yes | No | No | 127 | `m/44'/127'/0'/0/0` | +| Verge Currency | `XVG` | Yes | No | No | 77 | `m/44'/77'/0'/0/0` | +| Vertcoin | `VTC` | Yes | No | Yes | 28 | `m/44'/28'/0'/0/0` | +| [Viacoin](https://github.com/viacoin/viacore-viacoin) | `VIA`, `VIATEST` | Yes | Yes | Yes | 14 | `m/44'/14'/0'/0/0` | +| Vivo | `VIVO` | Yes | No | No | 166 | `m/44'/166'/0'/0/0` | +| Whitecoin | `XWC` | Yes | No | No | 559 | `m/44'/559'/0'/0/0` | +| Wincoin | `WC` | Yes | No | No | 181 | `m/44'/181'/0'/0/0` | +| 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` | +| [Ycash](https://github.com/ycashfoundation/ycash) | `YEC` | Yes | No | Yes | 347 | `m/44'/347'/0'/0/0` | +| ZClassic | `ZCL` | Yes | No | No | 147 | `m/44'/147'/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 diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 0e37b7d..1aeb3ad 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -6279,7 +6279,7 @@ class YcashMainnet(Cryptocurrency): NETWORK = "mainnet" SOURCE_CODE = "https://github.com/ycashfoundation/ycash" COIN_TYPE = CoinType({ - "INDEX": 133, + "INDEX": 347, "HARDENED": True }) @@ -6471,6 +6471,7 @@ class ZencashMainnet(Cryptocurrency): DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" WIF_SECRET_KEY = 0x80 + def get_cryptocurrency(symbol: str) -> Any: for _, cryptocurrency in inspect.getmembers(sys.modules[__name__]): diff --git a/tests/test_symbols.py b/tests/test_symbols.py index 5d9f77c..32065c6 100644 --- a/tests/test_symbols.py +++ b/tests/test_symbols.py @@ -160,6 +160,7 @@ def test_symbols(): assert WC == "WC" assert XUEZ == "XUEZ" assert XDC == "XDC" + assert YEC == "YEC" assert ZCL == "ZCL" assert ZEC == "ZEC" assert ZECTEST == "ZECTEST" From e149f915342f578d9db73d60e3ffbfeb5a28443e Mon Sep 17 00:00:00 2001 From: meherett Date: Wed, 7 Dec 2022 16:25:04 +0300 Subject: [PATCH 78/92] Drop: Python version 3.6 package removed. --- .github/workflows/pythonpackage.yml | 16 ++++++++++------ .travis.yml | 2 -- setup.py | 1 - tox.ini | 10 +--------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3fce9cd..eceed53 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -1,21 +1,25 @@ # This workflow will install Swap package dependencies, run tests and lint with a variety of Python versions. -name: workflow +name: hdwallet-workflow on: push: - branches: [master] + branches: [ + master + ] pull_request: - branches: [master] + branches: [ + master + ] jobs: python: - runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - + python-version: [ + "3.7", "3.8", "3.9", "3.10", "3.11" + ] steps: - uses: actions/checkout@master - name: Set up Python ${{ matrix.python-version }} diff --git a/.travis.yml b/.travis.yml index 0ccfdca..abb78b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ install: - pip install -e .[cli,tests,docs] tox-travis coveralls matrix: include: - - name: "3.6" - python: 3.6 - name: "3.7" python: 3.7 - name: "3.8" diff --git a/setup.py b/setup.py index 411c043..f05a5ef 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,6 @@ classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: ISC License (ISCL)", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tox.ini b/tox.ini index c400b8f..54bef60 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,14 @@ [tox] -envlist = python36,python37,python38,python39,python310,python311 +envlist = python37,python38,python39,python310,python311 [travis] python = - 3.6: python36 3.7: python37 3.8: python38 3.9: python39 3.10: python310 3.11: python311 -[testenv:python36] -install_command = - python -m pip install --upgrade pip {opts} {packages} - pip install -e . {opts} {packages} -extras = cli,tests,docs -commands = python -m pytest - [testenv:python37] install_command = python -m pip install --upgrade pip {opts} {packages} From 57aafde6f4a4861ed4ed3428b9a16fea5a0f389f Mon Sep 17 00:00:00 2001 From: meherett Date: Wed, 7 Dec 2022 17:43:23 +0300 Subject: [PATCH 79/92] Add: `cli` on extra requirements & bump python to 3.11 version. --- .readthedocs.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 12d2ae5..2514848 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,9 +7,10 @@ sphinx: fail_on_warning: false python: - version: 3.8 + version: 3.11 install: - method: pip path: . extra_requirements: - - docs \ No newline at end of file + - cli + - docs From dfee315dc9e0249d562f5fe2ad8fb59f192e1dfc Mon Sep 17 00:00:00 2001 From: meherett Date: Wed, 7 Dec 2022 17:47:12 +0300 Subject: [PATCH 80/92] Bump: Python-HDWallet from `v2.1.2` to `v2.2.0` package. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f05a5ef..739e407 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="hdwallet", - version="v2.1.2", + version="v2.2.0", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, From da3a3b8ab1747a9f20c700bd6904a6010c2db0ab Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:08:11 +0300 Subject: [PATCH 81/92] Drop: default language param from mnemonic func for auto selection. --- hdwallet/cli/generate/addresses.py | 2 +- hdwallet/cli/generate/hdwallet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hdwallet/cli/generate/addresses.py b/hdwallet/cli/generate/addresses.py index 60881f3..0f37ed3 100644 --- a/hdwallet/cli/generate/addresses.py +++ b/hdwallet/cli/generate/addresses.py @@ -45,7 +45,7 @@ def generate_addresses( ) elif mnemonic: hdwallet.from_mnemonic( - mnemonic=mnemonic, language=language, passphrase=passphrase + mnemonic=mnemonic, passphrase=passphrase ) elif seed: hdwallet.from_seed( diff --git a/hdwallet/cli/generate/hdwallet.py b/hdwallet/cli/generate/hdwallet.py index c83db28..d7e0172 100644 --- a/hdwallet/cli/generate/hdwallet.py +++ b/hdwallet/cli/generate/hdwallet.py @@ -47,7 +47,7 @@ def generate_hdwallet( ) elif mnemonic: hdwallet.from_mnemonic( - mnemonic=mnemonic, language=language, passphrase=passphrase + mnemonic=mnemonic, passphrase=passphrase ) elif seed: hdwallet.from_seed( From be9ff19880acae6ec86ed7abd66a0144118d7bd5 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:34:56 +0300 Subject: [PATCH 82/92] Change: LICENSE from `ISC` to The MIT License `MIT`. --- LICENSE | 30 ++++++++++++++++++------------ hdwallet/hdwallet.py | 34 +++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/LICENSE b/LICENSE index 01cebff..db727bf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,21 @@ -ISC License +The MIT License (MIT) -Copyright © 2021, Meheret Tesfaye Batu +Copyright © 2021-2022, Meheret Tesfaye Batu -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index c9ff5fa..f9dd4b1 100644 --- a/hdwallet/hdwallet.py +++ b/hdwallet/hdwallet.py @@ -1,19 +1,27 @@ #!/usr/bin/env python3 """ -Copyright © 2021, Meheret Tesfaye Batu - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The MIT License (MIT) + +Copyright © 2021-2022 by Meheret Tesfaye Batu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. """ from ecdsa.curves import SECP256k1 From fa2408e8434aa182b84be0307aca8d2f1d1930d0 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:38:58 +0300 Subject: [PATCH 83/92] Add: build with `ubuntu-22.04` os for Python `3.11` version. --- .readthedocs.yaml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 2514848..51d490c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,16 +1,25 @@ +# .readthedocs.yaml + version: 2 -formats: all -sphinx: - builder: html - configuration: docs/conf.py - fail_on_warning: false +build: + os: ubuntu-22.04 + tools: + python: 3.11 + commands: + - pip install --upgrade pip python: - version: 3.11 install: - method: pip path: . extra_requirements: - cli - docs + +formats: all + +sphinx: + builder: html + configuration: docs/conf.py + fail_on_warning: false From c3fe22bf1d731be7b0614fa932aec119c85b6e50 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:40:27 +0300 Subject: [PATCH 84/92] Add: `hdwallet-cli.svg` file. --- docs/static/svg/hdwallet-cli.svg | 312 +++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 docs/static/svg/hdwallet-cli.svg diff --git a/docs/static/svg/hdwallet-cli.svg b/docs/static/svg/hdwallet-cli.svg new file mode 100644 index 0000000..14990e4 --- /dev/null +++ b/docs/static/svg/hdwallet-cli.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + meheret@meherett:~$ meheret@meherett:~$ h meheret@meherett:~$ hd meheret@meherett:~$ hdw meheret@meherett:~$ hdwa meheret@meherett:~$ hdwal meheret@meherett:~$ hdwall meheret@meherett:~$ hdwalle meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet Usage: hdwallet [OPTIONS] COMMAND [ARGS]...Options: -v, --version Show HDWallet version and exit. -h, --help Show this message and exit.Commands: generate (g) Select Generate for HDWallet. list (l) Select List for HDWallet information.meheret@meherett:~$ c meheret@meherett:~$ cl meheret@meherett:~$ cle meheret@meherett:~$ clea meheret@meherett:~$ clear meheret@meherett:~$ clear meheret@meherett:~$ meheret@meherett:~$ h meheret@meherett:~$ hd meheret@meherett:~$ hdw meheret@meherett:~$ hdwa meheret@meherett:~$ hdwal meheret@meherett:~$ hdwall meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet g meheret@meherett:~$ hdwallet ge meheret@meherett:~$ hdwallet gen meheret@meherett:~$ hdwallet gene meheret@meherett:~$ hdwallet gener meheret@meherett:~$ hdwallet genera meheret@meherett:~$ hdwallet generat meheret@meherett:~$ hdwallet generate meheret@meherett:~$ hdwallet generate meheret@meherett:~$ hdwallet generate - meheret@meherett:~$ hdwallet generate -- meheret@meherett:~$ hdwallet generate --s meheret@meherett:~$ hdwallet generate --sy meheret@meherett:~$ hdwallet generate --sym meheret@meherett:~$ hdwallet generate --symb meheret@meherett:~$ hdwallet generate --symbo meheret@meherett:~$ hdwallet generate --symbol meheret@meherett:~$ hdwallet generate --symbol meheret@meherett:~$ hdwallet generate --symbol Q meheret@meherett:~$ hdwallet generate --symbol QT meheret@meherett:~$ hdwallet generate --symbol QTU meheret@meherett:~$ hdwallet generate --symbol QTUM meheret@meherett:~$ hdwallet generate --symbol QTUM { "cryptocurrency": "Qtum", "symbol": "QTUM", "network": "mainnet", "strength": 128, "entropy": "29ce968ac3606c3a7bd11ffba28f2b28", "mnemonic": "clap input pencil man almost brush urban catalog wing behave skull faculty", "language": "english", "passphrase": null, "seed": "346753900c4c3e8e19c2b5f535ef4a6894bb25591904381b1e1229b14adc7bc8179568fd27ed77cbc49b27ff594d8d5eb4e67877a306b1e176c9c16d854040fe", "root_xprivate_key": "xprv9s21ZrQH143K2Q2ovmMfhJjn7jk7V4BW7TrghQyCxE7rpo2ZYVzmXHusyPxTm5dmLsy411Qy8QEY4cZGRSw8k2d3zBNQ3pYTrRch4Yxsnxp", "root_xpublic_key": "xpub661MyMwAqRbcEt7H2ntg4SgWfmabtWuMUgnHVoNpWZeqhbMi63K256EMpfHXA3JScJNe9TsftFuicz16owjSMw6b2NCX5iHEGGvUdvpJ67j", "xprivate_key": "xprvA38LBjBU5PvsrKPrS7Fx2HbR2nyRc1mxxWDBodjw64nEmoUaEPFMkLJ3HioRGFfuj8LX3NEZTA2KdisLpgTwFptUgusTt46EuVvGVhTtTs1", "xpublic_key": "xpub6G7gbEiMumVB4oUKY8nxPRY9apov1UVpKj8nc29YeQKDeboimvZcJ8cX8zbK7xCPUb7qbqJPqEmwrcZzKuZKy6x16BqNpMuC42V4fLuGcKF", "uncompressed": " "uncompressed": "9f79144e375383aff745f03a814097bf6614d20343b80aad734335cab43a973b3d0e908be915f90c79ecb89c12cb286eb0f49fb0a67d983c006d5e2524c24ccc", "compressed": "029f79144e375383aff745f03a814097bf6614d20343b80aad734335cab43a973b", "chain_code": "4d14397b48d60a89bf76bff01629165b980cf8604e5d9fe7ef09e810e78dfd47", "private_key": "3b467a3fa9fe7d9e66c1bf77cfdc4dcfe552154295f06e6ad25196a6521047ad", "public_key": "029f79144e375383aff745f03a814097bf6614d20343b80aad734335cab43a973b", "wif": "KyCw9KY8PDBQ9pDzErdpfcW1HhR9gtNrR3QgNiSbuLzPFhHdgT3P", "finger_print": "6001ae5e", "semantic": "p2pkh", "path": "m/44'/2301'/0'/0/0", "hash": "6001ae5e1bc833de0eb83cb7dae20b4a0f45524a", "addresses": { "p2pkh": "QVMczXBHSxY7upAXHJKYCGz53Ma5BzkHof", "p2sh": "MNfPLr1K6Ez6TjxZHpiR2cTCpA863HR9fG", "p2wpkh": "qc11qvqq6uhsmeqeaur4c8jma4cstfg8525j2mjr3sd", "p2wpkh_in_p2sh": "M9pJejvuAKrVdKFB58uDgST3LAjkDd6Lrs", "p2wsh": "qc11qaxxx23hn5s4x2drsgqmaj4jcsv7p0hnl343 "p2wsh": "qc11qaxxx23hn5s4x2drsgqmaj4jcsv7p0hnl343w8q4s9ejs26mkucdsy4cnpg", "p2wsh_in_p2sh": "MW3V33v8qJNFWAzLRwgaRP7eHEpWV6MsRN" }} meheret@meherett:~$ meheret@meherett:~$ h meheret@meherett:~$ hd meheret@meherett:~$ hdw meheret@meherett:~$ hdwa meheret@meherett:~$ hdwal meheret@meherett:~$ hdwall meheret@meherett:~$ hdwalle meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet g meheret@meherett:~$ hdwallet ge meheret@meherett:~$ hdwallet gen meheret@meherett:~$ hdwallet gene meheret@meherett:~$ hdwallet gener meheret@meherett:~$ hdwallet genera meheret@meherett:~$ hdwallet generat meheret@meherett:~$ hdwallet generate meheret@meherett:~$ hdwallet generate meheret@meherett:~$ hdwallet generate a meheret@meherett:~$ hdwallet generate ad meheret@meherett:~$ hdwallet generate add meheret@meherett:~$ hdwallet generate addr meheret@meherett:~$ hdwallet generate addre meheret@meherett:~$ hdwallet generate addres meheret@meherett:~$ hdwallet generate address meheret@meherett:~$ hdwallet generate addresse meheret@meherett:~$ hdwallet generate addresses meheret@meherett:~$ hdwallet generate addresses meheret@meherett:~$ hdwallet generate addresses - meheret@meherett:~$ hdwallet generate addresses -- meheret@meherett:~$ hdwallet generate addresses --s meheret@meherett:~$ hdwallet generate addresses --sy meheret@meherett:~$ hdwallet generate addresses --sym meheret@meherett:~$ hdwallet generate addresses --symb meheret@meherett:~$ hdwallet generate addresses --symbo meheret@meherett:~$ hdwallet generate addresses --symbol meheret@meherett:~$ hdwallet generate addresses --symbol meheret@meherett:~$ hdwallet generate addresses --symbol T meheret@meherett:~$ hdwallet generate addresses --symbol TR meheret@meherett:~$ hdwallet generate addresses --symbol TRX meheret@meherett:~$ hdwallet generate addresses --symbol TRX meheret@meherett:~$ hdwallet generate addresses --symbol TRX - meheret@meherett:~$ hdwallet generate addresses --symbol TRX -- meheret@meherett:~$ hdwallet generate addresses --symbol TRX --m meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mn meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mne meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnem meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemo meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemon meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemoni meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic " meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" - meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" -- meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --e meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --en meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end- meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-i meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-in meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-ind meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-inde meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-index meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-index meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-index 3 meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-index 30 meheret@meherett:~$ hdwallet generate addresses --symbol TRX --mnemonic "bright demand olive glance crater key head glory quantum leisure intact age" --end-index 30 m/44'/195'/0'/0/0 TQ1zagMpHAfHkJ1P4RdrCLas2NVaBMkuUT 029b7a26adc6eb619b87a726b5406b8bd2a3296072a0e2157a61db1a06c90d664a KwjDSqmczRaZLFP3e5xV1CydvsCLrwPTEzj7sGCShvg5FtQpMSRc m/44'/195'/0'/0/1 TKjvZzLn8jiWs31fAeX6uZN7Hz7QzpWCRb 02911c3b471321afea66ad319eb1cab8efbcc090e554868e995ba318de02f1d549 KztBEMcLabJzjYT8jwQL8qPaAXpxt8Gyrba91mRXD1f5kctZ99sk m/44'/195'/0'/0/2 TTdKxmbJQdRXgRvgcVJ5arPZDZhhFyqPPH 03971ff2ec684e61747d9c2f699cd72d164aa0b76964cf5c38666b16aaeed7f9b4 KxFZ2qpFGqBYtUB3iN9rLcaeZnFZZ2zuteecnMemUBVYAmuKBKWm m/44'/195'/0'/0/3 TTyGhrkj5BZxspTygGqaXuaRq3o9fb4qQA 02785ba6a95e3b57cb828e1979f68e6d076b3c3c4be53bae1098288b84bee87b27 KxiDoDPTN7FJkGZvr5AjDT5bjrZmD1xbfQSkVDpMymHAtVUg19QC m/44'/195'/0'/0/4 TEDasJFGHY7WXySZJsnDtAH1oKF2YGCdn7 02a1e4740f0d07985687c7beaf71b39f4f7342497cccb1efc2976dd8934f8d6dd1 KyZHqZhnEs94whkZNd692oZn8SRp5FWxJapZjH1MwLr7ookpX4LR m/44'/195'/0'/0/5 TDXa4PypD4btqJf8iFuo8LokbPT9q6NKXZ 0347002a0053d5310828acc3ca8d4ea8f8c6473b6f964f0a56c0737d28c13efd10 L46yP2f5kopuGmhgi4nZW5EKUEVVvNxxhPbEnWMXByfSDxXfezkq m/44'/195'/0'/0/6 TJgSaKajDbDvv1MsijP2PPKnC9AccuNqME 02ab61c2c68a6235ca1dddf691b664e19954c38736e36621d408e6361813a00706 L51KvG7sEwCMKcoF9VBRWasLrABXypFZwTv6HtRCwrNsN4Y8vTJC m/44'/195'/0'/0/7 TZ6UQMXbvxwA3SqNxiaqwH7tPR574cad9f 02feac616db00651ff0718bf1276bca09a4e1ba861dab8ebc8d9dd1713693fe88f KyRgBKNXyRE9aJAGFGEbp9PCiEib7pbWnXsQbpbWQT2jRob62Wmo m/44'/195'/0'/0/8 TZHq4W99r3jYqF1VZkSnNiddrNq9jEBKKC 02b46a81fd7822e424f197be6e22d916b25d9acf8687886c2273c6e51e2669227d KzxUtnQpRTzjXzFyG4JoJJdNMi62ugp4M4hA4NP4V7R9uTEq84PD m/44'/195'/0'/0/9 TPqHvZyRnNjbn2uuKWEDRKrbDjTAx1ioy5 0211e66cd8f0aeb9564459afbf9040effba2386668f4b98137fcf68daf3e64ad5b L4snKWy8o2kS8iksx82vU4xCxSVwrRgsLZLMqsKMSCzGoodMTYQG m/44'/195'/0'/0/10 TWAG8HBPrA7i52pLXfsPu2hBwjqMLE2NCN 0232a0ae9829418793804899f7d7823ce1564682fdc3be834e5abe93d7392b885f L5L9rvwybjUhtDmLoPu4GZCyb4JnFTFMZz8N5QhPZ9KzvYgt5qG4 m/44'/195'/0'/0/11 TLg8etRwRvx7tKvcAWAvs3792RcCNWQ6tb 025403d1ccda817fb2665a28cd26895181655b80266f8fc09a321b0dee2fe57553 L5bA1TqwsKac4bKWr7VJtZGUBUNH6oXQcBFjSotCEL813vtNFutV m/44'/195'/0'/0/12 TPVz8VMQzE89mF6VrSdHCommVPqtq64mmH 03e46b9d2291854d7f50a497dec96fb48c9f7b47e5fd5688e810d0124fb41696af L112D1CZpB7wiEg19HcFWRR7JYx7EBPQ9e8BqBzk9QEi9ZcWYycg m/44'/195'/0'/0/13 TVaAwMiuv9HP9LDEQ5jbsyoPKEG92dt1K8 0375bfdc1339ad8ea75ed9e03bcab04e427ba51b2fcdd5ed135891d4b4c0272445 L5d9roSm26vxBLx71rL6uQhZG6zJ9AERMuNVnrTCNSZRoE8fHNkt m/44'/195'/0'/0/14 TYrV7tNXw3UesHQpEC7oyKQW2wDqqtv3GN 03e07692db1b4c5fb79c0d440d9d2d9afeda7a6c37c275eccfce7b434a4792539f L42XFpYHp5WAi1uUMsCWQzxdf6BcFkYDdiXkNtVbZ3dLAeL1DdQb m/44'/195'/0'/0/15 TXVGx5Kfy6nBFc9rxeBdUV9P9Cc6Hc9jPL 0279caecefba0e50691a3a11e42b97fc307afb16abd37a642712288733add69e37 KwgiCbEXviTLT74vWnZq8hNpCAhUzpUgeHXptJnMUR1zBNGVYe7b m/44'/195'/0'/0/16 TV1bLkoaiTXJeCfe1pKnvKtP9XcmzcrqZF 02902b569f8591fe4470666e352d14c13b3bc3c18f615569baf0befac518f33eae L3Hrg6AkgwHAQ9V4KtjyQc3Csqq4VGDhZ135KkYTTu49yvVq6dK3m/44'/195'/0'/0/17 TMR2WpaPuKf2zwTzbPfqNbVRDF2ibokLff 020d8c4cc8fefb4ed734cc5584384313f3aefbac27a3d340f346613d41394a64a3 L3AkJLU39uQf8VbfFeYY2iy3vTCAi8Ts9HCGueCxjd3LRREmYJxG m/44'/195'/0'/0/18 TSBEeZEcqBqGCHoKyr2cFmWggcN6tMhECH 0399066a7946396d6dc8495d7ce2ad541651d929c233c9d7f0bf4bc0dafb02ced2 L3EoBHNbSD9c1Qjt5RAdnqWvuTW6mb1eVvTp5zam787CaTCaWBDw m/44'/195'/0'/0/19 TW1LC4KUTuUfWEiPWV8y9HvzCQz42g1E4o 03dfcde88b940359a81774e878aa698a39b3d68ac1aea6c743708416b90ecd14ba L3RMqBbeAbQNZHtLyihm6HzSqt4T3smwCU9hzbwKRDeLPYp2LaCw m/44'/195'/0'/0/20 TPX382v5n8uzzTBgcGtAp6eMc5wrcS67qB 03bbae4f70402653991a32ee715f86c6db7efae88e8efdd9ad165f87276cefbbd8 L43jGQoHVtw48QrBJnjSXP8b9vmazBV7AQfPkGGx2dgKxua3kVdL m/44'/195'/0'/0/21 TFao1cUyx6BHLTJ4ydmB1TwCPunA4MPakk 02ad5b42b1e6122ea9886f346fdc2459892c4d879cc2729aea826c429782960906 KxEMNNfHWtFsoBamMHxiLxt4fFKcpmsQcj2iAhyRZyHqqcocrn9q m/44'/195'/0'/0/22 TRWXKUT7mqW5aDyGdRdC6BGEqyWRBt8sFB 034e1aa4d9e31e8614447e31cb9801a533df7aa5d9fdce8d7c98c46cef4afd576e KwdSMU3uasveW6Zshf4eBVeYha2VgMxKDkpM9pyXUF5GZZnP13TS m/44'/195'/0'/0/23 TSAd6nnKkzjCHvkDMt2yvfrVmZTJgAHzU1 039a5d9e4f1ae50fb067bd74113c37949e3217d9232d04f0f705d4eb58b74e25aa KyAMrqzEyy4cYygE6ssnvEpyiyERucXVpmqxJzgbbqw3z9a87iYC m/44'/195'/0'/0/24 TYhGooxqWEDh2jfuQNdLfpJz7QVKyQd5rx 029030ae7b41124a8056e0925815f61fbada3960c671f57b5e68ea01b906cc8175 KymQWvM6vgZHZXAnCPczAGoU6vGpMz6pgFsFZYie8EUQLLzFpyKWm/44'/195'/0'/0/25 TLfPTruqhoFHre8iYFnH4nQvhMEZ9Zy9tG 0205ca1d3d5e4bef5bb36877b4cc9541d81353433578daf8894127776818703568 KxuwJpqBSteXTBqq9fWpQgRZ7w7karug9aEaeE7ajgismMR3yTjr m/44'/195'/0'/0/26 TGRzU4XU3FpftNm26xYv5nyEvB9eJHAizN 03cc5f2bdcb761718ae26d9cf8dd7e3d5c1c91af20a7395f16f9992e98b1917acf L1DTmQSjKCj23b7RPxiJbr2ytfmmeyRrYDoEVHY9bS8YdV1fgjES m/44'/195'/0'/0/27 TVdEwPAuD897d4p9jui6vHgcpwGtxqwzBF 024ded326a286155f2c056e8a9ffb14f07d06c71b990afb9c6d29255273e08a8db L2VTjhgXiSSSGenA3sLz67As8DoJxjMU5nTJCFsSBigotJJkNPSP m/44'/195'/0'/0/28 TE17yisStvQokoZBavYY6yBtxjZMJGvqfN 029baa8da7c4077851d99041384558b7ff1055fa5dbe5c36f5634db39347f33530 L56RFzmW2viLAZpWbuYU6J13EkLoJ1XV15Mt7Dq1RQ3Edxabsg9E m/44'/195'/0'/0/29 THRsfRLt1tYnHzhvxLVYeEngejWtP2reHn 022fd7ffe8fd5474303a3d695a36ab1db861c8120e9e95cf9d8244dfc794928947 L5ZWTJGTJ4FMmjSZbEsXjVCue415i1fFt97JYQdpwGuwjzhVUz1W meheret@meherett:~$ meheret@meherett:~$ h meheret@meherett:~$ hd meheret@meherett:~$ hdw meheret@meherett:~$ hdwa meheret@meherett:~$ hdwal meheret@meherett:~$ hdwall meheret@meherett:~$ hdwalle meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet meheret@meherett:~$ hdwallet g meheret@meherett:~$ hdwallet g meheret@meherett:~$ hdwallet g a meheret@meherett:~$ hdwallet g a meheret@meherett:~$ hdwallet g a - meheret@meherett:~$ hdwallet g a -s meheret@meherett:~$ hdwallet g a -s meheret@meherett:~$ hdwallet g a -s E meheret@meherett:~$ hdwallet g a -s ET meheret@meherett:~$ hdwallet g a -s ETH meheret@meherett:~$ hdwallet g a -s ETH meheret@meherett:~$ hdwallet g a -s ETH - meheret@meherett:~$ hdwallet g a -s ETH -p meheret@meherett:~$ hdwallet g a -s ETH -p meheret@meherett:~$ hdwallet g a -s ETH -p " meheret@meherett:~$ hdwallet g a -s ETH -p "m meheret@meherett:~$ hdwallet g a -s ETH -p "m/ meheret@meherett:~$ hdwallet g a -s ETH -p "m/5 meheret@meherett:~$ hdwallet g a -s ETH -p "m/55 meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/ meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/5 meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56 meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" - meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -x meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xp meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpu meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub " meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub "xpub661MyMwAqRbcFKRkaA7h4S16MSFa5YVfJkXFBRKowUa6Ufyhp4TGhS5nkvkLXSmdNjoszzDkU26WW2rg1zBsQBt6Pv3T8oLEAExGHD3hcQs meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub "xpub661MyMwAqRbcFKRkaA7h4S16MSFa5YVfJkXFBRKowUa6Ufyhp4TGhS5nkvkLXSmdNjoszzDkU26WW2rg1zBsQBt6Pv3T8oLEAExGHD3hcQs meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub "xpub661MyMwAqRbcFKRkaA7h4S16MSFa5YVfJkXFBRKowUa6Ufyhp4TGhS5nkvkLXSmdNjoszzDkU26WW2rg1zBsQBt6Pv3T8oLEAExGHD3hcQs" meheret@meherett:~$ hdwallet g a -s ETH -p "m/55/56" -xpub "xpub661MyMwAqRbcFKRkaA7h4S16MSFa5YVfJkXFBRKowUa6Ufyhp4TGhS5nkvkLXSmdNjoszzDkU26WW2rg1zBsQBt6Pv3T8oLEAExGHD3hcQs" m/55/56/0 0x9E4DA0ed75De4ddc756B5960a8a2fe2f6619aa0a 0399880907701ed93fba8f7fcd0b6577ddce5b4757a0eedfbdfe34eba58774f01a None m/55/56/1 0x3605971Ff24bd4773CEBF5C464eb690b787B8C0e 02eaa9ded5fada16a787b3cf8bae2eded293277f634bf0389620ebb18a900495da None m/55/56/2 0xDD2BB9C752DDcfF68a80fF083Ce048f7cE8C637a 026032e1928f11ee8c6062064337d7f55766c6c25e4300b76aefd798ff7bb7d2c6 None m/55/56/3 0xe8799F0047eBbe6Ab2920aDe268C86fFc0e69B31 03742fe4d0e8be5f5998372dc68f8834886a2843dfe6186a4f77766f3d5482c499 None m/55/56/4 0x8adedCA2053248B96D20C268f83B541d9f5A82E3 024a6b9a32646490a7cfb2b85b1545ed97f7b53fb51f82ac3e053efdda8e1f0b49 None m/55/56/5 0x7Ad84124c6F5dc269072fF26EfcC3ff0D6d5aECB 027897b67245e044d01befb584075c671b3e3cf083ec48d67b1783d22f423f2a7d None m/55/56/6 0x22D3bF5E8a7a77e7d5469A64eC027e5bEE0EeD54 0364b82fb06c0c3574609c1b205d545fd9900c6423fb0b196b18119acc1cb13d64 None m/55/56/7 0x92170DFed4EDb55936517934e1a084e3E60A62dF 03040106d21e43225f7db05d32453bc61ae00d5ad327f9205fe26a874e2c904b7c None m/55/56/8 0xF5D47DBEd337d1346f7319b12D1072fFa96d9618 024db83a48945d2bba14aedb09a798d5c101d18c9f8054f26f319ca8c691a5fa6e None m/55/56/9 0x48f84Ec0F56763E1d1e5a1E7DAee1583fD904A7A 02542fe476502ae02c71ca5dda3f9bdc3711b5d5c487ae6f536ba9df819455ed13 None m/55/56/10 0x68eC626dB1bb947f5B8bf2E4C8ad3c28badEB8f4 033724fcb3580d5cc1df5fbdbb2fc590b7121fd16418edff22b7b3dc02773340e7 None m/55/56/11 0x291FD8A3B5173cC47C3B4ddD6778C734b3E870b4 036d75d980680e517effa21408e4178a92af3a1282d5279d17309f60adf78acd8c None m/55/56/12 0xbF5879CDC6138626F1f404193d9daBdd4538db8F 035e0a8860cd6416581d59ca86045cc2416948c4bc49fc5aaaaf4bf417b6b38594 None m/55/56/13 0x85e7e03Ef655cF00e956B29becc780A468b88c8B 03d3f1c0448838f9438dbf327494dfa65949892c5bcf6c99e37ba8e8e77baf54f8 None m/55/56/14 0xfA8b48F2A6DABDa771619248e3AE8474Ab7C0113 034e402e9445d6a1d2d3ca514508b0c229ca7027e2aa69c7766a446bb9d852e94c None m/55/56/15 0x015E590C1bc21c4ABf8497648C5Fe3659F63afd5 0286982173a103479df45e836408312bda4b8f7636b403ad1f069efe3fd30ffc65 None m/55/56/16 0x46baD561Fc3b7DC871dB13A3f62a31085a0C473B 02f0b96c57b087b85e6b7c122a8bcacd7abede2c456e6fff2532d4b757232f8cac None m/55/56/17 0x26fcCa2256f05fFE4981b7B9A8b571629A316927 0399af0378ec902d12d879904597af32243f3421319c439797332d1e63aed54777 None m/55/56/18 0x071Dcafb31161D7B5F0e6FD3Df8C0ffB9d585754 02b3aa3c9dc13f534a66de18a8b2620208eebf9136d45c067cc86ec5488d29c979 None m/55/56/19 0x54dd69AB113230b84dDf250cafd37eb33571f545 02cbe4c82aa2261177fece874b8c1c2ef4f2f731158a24a02e0e81692b838c2e57 None meheret@meherett:~$ meheret@meherett:~$ e meheret@meherett:~$ ex meheret@meherett:~$ exi meheret@meherett:~$ exit meheret@meherett:~$ exit exit + \ No newline at end of file From a595db96199c329b42dde9b904b55f1cfa10160d Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:45:27 +0300 Subject: [PATCH 85/92] Change: current hdwallet cli `gif` into the new `svg` file. --- README.md | 8 +++++--- docs/cli.rst | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6100051..728c5d0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ 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 chain. - +
+ + For more info see the BIP specs. @@ -129,7 +131,7 @@ print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False)) ``` -Ethereum mainnet [Ganache-CLI](https://github.com/trufflesuite/ganache-cli) wallet look's like: +Ethereum mainnet [Ganache](https://github.com/trufflesuite/ganache) wallet look's like: ```python #!/usr/bin/env python3 @@ -381,4 +383,4 @@ If You found this tool helpful consider making a donation: ## License -Distributed under the [ISC](https://github.com/meherett/python-hdwallet/blob/master/LICENSE) license. See ``LICENSE`` for more information. +Distributed under the [MIT](https://github.com/meherett/python-hdwallet/blob/master/LICENSE) license. See ``LICENSE`` for more information. diff --git a/docs/cli.rst b/docs/cli.rst index 85f2c08..4e8c76e 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2,7 +2,7 @@ Command Line Interface (CLI) ============================ -.. image:: static/gif/hdwallet.gif +.. image:: static/svg/hdwallet-cli.svg :alt: HDWallet CLI .. click:: hdwallet.cli.__main__:main From 25af715a335e473a79a265f195c2462f10d86aac Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 10:46:34 +0300 Subject: [PATCH 86/92] Bump: Python-HDWallet from `v2.2.0` to `v2.2.1` package. --- hdwallet/__init__.py | 6 +++--- setup.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index 239dcb2..071a875 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -10,10 +10,10 @@ ) # HDWallet Information's -__version__: str = "v2.1.2" -__license__: str = "ISCL" +__version__: str = "v2.2.1" +__license__: str = "MIT" __author__: str = "Meheret Tesfaye Batu" -__email__: str = "meherett@zoho.com" +__email__: str = "meherett.batu@gmail.com" __description__: str = "Python-based library for the implementation of a hierarchical deterministic wallet " \ "generator for more than 140+ multiple cryptocurrencies." diff --git a/setup.py b/setup.py index 739e407..fd75c99 100644 --- a/setup.py +++ b/setup.py @@ -20,14 +20,14 @@ setup( name="hdwallet", - version="v2.2.0", + version="v2.2.1", description="Python-based library for the implementation of a hierarchical deterministic wallet " "generator for more than 140+ multiple cryptocurrencies.", long_description=long_description, long_description_content_type="text/markdown", - license="ISCL", + license="MIT", author="Meheret Tesfaye Batu", - author_email="meherett@zoho.com", + author_email="meherett.batu@gmail.com", url="https://github.com/meherett/python-hdwallet", project_urls=project_urls, keywords=[ From f078b346e1bb08c5b39420523ea8c4f8db03804b Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 11:10:47 +0300 Subject: [PATCH 87/92] Update: copyright year and fix `build.tools.python` version. --- .readthedocs.yaml | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 51d490c..405a54d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,7 +5,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: 3.11 + python: "3.11" commands: - pip install --upgrade pip diff --git a/docs/conf.py b/docs/conf.py index bed7e1f..ad6d527 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ # -- Project information ----------------------------------------------------- project = "HDWallet" -copyright = f"{datetime.datetime.now().year}, {__author__}" +copyright = f"2021-{datetime.datetime.now().year}, {__author__}" author = __author__ # The full version, including alpha/beta/rc tags From c757675a6ab8e0da605e6ce2be4615c9dc811b1a Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 11:12:26 +0300 Subject: [PATCH 88/92] Bump: all extras require packages and add `tox` on tests. --- setup.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index fd75c99..728237b 100644 --- a/setup.py +++ b/setup.py @@ -41,18 +41,19 @@ install_requires=requirements, extras_require={ "cli": [ - "click>=8.0.3,<9", + "click>=8.1.3,<9", "click-aliases>=1.0.1,<2", - "tabulate>=0.8.9,<1" + "tabulate>=0.9.0,<1" ], "tests": [ - "pytest>=6.2.5,<7", - "pytest-cov>=3.0.0,<4" + "pytest>=7.2.0,<8", + "pytest-cov>=4.0.0,<5", + "tox==3.28.0" ], "docs": [ - "sphinx>=4.4.0,<5", - "furo==2022.2.14.1", - "sphinx-click>=3.1.0,<4" + "sphinx>=5.3.0,<6", + "furo==2022.12.7", + "sphinx-click>=4.4.0,<5" ] }, classifiers=[ From a36809e8e634c638f5968e5a4311cdbf6d335bc7 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 11:21:34 +0300 Subject: [PATCH 89/92] Update: hdwallet cli `svg` redirect link. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 728c5d0..f950db9 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ 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 chain. - - - + For more info see the BIP specs. From 6c095e1cf6dea0a25e28983e49718ff9a2498887 Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 11:34:48 +0300 Subject: [PATCH 90/92] Drop: `build.commands` option & update README.md --- .readthedocs.yaml | 2 -- README.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 405a54d..de81385 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,8 +6,6 @@ build: os: ubuntu-22.04 tools: python: "3.11" - commands: - - pip install --upgrade pip python: install: diff --git a/README.md b/README.md index f950db9..974485e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ 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 chain. - +![HDWallet-CLI](https://raw.githubusercontent.com/meherett/python-hdwallet/master/docs/static/svg/hdwallet-cli.svg) For more info see the BIP specs. From 06ad14761cecd182b76ecf1362695d06b2d485de Mon Sep 17 00:00:00 2001 From: meherett Date: Thu, 22 Dec 2022 11:52:55 +0300 Subject: [PATCH 91/92] Fix: Ycash segwit & add `FLUX` and `YEC` on cryptocurrencies docs. --- README.md | 2 +- docs/cryptocurrencies.rst | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 974485e..dd9769a 100644 --- a/README.md +++ b/README.md @@ -365,7 +365,7 @@ This library simplifies the process of creating a new hierarchical deterministic | Wincoin | `WC` | Yes | No | No | 181 | `m/44'/181'/0'/0/0` | | 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` | -| [Ycash](https://github.com/ycashfoundation/ycash) | `YEC` | Yes | No | Yes | 347 | `m/44'/347'/0'/0/0` | +| [Ycash](https://github.com/ycashfoundation/ycash) | `YEC` | Yes | No | No | 347 | `m/44'/347'/0'/0/0` | | ZClassic | `ZCL` | Yes | No | No | 147 | `m/44'/147'/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` | diff --git a/docs/cryptocurrencies.rst b/docs/cryptocurrencies.rst index ad7105f..9ce6b2f 100644 --- a/docs/cryptocurrencies.rst +++ b/docs/cryptocurrencies.rst @@ -392,6 +392,13 @@ This library simplifies the process of generating a new HDWallet's for: - No - 120 - m/44'/120'/0'/0/0 + * - `Flux `_ + - FLUX + - Yes + - No + - No + - 19167 + - m/44'/19167'/0'/0/0 * - Fuji Coin - FJC - Yes @@ -980,6 +987,13 @@ This library simplifies the process of generating a new HDWallet's for: - Yes - 550 - m/44'/550'/0'/0/0 + * - `Ycash `_ + - YEC + - Yes + - No + - No + - 347 + - m/44'/347'/0'/0/0 * - ZClassic - ZCL - Yes From 1a1610e1376c247219c1a7b68210b5eb153c9573 Mon Sep 17 00:00:00 2001 From: toni-dev <62844491+Toni-d-e-v@users.noreply.github.com> Date: Sat, 4 Feb 2023 09:46:02 +0100 Subject: [PATCH 92/92] Update cryptocurrencies.py --- hdwallet/cryptocurrencies.py | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 1aeb3ad..ed4dbc6 100644 --- a/hdwallet/cryptocurrencies.py +++ b/hdwallet/cryptocurrencies.py @@ -1514,6 +1514,47 @@ class DashMainnet(Cryptocurrency): WIF_SECRET_KEY = 0xcc +class RuxCrypto(Cryptocurrency): + # RXC.pubKeyHash = 0x3c; RXC.wif = 0xBC; RXC.scriptHash = 0x3d; + + NAME = "RuxCrypto" + SYMBOL = "RXC" + NETWORK = "mainnet" + SOURCE_CODE = "https://git.toni.software" + COIN_TYPE = CoinType({ + "INDEX": 6969, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x3d + PUBLIC_KEY_ADDRESS = 0x3c + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": None, + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x0488ade4, + "P2SH": 0x0488ade4, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x0488b21e, + "P2SH": 0x0488b21e, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + + MESSAGE_PREFIX = "\x18RXC Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0xBC + + class DashTestnet(Cryptocurrency): NAME = "Dash"