diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 0ddd342..b247009 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 @@ -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 diff --git a/.travis.yml b/.travis.yml index 32023d4..9c4e24b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,9 @@ language: python -dist: xenial -sudo: true +dist: bionic +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 .[tests,docs] tox-travis coveralls + - pip install -e .[cli,tests,docs] tox-travis coveralls matrix: include: - name: "3.6" @@ -16,5 +14,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/README.md b/README.md index 406156c..b430564 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ - # 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 the chain. +It allows the handling of multiple coins, multiple accounts, external and internal chains per account and millions of addresses per chain. + + For more info see the BIP specs. @@ -19,17 +21,23 @@ For more info see the BIP specs. | [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) | Hierarchical Deterministic Wallets | | [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) | Multi-Account Hierarchy for Deterministic Wallets | | [BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) | Derivation scheme for P2WPKH-nested-in-P2SH based accounts | -| [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki) | Derivation scheme for P2WPKH based accounts | -| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki) | Segregated Witness (Consensus layer) | +| [BIP84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) | Derivation scheme for P2WPKH based accounts | +| [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) | Segregated Witness (Consensus layer) | ## Installation -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: ``` @@ -40,14 +48,14 @@ 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 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 @@ -62,7 +70,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 @@ -168,7 +176,7 @@ for address_index in range(10): ```shell script Mnemonic: bright demand olive glance crater key head glory quantum leisure intact age -Base HD Path: m/44'/60'/0'/0/{address_index} +Base HD Path: m/44'/60'/0'/0/{address_index} (0) m/44'/60'/0'/0/0 0x3a149f0c5dc5c0F1E29e573215C23710dE9c4f87 0xa45f9af43912fdd5e88c492226be082029f257681d4b3e73b68be535d2fb0526 (1) m/44'/60'/0'/0/1 0x9e8A4fD9bA74DbB0c7F465EF56b47489793AA102 0x6e5ab2a3ae20c7b3a1c0645b03689e88e8cdff16f6a39d6a420bfebc20e8a941 @@ -185,18 +193,37 @@ Base HD Path: m/44'/60'/0'/0/{address_index} [Click this to see more examples :)](https://github.com/meherett/python-hdwallet/blob/master/examples) +## Development + +To get started, just fork this repo, clone it locally, and run: + +``` +pip install -e .[cli,tests,docs] -r requirements.txt +``` + +## Testing + +You can run the tests with: + +``` +pytest +``` + +Or use `tox` to run the complete suite against the full set of build targets, or pytest to run specific +tests against a specific version of Python. + ## Contributing -Feel free to open an [issue](https://github.com/meherett/hdwallet/issues) if you find a problem, -or a pull request if you've solved an issue. And also any help in testing, development, -documentation and other tasks is highly appreciated and useful to the project. +Feel free to open an [issue](https://github.com/meherett/hdwallet/issues) if you find a problem, +or a pull request if you've solved an issue. And also any help in testing, development, +documentation and other tasks is highly appreciated and useful to the project. There are tasks for contributors of all experience levels. For more information, see the [CONTRIBUTING.md](https://github.com/meherett/hdwallet/blob/master/CONTRIBUTING.md) file. ## Available Cryptocurrencies -This library simplifies the process of creating a new HDWallet's for: +This library simplifies the process of creating a new hierarchical deterministic wallets for: | Cryptocurrencies | Symbols | Mainnet | Testnet | Segwit | Coin Type | Default Paths | | :---------------------------------------------------------------- | :------------------: | :-----: | :-----: | :----: | :-------: | :------------------: | @@ -323,6 +350,7 @@ This library simplifies the process of creating a new HDWallet's for: | 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` | @@ -337,24 +365,17 @@ This library simplifies the process of creating a new HDWallet's for: | XUEZ | `XUEZ` | Yes | No | No | 225 | `m/44'/225'/0'/0/0` | | [XinFin](https://github.com/XinFinOrg/XDPoSChain) | `XDC` | Yes | No | Yes | 550 | `m/44'/550'/0'/0/0` | | ZClassic | `ZCL` | Yes | No | No | 147 | `m/44'/147'/0'/0/0` | -| Zcash | `ZEC` | Yes | No | No | 133 | `m/44'/133'/0'/0/0` | +| [Zcash](https://github.com/zcash/zcash) | `ZEC`, `ZECTEST` | Yes | Yes | No | 133 | `m/44'/133'/0'/0/0` | | Zencash | `ZEN` | Yes | No | No | 121 | `m/44'/121'/0'/0/0` | ## Donations -If You found this tool helpful consider making a donation: +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 | ## License diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 0000000..85f2c08 --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,10 @@ +============================ +Command Line Interface (CLI) +============================ + +.. image:: static/gif/hdwallet.gif + :alt: HDWallet CLI + +.. 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 187c00e..bed7e1f 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.0" +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. @@ -52,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": "", @@ -71,12 +75,26 @@ # "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, # 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..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 @@ -878,6 +882,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 @@ -977,9 +988,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 @@ -990,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 b59b75c..0a06737 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,14 +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. +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 @@ -26,5 +131,5 @@ For more info see the BIP specs. - Derivation scheme for P2WPKH-nested-in-P2SH based accounts * - `BIP84 `_ - Derivation scheme for P2WPKH based accounts - * - `BIP141 `_ + * - `BIP141 `_ - Segregated Witness (Consensus layer) diff --git a/docs/install.rst b/docs/install.rst index 0a54b6d..e23b637 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -2,13 +2,36 @@ Installing HDWallet =================== -The easiest way to install HDWallet is via pip: +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] + + +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. + + If you want to run the latest version of the code, you can install from git: :: @@ -25,4 +48,4 @@ We welcome pull requests. To get started, just fork this `github repository install.rst - Cryptocurrencies + cli.rst + Available Cryptocurrencies .. toctree:: :maxdepth: 3 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 05f5480..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) @@ -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..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) @@ -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_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_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 2c27e7b..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) @@ -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 5eb1913..2af64e1 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 as SYMBOL 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 = "yprvAMZNWbcSVmxMiVoKgQuKmemTpEz8dJs3v8hmgkRVUjncqkXsgoxyqZ8rDb" \ +# "eXzMqRQZEsTcB4T5iQQx7WazLyy3KiHZrdcHo6DmGAibeMxQV" -# 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=SYMBOL), "Invalid Root XPrivate Key." -# Print all Ethereum HDWallet information's +# Initialize Bitcoin mainnet HDWallet +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) +# 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..04e9079 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 as SYMBOL 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 = "zpub6uxKjJ8pnanQKU2betFrDPVmcVUvVgyAhgWS74iaN7yUE8RADoRRnztyVEQtnzi9Fh1Vp" \ +# "6iJ8RT6mMqjGnS6AxGjud3P2DLzpMHUw2zT1n2" -# 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=SYMBOL), "Invalid Root XPublic Key." -# Print all Ethereum HDWallet information's +# Initialize Bitcoin mainnet HDWallet +hdwallet: HDWallet = HDWallet(symbol=SYMBOL) +# 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()) diff --git a/hdwallet/__init__.py b/hdwallet/__init__.py index ce4c736..dd35aca 100644 --- a/hdwallet/__init__.py +++ b/hdwallet/__init__.py @@ -1,9 +1,32 @@ #!/usr/bin/env python3 from .hdwallet import ( - HDWallet, BIP32HDWallet, BIP44HDWallet, BIP49HDWallet, BIP84HDWallet, BIP141HDWallet + HDWallet, + BIP32HDWallet, + BIP44HDWallet, + BIP49HDWallet, + BIP84HDWallet, + BIP141HDWallet ) -__all__ = [ - "HDWallet", "BIP32HDWallet", "BIP44HDWallet", "BIP49HDWallet", "BIP84HDWallet", "BIP141HDWallet" +# HDWallet Information's +__version__: str = "v2.1.1" +__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" ] 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..71b1d60 --- /dev/null +++ b/hdwallet/cli/__main__.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python + +from click_aliases import ClickAliasedGroup +from typing import Optional + +from hdwallet import __version__ +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"], +) + + +def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + click.echo(__version__) + ctx.exit() + + +@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.") +def main(): + pass + + +@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, 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("-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 new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/hdwallet/cli/generate/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/hdwallet/cli/generate/addresses.py b/hdwallet/cli/generate/addresses.py new file mode 100644 index 0000000..60881f3 --- /dev/null +++ b/hdwallet/cli/generate/addresses.py @@ -0,0 +1,105 @@ +#!/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_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 +): + 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, + False if xpublic_key else 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 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() + + 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..c83db28 --- /dev/null +++ b/hdwallet/cli/generate/hdwallet.py @@ -0,0 +1,110 @@ +#!/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 wif or private_key or public_key: + pass + else: + 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)) + + except Exception as exception: + click.echo(click.style(f"Error: {str(exception)}"), err=True) + sys.exit() 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" + )) diff --git a/hdwallet/cryptocurrencies.py b/hdwallet/cryptocurrencies.py index 1099f4b..9ba2f2f 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): @@ -5594,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" @@ -6239,7 +6281,7 @@ class ZcashMainnet(Cryptocurrency): NAME = "Zcash" SYMBOL = "ZEC" NETWORK = "mainnet" - SOURCE_CODE = None + SOURCE_CODE = "https://github.com/zcash/zcash" COIN_TYPE = CoinType({ "INDEX": 133, "HARDENED": True @@ -6274,6 +6316,46 @@ class ZcashMainnet(Cryptocurrency): WIF_SECRET_KEY = 0x80 +class ZcashTestnet(Cryptocurrency): + + NAME = "Zcash" + SYMBOL = "ZECTEST" + NETWORK = "testnet" + SOURCE_CODE = "https://github.com/zcash/zcash" + COIN_TYPE = CoinType({ + "INDEX": 1, + "HARDENED": True + }) + + SCRIPT_ADDRESS = 0x1cba + PUBLIC_KEY_ADDRESS = 0x1d25 + SEGWIT_ADDRESS = SegwitAddress({ + "HRP": None, + "VERSION": 0x00 + }) + + EXTENDED_PRIVATE_KEY = ExtendedPrivateKey({ + "P2PKH": 0x4358394, + "P2SH": 0x4358394, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + EXTENDED_PUBLIC_KEY = ExtendedPublicKey({ + "P2PKH": 0x43587cf, + "P2SH": 0x43587cf, + "P2WPKH": None, + "P2WPKH_IN_P2SH": None, + "P2WSH": None, + "P2WSH_IN_P2SH": None + }) + + MASSAGE_PREFIX = "\x18Zcash Signed Message:\n" + DEFAULT_PATH = f"m/44'/{str(COIN_TYPE)}/0'/0/0" + WIF_SECRET_KEY = 0xef + + class ZencashMainnet(Cryptocurrency): NAME = "Zencash" diff --git a/hdwallet/derivations.py b/hdwallet/derivations.py index 9a49013..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" @@ -510,7 +512,7 @@ class BIP84Derivation(BIP32Derivation): >>> BIP84Derivation(cryptocurrency=BitcoinMainnet) >>> str(BIP84Derivation(cryptocurrency=BitcoinMainnet)) - "m/49'/0'/0'/0/0" + "m/84'/0'/0'/0/0" """ PURPOSE: int = 84 @@ -547,7 +549,7 @@ class BIP141Derivation(Derivation): >>> BIP141Derivation(cryptocurrency=BitcoinMainnet) >>> str(BIP141Derivation(cryptocurrency=BitcoinMainnet)) - "m/49'/0'/0'/0/0" + "m/44'/0'/0'/0/0" """ def __init__(self, cryptocurrency: Any, path: Union[str, Derivation] = None, semantic: str = "p2wpkh"): diff --git a/hdwallet/hdwallet.py b/hdwallet/hdwallet.py index 5e226b7..97e7d04 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 ( @@ -39,15 +55,14 @@ Cryptocurrency, get_cryptocurrency, SegwitAddress ) from .derivations import ( - Derivation, BIP32Derivation, BIP44Derivation, - BIP49Derivation, BIP84Derivation, BIP141Derivation + Derivation, BIP32Derivation, BIP44Derivation, BIP49Derivation, BIP84Derivation, BIP141Derivation ) from .exceptions import ( SemanticError, DerivationError ) 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 @@ -79,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): @@ -115,6 +130,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. @@ -140,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 @@ -148,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": @@ -179,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": @@ -213,15 +236,17 @@ 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_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDWallet": + def from_xprivate_key(self, xprivate_key: str, strict: 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 :returns: HDWallet -- Hierarchical Deterministic Wallet instance. @@ -229,18 +254,25 @@ def from_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDW >>> 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) + 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:] @@ -251,15 +283,20 @@ def from_root_xprivate_key(self, xprivate_key: str, strict: bool = True) -> "HDW if self._from_class: self.from_path(path=self._path_class) self._public_key = self.compressed() + self._semantic = get_semantic( + _cryptocurrency=self._cryptocurrency, + version=_deserialize_xprivate_key[0], + key_type="private_key" + ) 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) -> "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 :returns: HDWallet -- Hierarchical Deterministic Wallet instance. @@ -267,7 +304,7 @@ def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWal >>> 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 +313,16 @@ 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) + 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 @@ -287,65 +333,13 @@ def from_root_xpublic_key(self, xpublic_key: str, strict: bool = True) -> "HDWal 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() - 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._semantic = get_semantic( + _cryptocurrency=self._cryptocurrency, + version=_deserialize_xpublic_key[0], + key_type="public_key" ) - 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": @@ -430,7 +424,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(xprivate_key="xprv9s21ZrQH143K3xPGUzpogJeKtRdjHkK6muBJo8v7rEVRzT83xJgNcLpMoJXUf9wJFKfuHR4SGvfgdShh4t9VmjjrE9usBunK3LfNna31LGF") >>> hdwallet.from_path(path="m/44'/0'/'0/0/0") """ @@ -463,7 +457,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(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 +479,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:] @@ -579,17 +573,21 @@ 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:] - 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 @@ -613,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] @@ -627,9 +629,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 ) @@ -652,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) @@ -686,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) @@ -709,7 +719,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" @@ -719,13 +729,21 @@ def clean_derivation(self) -> "HDWallet": None """ - if self._i: - self._path, self._depth, self._parent_fingerprint, self._index = ( - "m", 0, b"\0\0\0\0", 0 + if self._root_private_key: + 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._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._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( + self._root_public_key[0], curve=SECP256k1 + ) return self def uncompressed(self, compressed: Optional[str] = None) -> str: @@ -1083,19 +1101,23 @@ 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.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() + 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)) @@ -1115,9 +1137,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)) @@ -1137,7 +1159,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)) 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/hdwallet/symbols.py b/hdwallet/symbols.py index e90c462..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 @@ -275,7 +277,7 @@ # ZClassic ZCL = "ZCL" # Zcash -ZEC = "ZEC" +ZEC, ZECTEST = "ZEC", "ZECTEST" # Zencash ZEN = "ZEN" @@ -404,6 +406,7 @@ "SYS", "TOA", "THT", + "TRX", "TWINS", "TWINSTEST", "USC", "UNO", @@ -418,6 +421,6 @@ "XUEZ", "XDC", "ZCL", - "ZEC", + "ZEC", "ZECTEST", "ZEN" ] diff --git a/hdwallet/utils.py b/hdwallet/utils.py index 16edefa..969c55f 100644 --- a/hdwallet/utils.py +++ b/hdwallet/utils.py @@ -7,11 +7,14 @@ import string import os +import inspect import unicodedata import binascii -from .exceptions import SemanticError -from .cryptocurrencies import get_cryptocurrency +from hdwallet import cryptocurrencies +from .cryptocurrencies import ( + get_cryptocurrency, Cryptocurrency +) from .libs.base58 import check_decode # Alphabet and digits. @@ -25,6 +28,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 @@ -119,7 +136,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: @@ -298,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) diff --git a/setup.py b/setup.py index 6219079..9b70d38 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,27 +20,39 @@ setup( name="hdwallet", - version="1.3.0", - description="Python-based library for the implementation of a " - "hierarchical deterministic wallet generator for more than 140+ multiple cryptocurrencies.", + 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, long_description_content_type="text/markdown", license="ISCL", - author="Meheret Tesfaye", + author="Meheret Tesfaye Batu", author_email="meherett@zoho.com", url="https://github.com/meherett/python-hdwallet", - keywords=["cryptography", "hd", "bip32", "bip44", "bip39", "wallet", "cryptocurrencies"], + project_urls=project_urls, + 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={ + "cli": [ + "click>=8.0.3,<9", + "click-aliases>=1.0.1,<2", + "tabulate>=0.8.9,<1" + ], "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.4.0,<5", + "furo==2022.2.14.1", + "sphinx-click>=3.1.0,<4" ] }, classifiers=[ @@ -44,6 +62,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" ] ) 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/__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..52bb3b1 --- /dev/null +++ b/tests/cli/generate/test_addresses.py @@ -0,0 +1,86 @@ +#!/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 + + 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 new file mode 100644 index 0000000..33ab587 --- /dev/null +++ b/tests/cli/generate/test_hdwallet.py @@ -0,0 +1,136 @@ +#!/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" + + 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/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__ 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 95% rename from tests/test_from_private_key.py rename to tests/hdwallet/test_from_private_key.py index fe3295d..4251a0e 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() @@ -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/test_from_public_key.py b/tests/hdwallet/test_from_public_key.py similarity index 96% rename from tests/test_from_public_key.py rename to tests/hdwallet/test_from_public_key.py index 3c75696..214857f 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() @@ -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/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 95% rename from tests/test_from_wif.py rename to tests/hdwallet/test_from_wif.py index e4d666c..8a4d3a0 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() @@ -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"] diff --git a/tests/test_from_root_xprivate_key.py b/tests/hdwallet/test_from_xprivate_key.py similarity index 50% rename from tests/test_from_root_xprivate_key.py rename to tests/hdwallet/test_from_xprivate_key.py index 043a051..17a4b68 100644 --- a/tests/test_from_root_xprivate_key.py +++ b/tests/hdwallet/test_from_xprivate_key.py @@ -7,18 +7,18 @@ # 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() -def test_from_root_xprivate_key(): +def test_from_xprivate_key(): hdwallet: HDWallet = HDWallet( symbol=_["bitcoin"]["mainnet"]["symbol"] ) - hdwallet.from_root_xprivate_key( + hdwallet.from_xprivate_key( xprivate_key=_["bitcoin"]["mainnet"]["root_xprivate_key"], strict=True ) hdwallet.from_path( @@ -75,3 +75,65 @@ def test_from_root_xprivate_key(): 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"], strict=False + ) + + assert hdwallet.cryptocurrency() == _["bitcoin"]["testnet"]["cryptocurrency"] + assert hdwallet.symbol() == _["bitcoin"]["testnet"]["symbol"] + assert hdwallet.network() == _["bitcoin"]["testnet"]["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"]["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"] + assert hdwallet.xpublic_key() == _["bitcoin"]["testnet"]["xpublic_key"] + assert hdwallet.uncompressed() == _["bitcoin"]["testnet"]["uncompressed"] + assert hdwallet.compressed() == _["bitcoin"]["testnet"]["compressed"] + assert hdwallet.chain_code() == _["bitcoin"]["testnet"]["chain_code"] + assert hdwallet.private_key() == _["bitcoin"]["testnet"]["private_key"] + assert hdwallet.public_key() == _["bitcoin"]["testnet"]["public_key"] + assert hdwallet.wif() == _["bitcoin"]["testnet"]["wif"] + assert hdwallet.finger_print() == _["bitcoin"]["testnet"]["finger_print"] + assert hdwallet.semantic() == _["bitcoin"]["testnet"]["semantic"] + assert hdwallet.path() == None + assert hdwallet.hash() == _["bitcoin"]["testnet"]["hash"] + assert hdwallet.p2pkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2pkh"] + assert hdwallet.p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2sh"] + assert hdwallet.p2wpkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh"] + assert hdwallet.p2wpkh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh_in_p2sh"] + assert hdwallet.p2wsh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh"] + assert hdwallet.p2wsh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh_in_p2sh"] + + assert isinstance(hdwallet.dumps(), dict) + + dumps: dict = _["bitcoin"]["testnet"] + + dumps["strength"] = None + dumps["entropy"] = None + dumps["mnemonic"] = None + dumps["language"] = None + dumps["passphrase"] = None + dumps["seed"] = 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"] + del dumps["xpublic_key_hex"] + + assert hdwallet.dumps() == dumps diff --git a/tests/hdwallet/test_from_xpublic_key.py b/tests/hdwallet/test_from_xpublic_key.py new file mode 100644 index 0000000..940ef06 --- /dev/null +++ b/tests/hdwallet/test_from_xpublic_key.py @@ -0,0 +1,45 @@ +#!/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_xpublic_key(): + + hdwallet: HDWallet = HDWallet( + symbol=_["bitcoin"]["mainnet"]["symbol"] + ) + # account_extended_xpublic_key path m/44'/0'/0' + hdwallet.from_xpublic_key( + xpublic_key=_["bitcoin"]["mainnet"]["account_extended_xpublic_key"] + ) + # the xpublic_key path m/44'/0'/0'/0/0 + hdwallet.from_path( + path="m/0/0" + ) + + assert hdwallet.cryptocurrency() == _["bitcoin"]["mainnet"]["cryptocurrency"] + assert hdwallet.symbol() == _["bitcoin"]["mainnet"]["symbol"] + assert hdwallet.network() == _["bitcoin"]["mainnet"]["network"] + # root_xpublic_key see hdwallet.from_xpublic_key(first_from_xpublic_key) + assert hdwallet.root_xpublic_key() == _["bitcoin"]["mainnet"]["account_extended_xpublic_key"] + + assert hdwallet.public_key() == _["bitcoin"]["mainnet"]["public_key"] + 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) + 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" diff --git a/tests/test_derivations.py b/tests/test_derivations.py new file mode 100644 index 0000000..07bd1be --- /dev/null +++ b/tests/test_derivations.py @@ -0,0 +1,169 @@ +#!/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" + + 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( + 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" + + 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( + 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" + + 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( + 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" + + 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" + ) + 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_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 deleted file mode 100644 index 939c717..0000000 --- a/tests/test_from_xprivate_key.py +++ /dev/null @@ -1,78 +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_xprivate_key(): - - hdwallet: HDWallet = HDWallet( - symbol=_["bitcoin"]["testnet"]["symbol"] - ) - - hdwallet.from_xprivate_key( - xprivate_key=_["bitcoin"]["testnet"]["xprivate_key"] - ) - - assert hdwallet.cryptocurrency() == _["bitcoin"]["testnet"]["cryptocurrency"] - assert hdwallet.symbol() == _["bitcoin"]["testnet"]["symbol"] - assert hdwallet.network() == _["bitcoin"]["testnet"]["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) is None - assert hdwallet.root_xpublic_key() is None - 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"] - assert hdwallet.xpublic_key() == _["bitcoin"]["testnet"]["xpublic_key"] - assert hdwallet.uncompressed() == _["bitcoin"]["testnet"]["uncompressed"] - assert hdwallet.compressed() == _["bitcoin"]["testnet"]["compressed"] - assert hdwallet.chain_code() == _["bitcoin"]["testnet"]["chain_code"] - assert hdwallet.private_key() == _["bitcoin"]["testnet"]["private_key"] - assert hdwallet.public_key() == _["bitcoin"]["testnet"]["public_key"] - assert hdwallet.wif() == _["bitcoin"]["testnet"]["wif"] - assert hdwallet.finger_print() == _["bitcoin"]["testnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["testnet"]["semantic"] - assert hdwallet.path() == None - assert hdwallet.hash() == _["bitcoin"]["testnet"]["hash"] - assert hdwallet.p2pkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2pkh"] - assert hdwallet.p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2sh"] - assert hdwallet.p2wpkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh"] - assert hdwallet.p2wpkh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh_in_p2sh"] - assert hdwallet.p2wsh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh"] - assert hdwallet.p2wsh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh_in_p2sh"] - - assert isinstance(hdwallet.dumps(), dict) - - dumps: dict = _["bitcoin"]["testnet"] - - dumps["strength"] = None - dumps["entropy"] = None - dumps["mnemonic"] = None - dumps["language"] = None - dumps["passphrase"] = None - dumps["seed"] = None - dumps["root_xprivate_key"] = None - dumps["root_xpublic_key"] = None - dumps["path"] = 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_xpublic_key.py b/tests/test_from_xpublic_key.py deleted file mode 100644 index d8b9055..0000000 --- a/tests/test_from_xpublic_key.py +++ /dev/null @@ -1,83 +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_xpublic_key(): - - hdwallet: HDWallet = HDWallet( - symbol=_["bitcoin"]["testnet"]["symbol"] - ) - - hdwallet.from_xpublic_key( - xpublic_key=_["bitcoin"]["testnet"]["xpublic_key"] - ) - - assert hdwallet.cryptocurrency() == _["bitcoin"]["testnet"]["cryptocurrency"] - assert hdwallet.symbol() == _["bitcoin"]["testnet"]["symbol"] - assert hdwallet.network() == _["bitcoin"]["testnet"]["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) is None - assert hdwallet.root_xpublic_key() is None - 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"] - assert hdwallet.xpublic_key() == _["bitcoin"]["testnet"]["xpublic_key"] - assert hdwallet.uncompressed() == _["bitcoin"]["testnet"]["uncompressed"] - assert hdwallet.uncompressed(compressed=_["bitcoin"]["testnet"]["compressed"]) == _["bitcoin"]["testnet"]["uncompressed"] - assert hdwallet.compressed() == _["bitcoin"]["testnet"]["compressed"] - assert hdwallet.compressed(uncompressed=_["bitcoin"]["testnet"]["uncompressed"]) == _["bitcoin"]["testnet"]["compressed"] - assert hdwallet.chain_code() == _["bitcoin"]["testnet"]["chain_code"] - assert hdwallet.private_key() is None - assert hdwallet.public_key() == _["bitcoin"]["testnet"]["public_key"] - assert hdwallet.wif() is None - assert hdwallet.finger_print() == _["bitcoin"]["testnet"]["finger_print"] - assert hdwallet.semantic() == _["bitcoin"]["testnet"]["semantic"] - assert hdwallet.path() == None - assert hdwallet.hash() == _["bitcoin"]["testnet"]["hash"] - assert hdwallet.p2pkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2pkh"] - assert hdwallet.p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2sh"] - assert hdwallet.p2wpkh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh"] - assert hdwallet.p2wpkh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wpkh_in_p2sh"] - assert hdwallet.p2wsh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh"] - assert hdwallet.p2wsh_in_p2sh_address() == _["bitcoin"]["testnet"]["addresses"]["p2wsh_in_p2sh"] - - assert isinstance(hdwallet.dumps(), dict) - - dumps: dict = _["bitcoin"]["testnet"] - - dumps["strength"] = None - dumps["entropy"] = None - dumps["mnemonic"] = None - dumps["language"] = None - 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 - 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_symbols.py b/tests/test_symbols.py new file mode 100644 index 0000000..4d9236d --- /dev/null +++ b/tests/test_symbols.py @@ -0,0 +1,165 @@ +# !/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 TRX == "TRX" + 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" 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..e77fd82 100644 --- a/tests/values.json +++ b/tests/values.json @@ -93,6 +93,8 @@ "root_xprivate_key_hex": "0488ade4000000000000000000ad41ef910cdcae932cb4060777b4284ee38f5b29c5fb60fda8416f298a14702c007a286fa06bdb8cc821b1d34d88547b36bf7ab434c632db7ee21fc77b4130a852", "root_xpublic_key": "xpub661MyMwAqRbcGGUtsoFw2d6ARvD2ABd7z327zxt2XiBBwMx9GAuNrrE7tbRuWF5MjjZ1BzDsRdaSHc9nVKAgHzQrv6pwYW3Hd7LSzbh8sWS", "root_xpublic_key_hex": "0488b21e000000000000000000ad41ef910cdcae932cb4060777b4284ee38f5b29c5fb60fda8416f298a14702c02949b9f64223e124eb9a8383fba0b21b5845fcfbdc84dec7692d21c716410eab0", + "account_extended_xprivate_key":"xprv9yo5N7Q158CUL131kN4rVZsa2n1cKWMkt7UgqVGvrY91GEhyuZCbRFboXhDfYUaDFVwXwMzDCTj3LAprcY3HLZLRF8Q59p4LhR62QaR46Mn", + "account_extended_xpublic_key":"xpub6CnRmcvtuVkmYV7UrPbrrhpJaor6iy5cFLQHdsgYQsfz9338T6Wqy3vHP1MXCEPMKzq1C8fErhrGnzEYarzsF814HnCzNF6vqo9JiGjjvE2", "xprivate_key": "xprvA3nwFVTnTFsvwcFaFJd18aFoAguPwgn3N5eWRx785nSHshmxogPYyCzuHc4mu7rZvPArT4WjjakcwfzmH3oJBH71DmaeEGo4bbNgbq3FsRV", "xprivate_key_hex": "0488ade405b9e49a9c00000000732993021308a753144a66f61475a5e52ce5c8a42b6c03fe43d5b30956c3722000730305af76b25e3b5fbc1169e6cb77d34a3fd7851368c11074381a359e287d3a", "xpublic_key": "xpub6GnHezzgHdSEA6L3MLA1ViCXiijtM9VtjJa7ELWje7yGkW77MDhoX1KP8sbcbXeaXXtri6kfDWPLiPuntxaw5cjaPbpiv2rm2wdq1uUwERQ", @@ -152,6 +154,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 diff --git a/tox.ini b/tox.ini index 3c126b6..1307a76 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,23 +7,39 @@ python = 3.7: python37 3.8: python38 3.9: python39 + 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 --upgrade pip {opts} {packages} + pip install -e . {opts} {packages} +extras = cli,tests,docs commands = python -m pytest