├── .dockerignore ├── .gitignore ├── Docker └── Dockerfile ├── LICENSE.md ├── README.md ├── entry_points.ini ├── poetry.lock ├── pyproject.toml ├── renovate.json ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── test_endpoint.py └── test_walletdat.py └── walletlib ├── __init__.py ├── base32.py ├── bitcoinj_compat.py ├── coinomi_proto.py ├── crypto.py ├── exceptions.py ├── protobufwallet.py ├── scripts ├── __init__.py └── dumpwallet.py ├── utils.py ├── wallet_segwit.py └── walletdat.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### macOS template 3 | # General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### Python template 31 | # Byte-compiled / optimized / DLL files 32 | __pycache__/ 33 | *.py[cod] 34 | *$py.class 35 | 36 | # C extensions 37 | *.so 38 | 39 | # Distribution / packaging 40 | .Python 41 | build/ 42 | develop-eggs/ 43 | dist/ 44 | downloads/ 45 | eggs/ 46 | .eggs/ 47 | lib/ 48 | lib64/ 49 | parts/ 50 | sdist/ 51 | var/ 52 | wheels/ 53 | pip-wheel-metadata/ 54 | share/python-wheels/ 55 | *.egg-info/ 56 | .installed.cfg 57 | *.egg 58 | MANIFEST 59 | 60 | # PyInstaller 61 | # Usually these files are written by a python script from a template 62 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 63 | *.manifest 64 | *.spec 65 | 66 | # Installer logs 67 | pip-log.txt 68 | pip-delete-this-directory.txt 69 | 70 | # Unit test / coverage reports 71 | htmlcov/ 72 | .tox/ 73 | .nox/ 74 | .coverage 75 | .coverage.* 76 | .cache 77 | nosetests.xml 78 | coverage.xml 79 | *.cover 80 | *.py,cover 81 | .hypothesis/ 82 | .pytest_cache/ 83 | 84 | # Translations 85 | *.mo 86 | *.pot 87 | 88 | # Django stuff: 89 | *.log 90 | local_settings.py 91 | db.sqlite3 92 | db.sqlite3-journal 93 | .user/ 94 | # Flask stuff: 95 | instance/ 96 | .webassets-cache 97 | 98 | # Scrapy stuff: 99 | .scrapy 100 | 101 | # Sphinx documentation 102 | docs/_build/ 103 | 104 | # PyBuilder 105 | target/ 106 | 107 | # Jupyter Notebook 108 | .ipynb_checkpoints 109 | 110 | # IPython 111 | profile_default/ 112 | ipython_config.py 113 | 114 | # pyenv 115 | .python-version 116 | 117 | # pipenv 118 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 119 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 120 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 121 | # install all needed dependencies. 122 | #Pipfile.lock 123 | 124 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 125 | __pypackages__/ 126 | 127 | # Celery stuff 128 | celerybeat-schedule 129 | celerybeat.pid 130 | 131 | # SageMath parsed files 132 | *.sage.py 133 | 134 | # Environments 135 | .env 136 | .venv 137 | env/ 138 | venv/ 139 | ENV/ 140 | env.bak/ 141 | venv.bak/ 142 | 143 | # Spyder project settings 144 | .spyderproject 145 | .spyproject 146 | 147 | # Rope project settings 148 | .ropeproject 149 | 150 | # mkdocs documentation 151 | /site 152 | 153 | # mypy 154 | .mypy_cache/ 155 | .dmypy.json 156 | dmypy.json 157 | 158 | # Pyre type checker 159 | .pyre/ 160 | 161 | ### Example user template template 162 | ### Example user template 163 | 164 | # IntelliJ project files 165 | .idea 166 | *.iml 167 | out 168 | gen 169 | ### JetBrains template 170 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 171 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 172 | 173 | # User-specific stuff 174 | .idea/**/workspace.xml 175 | .idea/**/tasks.xml 176 | .idea/**/usage.statistics.xml 177 | .idea/**/dictionaries 178 | .idea/**/shelf 179 | 180 | # Generated files 181 | .idea/**/contentModel.xml 182 | 183 | # Sensitive or high-churn files 184 | .idea/**/dataSources/ 185 | .idea/**/dataSources.ids 186 | .idea/**/dataSources.local.xml 187 | .idea/**/sqlDataSources.xml 188 | .idea/**/dynamic.xml 189 | .idea/**/uiDesigner.xml 190 | .idea/**/dbnavigator.xml 191 | 192 | # Gradle 193 | .idea/**/gradle.xml 194 | .idea/**/libraries 195 | 196 | # Gradle and Maven with auto-import 197 | # When using Gradle or Maven with auto-import, you should exclude module files, 198 | # since they will be recreated, and may cause churn. Uncomment if using 199 | # auto-import. 200 | # .idea/artifacts 201 | # .idea/compiler.xml 202 | # .idea/modules.xml 203 | # .idea/*.iml 204 | # .idea/modules 205 | # *.iml 206 | # *.ipr 207 | 208 | # CMake 209 | cmake-build-*/ 210 | 211 | # Mongo Explorer plugin 212 | .idea/**/mongoSettings.xml 213 | 214 | # File-based project format 215 | *.iws 216 | 217 | # IntelliJ 218 | out/ 219 | 220 | # mpeltonen/sbt-idea plugin 221 | .idea_modules/ 222 | 223 | # JIRA plugin 224 | atlassian-ide-plugin.xml 225 | 226 | # Cursive Clojure plugin 227 | .idea/replstate.xml 228 | 229 | # Crashlytics plugin (for Android Studio and IntelliJ) 230 | com_crashlytics_export_strings.xml 231 | crashlytics.properties 232 | crashlytics-build.properties 233 | fabric.properties 234 | 235 | # Editor-based Rest Client 236 | .idea/httpRequests 237 | 238 | # Android studio 3.1+ serialized cache file 239 | .idea/caches/build_file_checksums.ser 240 | 241 | /.idea/* 242 | 243 | tests/kd.txt 244 | 245 | *.txt 246 | 247 | tests/test_endpoint.py 248 | 249 | dumpwallet.py 250 | 251 | tests/test_wallets/ 252 | 253 | batch_process.py 254 | **/__pycache__/ 255 | /tests/__pycache__/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### macOS template 3 | # General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### Python template 31 | # Byte-compiled / optimized / DLL files 32 | __pycache__/ 33 | *.py[cod] 34 | *$py.class 35 | 36 | # C extensions 37 | *.so 38 | 39 | # Distribution / packaging 40 | .Python 41 | build/ 42 | develop-eggs/ 43 | dist/ 44 | downloads/ 45 | eggs/ 46 | .eggs/ 47 | lib/ 48 | lib64/ 49 | parts/ 50 | sdist/ 51 | var/ 52 | wheels/ 53 | pip-wheel-metadata/ 54 | share/python-wheels/ 55 | *.egg-info/ 56 | .installed.cfg 57 | *.egg 58 | MANIFEST 59 | 60 | # PyInstaller 61 | # Usually these files are written by a python script from a template 62 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 63 | *.manifest 64 | *.spec 65 | 66 | # Installer logs 67 | pip-log.txt 68 | pip-delete-this-directory.txt 69 | 70 | # Unit test / coverage reports 71 | htmlcov/ 72 | .tox/ 73 | .nox/ 74 | .coverage 75 | .coverage.* 76 | .cache 77 | nosetests.xml 78 | coverage.xml 79 | *.cover 80 | *.py,cover 81 | .hypothesis/ 82 | .pytest_cache/ 83 | 84 | # Translations 85 | *.mo 86 | *.pot 87 | 88 | # Django stuff: 89 | *.log 90 | local_settings.py 91 | db.sqlite3 92 | db.sqlite3-journal 93 | .user/ 94 | # Flask stuff: 95 | instance/ 96 | .webassets-cache 97 | 98 | # Scrapy stuff: 99 | .scrapy 100 | 101 | # Sphinx documentation 102 | docs/_build/ 103 | 104 | # PyBuilder 105 | target/ 106 | 107 | # Jupyter Notebook 108 | .ipynb_checkpoints 109 | 110 | # IPython 111 | profile_default/ 112 | ipython_config.py 113 | 114 | # pyenv 115 | .python-version 116 | 117 | # pipenv 118 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 119 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 120 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 121 | # install all needed dependencies. 122 | #Pipfile.lock 123 | 124 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 125 | __pypackages__/ 126 | 127 | # Celery stuff 128 | celerybeat-schedule 129 | celerybeat.pid 130 | 131 | # SageMath parsed files 132 | *.sage.py 133 | 134 | # Environments 135 | .env 136 | .venv 137 | env/ 138 | venv/ 139 | ENV/ 140 | env.bak/ 141 | venv.bak/ 142 | 143 | # Spyder project settings 144 | .spyderproject 145 | .spyproject 146 | 147 | # Rope project settings 148 | .ropeproject 149 | 150 | # mkdocs documentation 151 | /site 152 | 153 | # mypy 154 | .mypy_cache/ 155 | .dmypy.json 156 | dmypy.json 157 | 158 | # Pyre type checker 159 | .pyre/ 160 | 161 | ### Example user template template 162 | ### Example user template 163 | 164 | # IntelliJ project files 165 | .idea 166 | *.iml 167 | out 168 | gen 169 | ### JetBrains template 170 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 171 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 172 | 173 | # User-specific stuff 174 | .idea/**/workspace.xml 175 | .idea/**/tasks.xml 176 | .idea/**/usage.statistics.xml 177 | .idea/**/dictionaries 178 | .idea/**/shelf 179 | 180 | # Generated files 181 | .idea/**/contentModel.xml 182 | 183 | # Sensitive or high-churn files 184 | .idea/**/dataSources/ 185 | .idea/**/dataSources.ids 186 | .idea/**/dataSources.local.xml 187 | .idea/**/sqlDataSources.xml 188 | .idea/**/dynamic.xml 189 | .idea/**/uiDesigner.xml 190 | .idea/**/dbnavigator.xml 191 | 192 | # Gradle 193 | .idea/**/gradle.xml 194 | .idea/**/libraries 195 | 196 | # Gradle and Maven with auto-import 197 | # When using Gradle or Maven with auto-import, you should exclude module files, 198 | # since they will be recreated, and may cause churn. Uncomment if using 199 | # auto-import. 200 | # .idea/artifacts 201 | # .idea/compiler.xml 202 | # .idea/modules.xml 203 | # .idea/*.iml 204 | # .idea/modules 205 | # *.iml 206 | # *.ipr 207 | 208 | # CMake 209 | cmake-build-*/ 210 | 211 | # Mongo Explorer plugin 212 | .idea/**/mongoSettings.xml 213 | 214 | # File-based project format 215 | *.iws 216 | 217 | # IntelliJ 218 | out/ 219 | 220 | # mpeltonen/sbt-idea plugin 221 | .idea_modules/ 222 | 223 | # JIRA plugin 224 | atlassian-ide-plugin.xml 225 | 226 | # Cursive Clojure plugin 227 | .idea/replstate.xml 228 | 229 | # Crashlytics plugin (for Android Studio and IntelliJ) 230 | com_crashlytics_export_strings.xml 231 | crashlytics.properties 232 | crashlytics-build.properties 233 | fabric.properties 234 | 235 | # Editor-based Rest Client 236 | .idea/httpRequests 237 | 238 | # Android studio 3.1+ serialized cache file 239 | .idea/caches/build_file_checksums.ser 240 | 241 | /.idea/* 242 | 243 | tests/kd.txt 244 | 245 | *.txt 246 | 247 | tests/test_endpoint.py 248 | 249 | dumpwallet.py 250 | 251 | tests/test_wallets/ 252 | 253 | batch_process.py 254 | **/__pycache__/ 255 | /tests/__pycache__/ 256 | /.circleci/* 257 | -------------------------------------------------------------------------------- /Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y libdb++-dev build-essential python3-dev python3-pip python3-click python3-arrow python3-base58 python3-bsddb3 python3-protobuf git wget tini 7 | 8 | RUN pip3 install walletlib==0.2.10 9 | WORKDIR /app 10 | ENTRYPOINT ["tini", "--", "dumpwallet"] 11 | CMD ["--help"] 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Unlicense (Public Domain) 2 | ============================ 3 | 4 | This is free and unencumbered software released into the public domain. 5 | 6 | Anyone is free to copy, modify, publish, use, compile, sell, or 7 | distribute this software, either in source code form or as a compiled 8 | binary, for any purpose, commercial or non-commercial, and by any 9 | means. 10 | 11 | In jurisdictions that recognize copyright laws, the author or authors 12 | of this software dedicate any and all copyright interest in the 13 | software to the public domain. We make this dedication for the benefit 14 | of the public at large and to the detriment of our heirs and 15 | successors. We intend this dedication to be an overt act of 16 | relinquishment in perpetuity of all present and future rights to this 17 | software under copyright law. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | For more information, please refer to <> 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # walletlib 2 | ![PyPI - Python Version](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue) 3 | 4 | 5 | 6 | Unified interface to programmatically open and extract data from cryptocurrency wallet backup files 7 | 8 | ## Quick Start with Docker - Start here if you simply need to dump the contents of a wallet file 9 | 10 | ### Make sure that the files you are trying to open are in one directory. 11 | 12 | ```bash 13 | $ docker pull jimzhou/walletlib:latest 14 | $ docker run -v /path/to/your/wallet/folder:/app jimzhou/walletlib:latest wallet.dat -o wallet_output.txt --keys -p password 15 | ``` 16 | Output file will be in the directory with the wallet. --keys and -p are optional 17 | 18 | 19 | 20 | ## Quick Start with installation 21 | 22 | This module requires Python 3.7+ 23 | 24 | Note: prior to installation, make sure that BerkeleyDB 4.8+ is installed. 25 | 26 | With Homebrew: 27 | ```bash 28 | $ brew install berkeley-db@4 29 | ``` 30 | 31 | On Ubuntu 32 | ``` 33 | $ sudo apt-get install libdb++-dev python3-bsddb3 34 | ``` 35 | 36 | ```bash 37 | $ pip install walletlib 38 | ``` 39 | A convenient cli script is available after installation. 40 | ```bash 41 | $ python -m dumpwallet wallet.dat -o output.txt 42 | ``` 43 | or 44 | ```bash 45 | $ dumpwallet wallet.dat -o output.txt 46 | $ dumpwallet wallet-protobuf -o output.txt --keys 47 | ``` 48 | 49 | ## Features 50 | - Automatic reading of version byte and WIF prefix from default keys 51 | - Dumping just keys or all data 52 | - Read only access of wallet files 53 | 54 | ## Installation 55 | The simplest way to install walletlib is using pip. 56 | ```bash 57 | $ pip install walletlib 58 | ``` 59 | You can also clone this repo and then run 60 | ```bash 61 | $ python setup.py install 62 | ``` 63 | ## Usage 64 | ```python 65 | import walletlib 66 | 67 | wallet = walletlib.Walletdat.load("path/to/wallet.dat") 68 | wallet.parse(passphrase="password") 69 | wallet.dump_all(filepath="output.txt") 70 | wallet.dump_keys(filepath="output_keys.txt") 71 | 72 | ``` 73 | Bitcoinj wallets: 74 | 75 | ```python 76 | import walletlib 77 | 78 | wallet = walletlib.ProtobufWallet.load("path/to/wallet-protobuf") 79 | wallet.parse() 80 | wallet.dump_all(filepath="output.txt") 81 | wallet.dump_keys(filepath="output_keys.txt") 82 | ``` 83 | 84 | ## Roadmap 85 | - [x] wallet.dat 86 | - [x] Encrypted keys 87 | - [x] Auto-identify prefix 88 | - [x] Decrypt encrypted keys 89 | - [x] p2pkh Wallets 90 | - [ ] Bech32 wallets 91 | - [x] Bitcoinj/Dogecoinj/Altcoinj wallets 92 | - [x] Open unencrypted wallet-protobuf/multibit .wallet/.key files 93 | - [ ] Decrypt encrypted wallets 94 | - [ ] Coinomi protobuf wallets 95 | - [ ] Blockchain.com wallet.aes.json 96 | - [ ] Documentation 97 | -------------------------------------------------------------------------------- /entry_points.ini: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | dumpwallet = walletlib.scripts.dumpwallet:main -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "arrow" 3 | version = "0.17.0" 4 | description = "Better dates & times for Python" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 8 | 9 | [package.dependencies] 10 | python-dateutil = ">=2.7.0" 11 | 12 | [[package]] 13 | name = "asn1crypto" 14 | version = "1.4.0" 15 | description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" 16 | category = "main" 17 | optional = false 18 | python-versions = "*" 19 | 20 | [[package]] 21 | name = "base58" 22 | version = "2.1.1" 23 | description = "Base58 and Base58Check implementation." 24 | category = "main" 25 | optional = false 26 | python-versions = ">=3.5" 27 | 28 | [package.extras] 29 | tests = ["mypy", "PyHamcrest (>=2.0.2)", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] 30 | 31 | [[package]] 32 | name = "betterproto" 33 | version = "1.2.5" 34 | description = "A better Protobuf / gRPC generator & library" 35 | category = "main" 36 | optional = false 37 | python-versions = ">=3.6" 38 | 39 | [package.dependencies] 40 | grpclib = "*" 41 | stringcase = "*" 42 | 43 | [package.extras] 44 | compiler = ["black", "jinja2", "protobuf"] 45 | 46 | [[package]] 47 | name = "bleach" 48 | version = "4.1.0" 49 | description = "An easy safelist-based HTML-sanitizing tool." 50 | category = "dev" 51 | optional = false 52 | python-versions = ">=3.6" 53 | 54 | [package.dependencies] 55 | packaging = "*" 56 | six = ">=1.9.0" 57 | webencodings = "*" 58 | 59 | [[package]] 60 | name = "bsddb3" 61 | version = "6.2.9" 62 | description = "Python bindings for Oracle Berkeley DB" 63 | category = "main" 64 | optional = false 65 | python-versions = "*" 66 | 67 | [[package]] 68 | name = "certifi" 69 | version = "2021.10.8" 70 | description = "Python package for providing Mozilla's CA Bundle." 71 | category = "dev" 72 | optional = false 73 | python-versions = "*" 74 | 75 | [[package]] 76 | name = "cffi" 77 | version = "1.15.0" 78 | description = "Foreign Function Interface for Python calling C code." 79 | category = "main" 80 | optional = false 81 | python-versions = "*" 82 | 83 | [package.dependencies] 84 | pycparser = "*" 85 | 86 | [[package]] 87 | name = "charset-normalizer" 88 | version = "2.0.12" 89 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 90 | category = "dev" 91 | optional = false 92 | python-versions = ">=3.5.0" 93 | 94 | [package.extras] 95 | unicode_backport = ["unicodedata2"] 96 | 97 | [[package]] 98 | name = "click" 99 | version = "7.1.2" 100 | description = "Composable command line interface toolkit" 101 | category = "main" 102 | optional = false 103 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 104 | 105 | [[package]] 106 | name = "coincurve" 107 | version = "15.0.1" 108 | description = "Cross-platform Python CFFI bindings for libsecp256k1" 109 | category = "main" 110 | optional = false 111 | python-versions = ">=3.6" 112 | 113 | [package.dependencies] 114 | asn1crypto = "*" 115 | cffi = ">=1.3.0" 116 | 117 | [[package]] 118 | name = "colorama" 119 | version = "0.4.4" 120 | description = "Cross-platform colored terminal text." 121 | category = "dev" 122 | optional = false 123 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 124 | 125 | [[package]] 126 | name = "cryptography" 127 | version = "36.0.1" 128 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 129 | category = "dev" 130 | optional = false 131 | python-versions = ">=3.6" 132 | 133 | [package.dependencies] 134 | cffi = ">=1.12" 135 | 136 | [package.extras] 137 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] 138 | docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] 139 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] 140 | sdist = ["setuptools_rust (>=0.11.4)"] 141 | ssh = ["bcrypt (>=3.1.5)"] 142 | test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] 143 | 144 | [[package]] 145 | name = "docutils" 146 | version = "0.18.1" 147 | description = "Docutils -- Python Documentation Utilities" 148 | category = "dev" 149 | optional = false 150 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 151 | 152 | [[package]] 153 | name = "grpclib" 154 | version = "0.4.2" 155 | description = "Pure-Python gRPC implementation for asyncio" 156 | category = "main" 157 | optional = false 158 | python-versions = ">=3.6" 159 | 160 | [package.dependencies] 161 | h2 = ">=3.1.0,<5" 162 | multidict = "*" 163 | 164 | [[package]] 165 | name = "h2" 166 | version = "4.1.0" 167 | description = "HTTP/2 State-Machine based protocol implementation" 168 | category = "main" 169 | optional = false 170 | python-versions = ">=3.6.1" 171 | 172 | [package.dependencies] 173 | hpack = ">=4.0,<5" 174 | hyperframe = ">=6.0,<7" 175 | 176 | [[package]] 177 | name = "hpack" 178 | version = "4.0.0" 179 | description = "Pure-Python HPACK header compression" 180 | category = "main" 181 | optional = false 182 | python-versions = ">=3.6.1" 183 | 184 | [[package]] 185 | name = "hyperframe" 186 | version = "6.0.1" 187 | description = "HTTP/2 framing layer for Python" 188 | category = "main" 189 | optional = false 190 | python-versions = ">=3.6.1" 191 | 192 | [[package]] 193 | name = "idna" 194 | version = "3.3" 195 | description = "Internationalized Domain Names in Applications (IDNA)" 196 | category = "dev" 197 | optional = false 198 | python-versions = ">=3.5" 199 | 200 | [[package]] 201 | name = "importlib-metadata" 202 | version = "4.11.1" 203 | description = "Read metadata from Python packages" 204 | category = "dev" 205 | optional = false 206 | python-versions = ">=3.7" 207 | 208 | [package.dependencies] 209 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 210 | zipp = ">=0.5" 211 | 212 | [package.extras] 213 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 214 | perf = ["ipython"] 215 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 216 | 217 | [[package]] 218 | name = "jeepney" 219 | version = "0.7.1" 220 | description = "Low-level, pure Python DBus protocol wrapper." 221 | category = "dev" 222 | optional = false 223 | python-versions = ">=3.6" 224 | 225 | [package.extras] 226 | test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] 227 | trio = ["trio", "async-generator"] 228 | 229 | [[package]] 230 | name = "keyring" 231 | version = "23.5.0" 232 | description = "Store and access your passwords safely." 233 | category = "dev" 234 | optional = false 235 | python-versions = ">=3.7" 236 | 237 | [package.dependencies] 238 | importlib-metadata = ">=3.6" 239 | jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} 240 | pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} 241 | SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} 242 | 243 | [package.extras] 244 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] 245 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] 246 | 247 | [[package]] 248 | name = "multidict" 249 | version = "6.0.2" 250 | description = "multidict implementation" 251 | category = "main" 252 | optional = false 253 | python-versions = ">=3.7" 254 | 255 | [[package]] 256 | name = "packaging" 257 | version = "21.3" 258 | description = "Core utilities for Python packages" 259 | category = "dev" 260 | optional = false 261 | python-versions = ">=3.6" 262 | 263 | [package.dependencies] 264 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 265 | 266 | [[package]] 267 | name = "pkginfo" 268 | version = "1.8.2" 269 | description = "Query metadatdata from sdists / bdists / installed packages." 270 | category = "dev" 271 | optional = false 272 | python-versions = "*" 273 | 274 | [package.extras] 275 | testing = ["coverage", "nose"] 276 | 277 | [[package]] 278 | name = "pycparser" 279 | version = "2.21" 280 | description = "C parser in Python" 281 | category = "main" 282 | optional = false 283 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 284 | 285 | [[package]] 286 | name = "pycryptodome" 287 | version = "3.14.1" 288 | description = "Cryptographic library for Python" 289 | category = "main" 290 | optional = false 291 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 292 | 293 | [[package]] 294 | name = "pygments" 295 | version = "2.11.2" 296 | description = "Pygments is a syntax highlighting package written in Python." 297 | category = "dev" 298 | optional = false 299 | python-versions = ">=3.5" 300 | 301 | [[package]] 302 | name = "pyparsing" 303 | version = "3.0.7" 304 | description = "Python parsing module" 305 | category = "dev" 306 | optional = false 307 | python-versions = ">=3.6" 308 | 309 | [package.extras] 310 | diagrams = ["jinja2", "railroad-diagrams"] 311 | 312 | [[package]] 313 | name = "python-dateutil" 314 | version = "2.8.2" 315 | description = "Extensions to the standard Python datetime module" 316 | category = "main" 317 | optional = false 318 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 319 | 320 | [package.dependencies] 321 | six = ">=1.5" 322 | 323 | [[package]] 324 | name = "pywin32-ctypes" 325 | version = "0.2.0" 326 | description = "" 327 | category = "dev" 328 | optional = false 329 | python-versions = "*" 330 | 331 | [[package]] 332 | name = "readme-renderer" 333 | version = "32.0" 334 | description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" 335 | category = "dev" 336 | optional = false 337 | python-versions = ">=3.6" 338 | 339 | [package.dependencies] 340 | bleach = ">=2.1.0" 341 | docutils = ">=0.13.1" 342 | Pygments = ">=2.5.1" 343 | 344 | [package.extras] 345 | md = ["cmarkgfm (>=0.5.0,<0.7.0)"] 346 | 347 | [[package]] 348 | name = "requests" 349 | version = "2.27.1" 350 | description = "Python HTTP for Humans." 351 | category = "dev" 352 | optional = false 353 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 354 | 355 | [package.dependencies] 356 | certifi = ">=2017.4.17" 357 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 358 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 359 | urllib3 = ">=1.21.1,<1.27" 360 | 361 | [package.extras] 362 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 363 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 364 | 365 | [[package]] 366 | name = "requests-toolbelt" 367 | version = "0.9.1" 368 | description = "A utility belt for advanced users of python-requests" 369 | category = "dev" 370 | optional = false 371 | python-versions = "*" 372 | 373 | [package.dependencies] 374 | requests = ">=2.0.1,<3.0.0" 375 | 376 | [[package]] 377 | name = "rfc3986" 378 | version = "2.0.0" 379 | description = "Validating URI References per RFC 3986" 380 | category = "dev" 381 | optional = false 382 | python-versions = ">=3.7" 383 | 384 | [package.extras] 385 | idna2008 = ["idna"] 386 | 387 | [[package]] 388 | name = "secretstorage" 389 | version = "3.3.1" 390 | description = "Python bindings to FreeDesktop.org Secret Service API" 391 | category = "dev" 392 | optional = false 393 | python-versions = ">=3.6" 394 | 395 | [package.dependencies] 396 | cryptography = ">=2.0" 397 | jeepney = ">=0.6" 398 | 399 | [[package]] 400 | name = "six" 401 | version = "1.16.0" 402 | description = "Python 2 and 3 compatibility utilities" 403 | category = "main" 404 | optional = false 405 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 406 | 407 | [[package]] 408 | name = "stringcase" 409 | version = "1.2.0" 410 | description = "String case converter." 411 | category = "main" 412 | optional = false 413 | python-versions = "*" 414 | 415 | [[package]] 416 | name = "tqdm" 417 | version = "4.62.3" 418 | description = "Fast, Extensible Progress Meter" 419 | category = "dev" 420 | optional = false 421 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 422 | 423 | [package.dependencies] 424 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 425 | 426 | [package.extras] 427 | dev = ["py-make (>=0.1.0)", "twine", "wheel"] 428 | notebook = ["ipywidgets (>=6)"] 429 | telegram = ["requests"] 430 | 431 | [[package]] 432 | name = "twine" 433 | version = "3.7.1" 434 | description = "Collection of utilities for publishing packages on PyPI" 435 | category = "dev" 436 | optional = false 437 | python-versions = ">=3.6" 438 | 439 | [package.dependencies] 440 | colorama = ">=0.4.3" 441 | importlib-metadata = ">=3.6" 442 | keyring = ">=15.1" 443 | pkginfo = ">=1.8.1" 444 | readme-renderer = ">=21.0" 445 | requests = ">=2.20" 446 | requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" 447 | rfc3986 = ">=1.4.0" 448 | tqdm = ">=4.14" 449 | 450 | [[package]] 451 | name = "typing-extensions" 452 | version = "4.1.1" 453 | description = "Backported and Experimental Type Hints for Python 3.6+" 454 | category = "dev" 455 | optional = false 456 | python-versions = ">=3.6" 457 | 458 | [[package]] 459 | name = "urllib3" 460 | version = "1.22" 461 | description = "HTTP library with thread-safe connection pooling, file post, and more." 462 | category = "dev" 463 | optional = false 464 | python-versions = "*" 465 | 466 | [package.extras] 467 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 468 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 469 | 470 | [[package]] 471 | name = "webencodings" 472 | version = "0.5.1" 473 | description = "Character encoding aliases for legacy web content" 474 | category = "dev" 475 | optional = false 476 | python-versions = "*" 477 | 478 | [[package]] 479 | name = "zipp" 480 | version = "3.7.0" 481 | description = "Backport of pathlib-compatible object wrapper for zip files" 482 | category = "dev" 483 | optional = false 484 | python-versions = ">=3.7" 485 | 486 | [package.extras] 487 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 488 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 489 | 490 | [metadata] 491 | lock-version = "1.1" 492 | python-versions = ">= 3.7" 493 | content-hash = "2e462f649676f7cb097dbebd52e70ab402cbd89472b88a465100f6a672c9d900" 494 | 495 | [metadata.files] 496 | arrow = [ 497 | {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, 498 | {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, 499 | ] 500 | asn1crypto = [ 501 | {file = "asn1crypto-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8"}, 502 | {file = "asn1crypto-1.4.0.tar.gz", hash = "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c"}, 503 | ] 504 | base58 = [ 505 | {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, 506 | {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, 507 | ] 508 | betterproto = [ 509 | {file = "betterproto-1.2.5.tar.gz", hash = "sha256:74a3ab34646054f674d236d1229ba8182dc2eae86feb249b8590ef496ce9803d"}, 510 | ] 511 | bleach = [ 512 | {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, 513 | {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, 514 | ] 515 | bsddb3 = [ 516 | {file = "bsddb3-6.2.9.tar.gz", hash = "sha256:70d05ec8dc568f42e70fc919a442e0daadc2a905a1cfb7ca77f549d49d6e7801"}, 517 | ] 518 | certifi = [ 519 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 520 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 521 | ] 522 | cffi = [ 523 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 524 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 525 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 526 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 527 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 528 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 529 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 530 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 531 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 532 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 533 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 534 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 535 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 536 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 537 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 538 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 539 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 540 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 541 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 542 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 543 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 544 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 545 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 546 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 547 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 548 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 549 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 550 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 551 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 552 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 553 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 554 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 555 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 556 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 557 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 558 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 559 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 560 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 561 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 562 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 563 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 564 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 565 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 566 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 567 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 568 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 569 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 570 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 571 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 572 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 573 | ] 574 | charset-normalizer = [ 575 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 576 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 577 | ] 578 | click = [ 579 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 580 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 581 | ] 582 | coincurve = [ 583 | {file = "coincurve-15.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:764065be6c1953b287af635d5e5ec232cb303b259ee232a86624b743db77436d"}, 584 | {file = "coincurve-15.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3c29becba9c484400567bfc04970fd1ddef5d9086da6ad58daa87e632579847"}, 585 | {file = "coincurve-15.0.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e99265e22d5fc7cb28a378a9efc185320904df8901bf776bead4c7c5b6ba254"}, 586 | {file = "coincurve-15.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae738c3730ef4230b13a9e0d4ebda2c6bdd2d3a8065a3f2887392d44734e483"}, 587 | {file = "coincurve-15.0.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2b21120f2fb6223a16f13612af349a6b33b777911c34da4d01347ae905f1f895"}, 588 | {file = "coincurve-15.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dfddadfffd119fc62792478b8e85cb4db7731d1ca9edf80deffea0ed889eae1"}, 589 | {file = "coincurve-15.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a68add46c590c75ec041b230368bd6e96ffbf5c6be9cb6c8b9672e3196c6a0a"}, 590 | {file = "coincurve-15.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe09a17fcb5c2ce0a39bb100eb65863ba296bdc07baabe23fe4736b965147ed"}, 591 | {file = "coincurve-15.0.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9030de7b770217b1e3a0b07a46b112407bbc6a671ba1dc08ee1546e7ebaea512"}, 592 | {file = "coincurve-15.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d32eebad222132a2e654c8a10ced1ddec02e0b0c0b45780984f53536131caea3"}, 593 | {file = "coincurve-15.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40238fb994cea86d3a8af6decc848cfde5987372c7c93851ea4eff4a181139a7"}, 594 | {file = "coincurve-15.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d8d02ad38f07f31088bff0d629b215308b623f506d840696c9c65723580ad2"}, 595 | {file = "coincurve-15.0.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:eb32ae15b9de3db6e712167b4469dc7354f93e0a04dd1353c1e555d6dc6d9c53"}, 596 | {file = "coincurve-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7cafea5041fe523207e91e91eb764a9656aed4387488f6b0fb1cca91eebd8b7"}, 597 | {file = "coincurve-15.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35db5ed2199483e68106b4ef31f79824306bb2d9a9cb750930a9b2325da63ae5"}, 598 | {file = "coincurve-15.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45ae1be2c8cb9d1c4447245060778552452db4875f8c79ffc329436f78576ca5"}, 599 | {file = "coincurve-15.0.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f8f6cb28d6252b3f51325348c26d32102e3bcb5e93f4a986f1e231e0e1660c7"}, 600 | {file = "coincurve-15.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eed4b4bf721f4511ae678c676e2a4e8ba6a0480171d59dbdce8776d9e0bec47e"}, 601 | {file = "coincurve-15.0.1-py3-none-win32.whl", hash = "sha256:52e8c87ff8587cd346735cc1687f27e0bab49606e6e4be963b46f1bdb39ad9de"}, 602 | {file = "coincurve-15.0.1-py3-none-win_amd64.whl", hash = "sha256:11bd37fc072aaf22b40d05c4a9587c4b03058e8b22e70ca38fff7b3bed14e23c"}, 603 | {file = "coincurve-15.0.1.tar.gz", hash = "sha256:eb556b4c52827ca4b32a3b6cf86b19b848ae1cf9ab5e2bf7ed2eb05aa38aabe3"}, 604 | ] 605 | colorama = [ 606 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 607 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 608 | ] 609 | cryptography = [ 610 | {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, 611 | {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, 612 | {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, 613 | {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, 614 | {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, 615 | {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, 616 | {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, 617 | {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, 618 | {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, 619 | {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, 620 | {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, 621 | {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, 622 | {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, 623 | {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, 624 | {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, 625 | {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, 626 | {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, 627 | {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, 628 | {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, 629 | {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, 630 | ] 631 | docutils = [ 632 | {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, 633 | {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, 634 | ] 635 | grpclib = [ 636 | {file = "grpclib-0.4.2.tar.gz", hash = "sha256:ead080cb7d56d6a5e835aaf5255d1ef1dce475a7722566ea225f0188fce33b68"}, 637 | ] 638 | h2 = [ 639 | {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, 640 | {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, 641 | ] 642 | hpack = [ 643 | {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, 644 | {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, 645 | ] 646 | hyperframe = [ 647 | {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, 648 | {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, 649 | ] 650 | idna = [ 651 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 652 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 653 | ] 654 | importlib-metadata = [ 655 | {file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"}, 656 | {file = "importlib_metadata-4.11.1.tar.gz", hash = "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c"}, 657 | ] 658 | jeepney = [ 659 | {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, 660 | {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, 661 | ] 662 | keyring = [ 663 | {file = "keyring-23.5.0-py3-none-any.whl", hash = "sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261"}, 664 | {file = "keyring-23.5.0.tar.gz", hash = "sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9"}, 665 | ] 666 | multidict = [ 667 | {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, 668 | {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, 669 | {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, 670 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, 671 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, 672 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, 673 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, 674 | {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, 675 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, 676 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, 677 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, 678 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, 679 | {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, 680 | {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, 681 | {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, 682 | {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, 683 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, 684 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, 685 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, 686 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, 687 | {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, 688 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, 689 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, 690 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, 691 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, 692 | {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, 693 | {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, 694 | {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, 695 | {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, 696 | {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, 697 | {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, 698 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, 699 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, 700 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, 701 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, 702 | {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, 703 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, 704 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, 705 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, 706 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, 707 | {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, 708 | {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, 709 | {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, 710 | {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, 711 | {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, 712 | {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, 713 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, 714 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, 715 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, 716 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, 717 | {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, 718 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, 719 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, 720 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, 721 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, 722 | {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, 723 | {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, 724 | {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, 725 | {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, 726 | ] 727 | packaging = [ 728 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 729 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 730 | ] 731 | pkginfo = [ 732 | {file = "pkginfo-1.8.2-py2.py3-none-any.whl", hash = "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"}, 733 | {file = "pkginfo-1.8.2.tar.gz", hash = "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff"}, 734 | ] 735 | pycparser = [ 736 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 737 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 738 | ] 739 | pycryptodome = [ 740 | {file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"}, 741 | {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"}, 742 | {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493"}, 743 | {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263"}, 744 | {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72"}, 745 | {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367"}, 746 | {file = "pycryptodome-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30"}, 747 | {file = "pycryptodome-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132"}, 748 | {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222"}, 749 | {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e"}, 750 | {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89"}, 751 | {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8"}, 752 | {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2"}, 753 | {file = "pycryptodome-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423"}, 754 | {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0"}, 755 | {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c"}, 756 | {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7"}, 757 | {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc"}, 758 | {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5"}, 759 | {file = "pycryptodome-3.14.1-cp35-abi3-win32.whl", hash = "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192"}, 760 | {file = "pycryptodome-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84"}, 761 | {file = "pycryptodome-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5"}, 762 | {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875"}, 763 | {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075"}, 764 | {file = "pycryptodome-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525"}, 765 | {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50"}, 766 | {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba"}, 767 | {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d"}, 768 | {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"}, 769 | {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"}, 770 | ] 771 | pygments = [ 772 | {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, 773 | {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, 774 | ] 775 | pyparsing = [ 776 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 777 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 778 | ] 779 | python-dateutil = [ 780 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 781 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 782 | ] 783 | pywin32-ctypes = [ 784 | {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, 785 | {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, 786 | ] 787 | readme-renderer = [ 788 | {file = "readme_renderer-32.0-py3-none-any.whl", hash = "sha256:a50a0f2123a4c1145ac6f420e1a348aafefcc9211c846e3d51df05fe3d865b7d"}, 789 | {file = "readme_renderer-32.0.tar.gz", hash = "sha256:b512beafa6798260c7d5af3e1b1f097e58bfcd9a575da7c4ddd5e037490a5b85"}, 790 | ] 791 | requests = [ 792 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 793 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 794 | ] 795 | requests-toolbelt = [ 796 | {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, 797 | {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, 798 | ] 799 | rfc3986 = [ 800 | {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, 801 | {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, 802 | ] 803 | secretstorage = [ 804 | {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, 805 | {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, 806 | ] 807 | six = [ 808 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 809 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 810 | ] 811 | stringcase = [ 812 | {file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"}, 813 | ] 814 | tqdm = [ 815 | {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, 816 | {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, 817 | ] 818 | twine = [ 819 | {file = "twine-3.7.1-py3-none-any.whl", hash = "sha256:8c120845fc05270f9ee3e9d7ebbed29ea840e41f48cd059e04733f7e1d401345"}, 820 | {file = "twine-3.7.1.tar.gz", hash = "sha256:28460a3db6b4532bde6a5db6755cf2dce6c5020bada8a641bb2c5c7a9b1f35b8"}, 821 | ] 822 | typing-extensions = [ 823 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 824 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 825 | ] 826 | urllib3 = [ 827 | {file = "urllib3-1.22-py2.py3-none-any.whl", hash = "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b"}, 828 | {file = "urllib3-1.22.tar.gz", hash = "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"}, 829 | ] 830 | webencodings = [ 831 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 832 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 833 | ] 834 | zipp = [ 835 | {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, 836 | {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, 837 | ] 838 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "walletlib" 3 | version = "0.2.10" 4 | description = "Library for accessing cryptocurrency wallet files" 5 | authors = ["jim zhou <43537315+jimtje@users.noreply.github.com>"] 6 | license = "Unlicense" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">= 3.7" 10 | pycryptodome = "^3.9.9" 11 | click = "^7.1.2" 12 | coincurve = "^15.0.1" 13 | bsddb3 = "^6.2.9" 14 | base58 = "^2.1.0" 15 | arrow = "^0.17.0" 16 | betterproto = "^1.2.5" 17 | 18 | [tool.poetry.dev-dependencies] 19 | twine = "^3.4.1" 20 | 21 | [tool.poetry.scripts] 22 | dumpwallet = "walletlib.scripts.dumpwallet:main" 23 | 24 | [build-system] 25 | requires = ["setuptools", "wheel"] 26 | build-backend = "setuptools.build_meta" 27 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true 6 | } 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome==3.9.9 2 | Click==7.1.2 3 | coincurve==15.0.1 4 | bsddb3==6.2.9 5 | base58==2.1.0 6 | arrow==0.17.0 7 | betterproto==1.2.5 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | with open("requirements.txt") as f: 5 | requirements = f.read().splitlines() 6 | 7 | 8 | with open("README.md", "r") as fh: 9 | long_description = fh.read() 10 | 11 | setup( 12 | name="walletlib", 13 | version="0.2.10", 14 | packages=find_packages(), 15 | include_package_data=True, 16 | url="https://github.com/jimtje/walletlib", 17 | license="Unlicense", 18 | author="jim zhou", 19 | author_email="jimtje@gmail.com", 20 | description="Library for accessing cryptocurrency wallet files", 21 | long_description=long_description, 22 | long_description_content_type='text/markdown', 23 | keywords='wallet crypto cryptocurrency', 24 | python_requires=">=3.7.0", 25 | install_requires=requirements, 26 | entry_points={ 27 | "console_scripts": ["dumpwallet = walletlib.scripts.dumpwallet:main"] 28 | }, 29 | ) 30 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimtje/walletlib/73032d226bf5d8871a6604e1b1c2216c316ea4a1/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_endpoint.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimtje/walletlib/73032d226bf5d8871a6604e1b1c2216c316ea4a1/tests/test_endpoint.py -------------------------------------------------------------------------------- /tests/test_walletdat.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | -------------------------------------------------------------------------------- /walletlib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library for accessing cryptocurrency wallet files 3 | 4 | keywords: wallet, crypto, wallet.dat 5 | author: jim zhou jimtje@gmail.com 6 | """ 7 | 8 | from .walletdat import Walletdat 9 | from .protobufwallet import ProtobufWallet 10 | __version__ = "0.2.10" 11 | 12 | __url__ = "https://github.com/jimtje/walletlib" 13 | 14 | 15 | __all__ = ["Walletdat", "ProtobufWallet"] 16 | -------------------------------------------------------------------------------- /walletlib/base32.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Pieter Wuille 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | 22 | BECH32_VERSION_SET = ('bc', 'tb', 'bcrt') 23 | CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 24 | 25 | 26 | def bech32_polymod(values): 27 | """Internal function that computes the Bech32 checksum.""" 28 | generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 29 | chk = 1 30 | for value in values: 31 | top = chk >> 25 32 | chk = (chk & 0x1ffffff) << 5 ^ value 33 | for i in range(5): 34 | chk ^= generator[i] if ((top >> i) & 1) else 0 35 | return chk 36 | 37 | 38 | def bech32_hrp_expand(hrp): 39 | """Expand the HRP into values for checksum computation.""" 40 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] 41 | 42 | 43 | def bech32_verify_checksum(hrp, data): 44 | """Verify a checksum given HRP and converted data characters.""" 45 | return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 46 | 47 | 48 | def bech32_create_checksum(hrp, data): 49 | """Compute the checksum values given HRP and data.""" 50 | values = bech32_hrp_expand(hrp) + data 51 | polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 52 | return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] 53 | 54 | 55 | def bech32_encode(hrp, data): 56 | """Compute a Bech32 string given HRP and data values.""" 57 | combined = data + bech32_create_checksum(hrp, data) 58 | return hrp + '1' + ''.join([CHARSET[d] for d in combined]) 59 | 60 | 61 | def bech32_decode(bech): 62 | """Validate a Bech32 string, and determine HRP and data.""" 63 | if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or 64 | (bech.lower() != bech and bech.upper() != bech)): 65 | return (None, None) 66 | bech = bech.lower() 67 | pos = bech.rfind('1') 68 | if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: 69 | return (None, None) 70 | if not all(x in CHARSET for x in bech[pos + 1:]): 71 | return (None, None) 72 | hrp = bech[:pos] 73 | data = [CHARSET.find(x) for x in bech[pos + 1:]] 74 | if not bech32_verify_checksum(hrp, data): 75 | return (None, None) 76 | return (hrp, data[:-6]) 77 | 78 | 79 | def convertbits(data, frombits, tobits, pad=True): 80 | """General power-of-2 base conversion.""" 81 | acc = 0 82 | bits = 0 83 | ret = [] 84 | maxv = (1 << tobits) - 1 85 | max_acc = (1 << (frombits + tobits - 1)) - 1 86 | for value in data: 87 | if value < 0 or (value >> frombits): 88 | return None 89 | acc = ((acc << frombits) | value) & max_acc 90 | bits += frombits 91 | while bits >= tobits: 92 | bits -= tobits 93 | ret.append((acc >> bits) & maxv) 94 | if pad: 95 | if bits: 96 | ret.append((acc << (tobits - bits)) & maxv) 97 | elif bits >= frombits or ((acc << (tobits - bits)) & maxv): 98 | return None 99 | return ret 100 | 101 | 102 | # def decode(hrp, addr): 103 | def decode(addr): 104 | """Decode a segwit address.""" 105 | hrpgot, data = bech32_decode(addr) 106 | # if hrpgot != hrp: 107 | if hrpgot not in BECH32_VERSION_SET: 108 | return (None, None) 109 | decoded = convertbits(data[1:], 5, 8, False) 110 | if decoded is None or len(decoded) < 2 or len(decoded) > 40: 111 | return (None, None) 112 | if data[0] > 16: 113 | return (None, None) 114 | if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: 115 | return (None, None) 116 | return (data[0], decoded) 117 | 118 | 119 | def encode(hrp, witver, witprog): 120 | """Encode a segwit address.""" 121 | ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) 122 | if decode(ret) == (None, None): 123 | return None 124 | return ret 125 | -------------------------------------------------------------------------------- /walletlib/bitcoinj_compat.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # sources: wallet.proto 3 | # plugin: python-betterproto 4 | from dataclasses import dataclass 5 | from typing import List 6 | 7 | import betterproto 8 | 9 | 10 | class KeyType(betterproto.Enum): 11 | ORIGINAL = 1 12 | ENCRYPTED_SCRYPT_AES = 2 13 | DETERMINISTIC_MNEMONIC = 3 14 | DETERMINISTIC_KEY = 4 15 | 16 | 17 | class TransactionConfidenceType(betterproto.Enum): 18 | UNKNOWN = 0 19 | BUILDING = 1 20 | PENDING = 2 21 | NOT_IN_BEST_CHAIN = 3 22 | DEAD = 4 23 | 24 | 25 | class TransactionConfidenceSource(betterproto.Enum): 26 | SOURCE_UNKNOWN = 0 27 | SOURCE_NETWORK = 1 28 | SOURCE_SELF = 2 29 | 30 | 31 | class TransactionPool(betterproto.Enum): 32 | UNSPENT = 4 33 | SPENT = 5 34 | INACTIVE = 2 35 | DEAD = 10 36 | PENDING = 16 37 | PENDING_INACTIVE = 18 38 | 39 | 40 | class TransactionPurpose(betterproto.Enum): 41 | UNKNOWN = 0 42 | USER_PAYMENT = 1 43 | KEY_ROTATION = 2 44 | ASSURANCE_CONTRACT_CLAIM = 3 45 | ASSURANCE_CONTRACT_PLEDGE = 4 46 | ASSURANCE_CONTRACT_STUB = 5 47 | 48 | 49 | class WalletEncryptionType(betterproto.Enum): 50 | UNENCRYPTED = 1 51 | ENCRYPTED_SCRYPT_AES = 2 52 | 53 | 54 | @dataclass 55 | class PeerAddress(betterproto.Message): 56 | ip_address: bytes = betterproto.bytes_field(1) 57 | port: int = betterproto.uint32_field(2) 58 | services: int = betterproto.uint64_field(3) 59 | 60 | 61 | @dataclass 62 | class EncryptedData(betterproto.Message): 63 | initialisation_vector: bytes = betterproto.bytes_field(1) 64 | encrypted_private_key: bytes = betterproto.bytes_field(2) 65 | 66 | 67 | @dataclass 68 | class DeterministicKey(betterproto.Message): 69 | """ 70 | * Data attached to a Key message that defines the data needed by the BIP32 71 | deterministic key hierarchy algorithm. 72 | """ 73 | 74 | # Random data that allows us to extend a key. Without this, we can't figure 75 | # out the next key in the chain and should just treat it as a regular 76 | # ORIGINAL type key. 77 | chain_code: bytes = betterproto.bytes_field(1) 78 | # The path through the key tree. Each number is encoded in the standard form: 79 | # high bit set for private derivation and high bit unset for public 80 | # derivation. 81 | path: List[int] = betterproto.uint32_field(2) 82 | # How many children of this key have been issued, that is, given to the user 83 | # when they requested a fresh key? For the parents of keys being handed out, 84 | # this is always less than the true number of children: the difference is 85 | # called the lookahead zone. These keys are put into Bloom filters so we can 86 | # spot transactions made by clones of this wallet - for instance when 87 | # restoring from backup or if the seed was shared between devices. If this 88 | # field is missing it means we're not issuing subkeys of this key to users. 89 | issued_subkeys: int = betterproto.uint32_field(3) 90 | lookahead_size: int = betterproto.uint32_field(4) 91 | # * Flag indicating that this key is a root of a following chain. This chain 92 | # is following the next non-following chain. Following/followed chains 93 | # concept is used for married keychains, where the set of keys combined 94 | # together to produce a single P2SH multisignature address 95 | is_following: bool = betterproto.bool_field(5) 96 | # Number of signatures required to spend. This field is needed only for 97 | # married keychains to reconstruct KeyChain and represents the N value from 98 | # N-of-M CHECKMULTISIG script. For regular single keychains it will always be 99 | # 1. 100 | sigs_required_to_spend: int = betterproto.uint32_field(6) 101 | 102 | 103 | @dataclass 104 | class Key(betterproto.Message): 105 | """ 106 | * A key used to control Bitcoin spending. Either the private key, the 107 | public key or both may be present. It is recommended that if the private 108 | key is provided that the public key is provided too because deriving it is 109 | slow. If only the public key is provided, the key can only be used to watch 110 | the blockchain and verify transactions, and not for spending. 111 | """ 112 | 113 | type: "KeyType" = betterproto.enum_field(1) 114 | # Either the private EC key bytes (without any ASN.1 wrapping), or the 115 | # deterministic root seed. If the secret is encrypted, or this is a "watching 116 | # entry" then this is missing. 117 | secret_bytes: bytes = betterproto.bytes_field(2) 118 | # If the secret data is encrypted, then secret_bytes is missing and this 119 | # field is set. 120 | encrypted_data: "EncryptedData" = betterproto.message_field(6) 121 | # The public EC key derived from the private key. We allow both to be stored 122 | # to avoid mobile clients having to do lots of slow EC math on startup. For 123 | # DETERMINISTIC_MNEMONIC entries this is missing. 124 | public_key: bytes = betterproto.bytes_field(3) 125 | # User-provided label associated with the key. 126 | label: str = betterproto.string_field(4) 127 | # Timestamp stored as millis since epoch. Useful for skipping block bodies 128 | # before this point. Only reason it's optional is that some very old wallets 129 | # don't have this data. 130 | creation_timestamp: int = betterproto.int64_field(5) 131 | deterministic_key: "DeterministicKey" = betterproto.message_field(7) 132 | # The seed for a deterministic key hierarchy. Derived from the mnemonic, but 133 | # cached here for quick startup. Only applicable to a DETERMINISTIC_MNEMONIC 134 | # key entry. 135 | deterministic_seed: bytes = betterproto.bytes_field(8) 136 | # Encrypted version of the seed 137 | encrypted_deterministic_seed: "EncryptedData" = betterproto.message_field( 138 | 9) 139 | 140 | 141 | @dataclass 142 | class Script(betterproto.Message): 143 | program: bytes = betterproto.bytes_field(1) 144 | # Timestamp stored as millis since epoch. Useful for skipping block bodies 145 | # before this point when watching for scripts on the blockchain. 146 | creation_timestamp: int = betterproto.int64_field(2) 147 | 148 | 149 | @dataclass 150 | class TransactionInput(betterproto.Message): 151 | # Hash of the transaction this input is using. 152 | transaction_out_point_hash: bytes = betterproto.bytes_field(1) 153 | # Index of transaction output used by this input. 154 | transaction_out_point_index: int = betterproto.uint32_field(2) 155 | # Script that contains the signatures/pubkeys. 156 | script_bytes: bytes = betterproto.bytes_field(3) 157 | # Sequence number. Currently unused, but intended for contracts in future. 158 | sequence: int = betterproto.uint32_field(4) 159 | # Value of connected output, if known 160 | value: int = betterproto.int64_field(5) 161 | 162 | 163 | @dataclass 164 | class TransactionOutput(betterproto.Message): 165 | value: int = betterproto.int64_field(1) 166 | script_bytes: bytes = betterproto.bytes_field(2) 167 | # If spent, the hash of the transaction doing the spend. 168 | spent_by_transaction_hash: bytes = betterproto.bytes_field(3) 169 | # If spent, the index of the transaction input of the transaction doing the 170 | # spend. 171 | spent_by_transaction_index: int = betterproto.int32_field(4) 172 | 173 | 174 | @dataclass 175 | class TransactionConfidence(betterproto.Message): 176 | """ 177 | * A description of the confidence we have that a transaction cannot be 178 | reversed in the future. Parsing should be lenient, since this could change 179 | for different applications yet we should maintain backward compatibility. 180 | """ 181 | 182 | # This is optional in case we add confidence types to prevent parse errors - 183 | # backwards compatible. 184 | type: "TransactionConfidenceType" = betterproto.enum_field(1) 185 | # If type == BUILDING then this is the chain height at which the transaction 186 | # was included. 187 | appeared_at_height: int = betterproto.int32_field(2) 188 | # If set, hash of the transaction that double spent this one into oblivion. A 189 | # transaction can be double spent by multiple transactions in the case of 190 | # several inputs being re-spent by several transactions but we don't bother 191 | # to track them all, just the first. This only makes sense if type = DEAD. 192 | overriding_transaction: bytes = betterproto.bytes_field(3) 193 | # If type == BUILDING then this is the depth of the transaction in the 194 | # blockchain. Zero confirmations: depth = 0, one confirmation: depth = 1 195 | # etc. 196 | depth: int = betterproto.int32_field(4) 197 | broadcast_by: List["PeerAddress"] = betterproto.message_field(6) 198 | source: "TransactionConfidenceSource" = betterproto.enum_field(7) 199 | 200 | 201 | @dataclass 202 | class Transaction(betterproto.Message): 203 | # See Wallet.java for detailed description of pool semantics 204 | version: int = betterproto.int32_field(1) 205 | hash: bytes = betterproto.bytes_field(2) 206 | # If pool is not present, that means either: - This Transaction is either 207 | # not in a wallet at all (the proto is re-used elsewhere) - Or it is stored 208 | # but for other purposes, for example, because it is the overriding 209 | # transaction of a double spend. - Or the Pool enum got a new value which 210 | # your software is too old to parse. 211 | pool: "TransactionPool" = betterproto.enum_field(3) 212 | lock_time: int = betterproto.uint32_field(4) 213 | updated_at: int = betterproto.int64_field(5) 214 | transaction_input: List["TransactionInput"] = betterproto.message_field(6) 215 | transaction_output: List["TransactionOutput"] = betterproto.message_field( 216 | 7) 217 | # A list of blocks in which the transaction has been observed (on any chain). 218 | # Also, a number used to disambiguate ordering within a block. 219 | block_hash: List[bytes] = betterproto.bytes_field(8) 220 | block_relativity_offsets: List[int] = betterproto.int32_field(11) 221 | # Data describing where the transaction is in the chain. 222 | confidence: "TransactionConfidence" = betterproto.message_field(9) 223 | purpose: "TransactionPurpose" = betterproto.enum_field(10) 224 | # Exchange rate that was valid when the transaction was sent. 225 | exchange_rate: "ExchangeRate" = betterproto.message_field(12) 226 | # Memo of the transaction. It can be used to record the memo of the payment 227 | # request that initiated the transaction. 228 | memo: str = betterproto.string_field(13) 229 | 230 | 231 | @dataclass 232 | class ScryptParameters(betterproto.Message): 233 | """ 234 | * The parameters used in the scrypt key derivation function. The default 235 | values are taken from http://www.tarsnap.com/scrypt/scrypt-slides.pdf. 236 | They can be increased - n is the number of iterations performed and r and 237 | p can be used to tweak the algorithm - see: 238 | http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work- 239 | factors 240 | """ 241 | 242 | salt: bytes = betterproto.bytes_field(1) 243 | n: int = betterproto.int64_field(2) 244 | r: int = betterproto.int32_field(3) 245 | p: int = betterproto.int32_field(4) 246 | 247 | 248 | @dataclass 249 | class Extension(betterproto.Message): 250 | """* An extension to the wallet""" 251 | 252 | id: str = betterproto.string_field(1) 253 | data: bytes = betterproto.bytes_field(2) 254 | # If we do not understand a mandatory extension, abort to prevent data loss. 255 | # For example, this could be applied to a new type of holding, such as a 256 | # contract, where dropping of an extension in a read/write cycle could cause 257 | # loss of value. 258 | mandatory: bool = betterproto.bool_field(3) 259 | 260 | 261 | @dataclass 262 | class Tag(betterproto.Message): 263 | """ 264 | * A simple key->value mapping that has no interpreted content at all. A bit 265 | like the extensions mechanism except an extension is keyed by the ID of a 266 | piece of code that's loaded with the given data, and has the concept of 267 | being mandatory if that code isn't found. Whereas this is just a blind 268 | key/value store. 269 | """ 270 | 271 | tag: str = betterproto.string_field(1) 272 | data: bytes = betterproto.bytes_field(2) 273 | 274 | 275 | @dataclass 276 | class TransactionSigner(betterproto.Message): 277 | """* Data required to reconstruct TransactionSigner.""" 278 | 279 | # fully qualified class name of TransactionSigner implementation 280 | class_name: str = betterproto.string_field(1) 281 | # arbitrary data required for signer to function 282 | data: bytes = betterproto.bytes_field(2) 283 | 284 | 285 | @dataclass 286 | class Wallet(betterproto.Message): 287 | """* A bitcoin wallet""" 288 | 289 | network_identifier: str = betterproto.string_field(1) 290 | # The SHA256 hash of the head of the best chain seen by this wallet. 291 | last_seen_block_hash: bytes = betterproto.bytes_field(2) 292 | # The height in the chain of the last seen block. 293 | last_seen_block_height: int = betterproto.uint32_field(12) 294 | last_seen_block_time_secs: int = betterproto.int64_field(14) 295 | key: List["Key"] = betterproto.message_field(3) 296 | transaction: List["Transaction"] = betterproto.message_field(4) 297 | watched_script: List["Script"] = betterproto.message_field(15) 298 | encryption_type: "WalletEncryptionType" = betterproto.enum_field(5) 299 | encryption_parameters: "ScryptParameters" = betterproto.message_field(6) 300 | # The version number of the wallet - used to detect wallets that were 301 | # produced in the future (i.e. the wallet may contain some future format this 302 | # protobuf or parser code does not know about). A version that's higher than 303 | # the default is considered from the future. 304 | version: int = betterproto.int32_field(7) 305 | extension: List["Extension"] = betterproto.message_field(10) 306 | # A UTF8 encoded text description of the wallet that is intended for end user 307 | # provided text. 308 | description: str = betterproto.string_field(11) 309 | # UNIX time in seconds since the epoch. If set, then any keys created before 310 | # this date are assumed to be no longer wanted. Money sent to them will be 311 | # re-spent automatically to the first key that was created after this time. 312 | # It can be used to recover a compromised wallet, or just as part of 313 | # preventative defence-in-depth measures. 314 | key_rotation_time: int = betterproto.uint64_field(13) 315 | tags: List["Tag"] = betterproto.message_field(16) 316 | # transaction signers added to the wallet 317 | transaction_signers: List["TransactionSigner"] = betterproto.message_field( 318 | 17) 319 | 320 | 321 | @dataclass 322 | class ExchangeRate(betterproto.Message): 323 | """* An exchange rate between Bitcoin and some fiat currency.""" 324 | 325 | # This much of satoshis (1E-8 fractions)… 326 | coin_value: int = betterproto.int64_field(1) 327 | # …is worth this much of fiat (1E-4 fractions). 328 | fiat_value: int = betterproto.int64_field(2) 329 | # ISO 4217 currency code (if available) of the fiat currency. 330 | fiat_currency_code: str = betterproto.string_field(3) 331 | -------------------------------------------------------------------------------- /walletlib/coinomi_proto.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # sources: coinomiwallet.proto 3 | # plugin: python-betterproto 4 | from dataclasses import dataclass 5 | from typing import List 6 | 7 | import betterproto 8 | 9 | 10 | class KeyType(betterproto.Enum): 11 | ORIGINAL = 1 12 | ENCRYPTED_SCRYPT_AES = 2 13 | DETERMINISTIC_MNEMONIC = 3 14 | DETERMINISTIC_KEY = 4 15 | 16 | 17 | class TransactionConfidenceType(betterproto.Enum): 18 | UNKNOWN = 0 19 | BUILDING = 1 20 | PENDING = 2 21 | DEAD = 3 22 | 23 | 24 | class TransactionConfidenceSource(betterproto.Enum): 25 | SOURCE_UNKNOWN = 0 26 | SOURCE_NETWORK = 1 27 | SOURCE_SELF = 2 28 | SOURCE_TRUSTED = 3 29 | 30 | 31 | class TransactionPool(betterproto.Enum): 32 | UNSPENT = 4 33 | SPENT = 5 34 | DEAD = 10 35 | PENDING = 16 36 | 37 | 38 | class WalletEncryptionType(betterproto.Enum): 39 | UNENCRYPTED = 1 40 | ENCRYPTED_SCRYPT_AES = 2 41 | ENCRYPTED_AES = 3 42 | 43 | 44 | @dataclass 45 | class PeerAddress(betterproto.Message): 46 | ip_address: bytes = betterproto.bytes_field(1) 47 | port: int = betterproto.uint32_field(2) 48 | services: int = betterproto.uint64_field(3) 49 | 50 | 51 | @dataclass 52 | class EncryptedData(betterproto.Message): 53 | initialisation_vector: bytes = betterproto.bytes_field(1) 54 | encrypted_private_key: bytes = betterproto.bytes_field(2) 55 | 56 | 57 | @dataclass 58 | class DeterministicKey(betterproto.Message): 59 | """ 60 | * Data attached to a Key message that defines the data needed by the BIP32 61 | deterministic key hierarchy algorithm. 62 | """ 63 | 64 | # Random data that allows us to extend a key. Without this, we can't figure 65 | # out the next key in the chain and should just treat it as a regular 66 | # ORIGINAL type key. 67 | chain_code: bytes = betterproto.bytes_field(1) 68 | # The path through the key tree. Each number is encoded in the standard form: 69 | # high bit set for private derivation and high bit unset for public 70 | # derivation. 71 | path: List[int] = betterproto.uint32_field(2) 72 | # How many children of this key have been issued, that is, given to the user 73 | # when they requested a fresh key? For the parents of keys being handed out, 74 | # this is always less than the true number of children: the difference is 75 | # called the lookahead zone. These keys are put into Bloom filters so we can 76 | # spot transactions made by clones of this wallet - for instance when 77 | # restoring from backup or if the seed was shared between devices. If this 78 | # field is missing it means we're not issuing subkeys of this key to users. 79 | issued_subkeys: int = betterproto.uint32_field(3) 80 | lookahead_size: int = betterproto.uint32_field(4) 81 | # * Flag indicating that this key is a root of a following chain. This chain 82 | # is following the next non-following chain. Following/followed chains 83 | # concept is used for married keychains, where the set of keys combined 84 | # together to produce a single P2SH multisignature address 85 | is_following: bool = betterproto.bool_field(5) 86 | 87 | 88 | @dataclass 89 | class Key(betterproto.Message): 90 | """ 91 | * A key used to control Bitcoin spending. Either the private key, the 92 | public key or both may be present. It is recommended that if the private 93 | key is provided that the public key is provided too because deriving it is 94 | slow. If only the public key is provided, the key can only be used to watch 95 | the blockchain and verify transactions, and not for spending. 96 | """ 97 | 98 | type: "KeyType" = betterproto.enum_field(1) 99 | # Either the private EC key bytes (without any ASN.1 wrapping), or the 100 | # deterministic root seed. If the secret is encrypted, or this is a "watching 101 | # entry" then this is missing. 102 | secret_bytes: bytes = betterproto.bytes_field(2) 103 | # If the secret data is encrypted, then secret_bytes is missing and this 104 | # field is set. 105 | encrypted_data: "EncryptedData" = betterproto.message_field(3) 106 | # The public EC key derived from the private key. We allow both to be stored 107 | # to avoid mobile clients having to do lots of slow EC math on startup. For 108 | # DETERMINISTIC_MNEMONIC entries this is missing. 109 | public_key: bytes = betterproto.bytes_field(4) 110 | # User-provided label associated with the key. 111 | label: str = betterproto.string_field(5) 112 | deterministic_key: "DeterministicKey" = betterproto.message_field(6) 113 | 114 | 115 | @dataclass 116 | class TransactionInput(betterproto.Message): 117 | # Hash of the transaction this input is using. 118 | transaction_out_point_hash: bytes = betterproto.bytes_field(1) 119 | # Index of transaction output used by this input. 120 | transaction_out_point_index: int = betterproto.uint32_field(2) 121 | # Script that contains the signatures/pubkeys. 122 | script_bytes: bytes = betterproto.bytes_field(3) 123 | # Sequence number. Currently unused, but intended for contracts in future. 124 | sequence: int = betterproto.uint32_field(4) 125 | # Value of connected output, if known 126 | value: int = betterproto.int64_field(5) 127 | 128 | 129 | @dataclass 130 | class TransactionOutput(betterproto.Message): 131 | value: int = betterproto.int64_field(1) 132 | script_bytes: bytes = betterproto.bytes_field(2) 133 | # If spent, the hash of the transaction doing the spend. 134 | spent_by_transaction_hash: bytes = betterproto.bytes_field(3) 135 | # If spent, the index of the transaction input of the transaction doing the 136 | # spend. 137 | spent_by_transaction_index: int = betterproto.int32_field(4) 138 | 139 | 140 | @dataclass 141 | class TransactionConfidence(betterproto.Message): 142 | """ 143 | * A description of the confidence we have that a transaction cannot be 144 | reversed in the future. Parsing should be lenient, since this could change 145 | for different applications yet we should maintain backward compatibility. 146 | """ 147 | 148 | # This is optional in case we add confidence types to prevent parse errors - 149 | # backwards compatible. 150 | type: "TransactionConfidenceType" = betterproto.enum_field(1) 151 | # If type == BUILDING then this is the chain height at which the transaction 152 | # was included. 153 | appeared_at_height: int = betterproto.int32_field(2) 154 | # If set, hash of the transaction that double spent this one into oblivion. A 155 | # transaction can be double spent by multiple transactions in the case of 156 | # several inputs being re-spent by several transactions but we don't bother 157 | # to track them all, just the first. This only makes sense if type = DEAD. 158 | overriding_transaction: bytes = betterproto.bytes_field(3) 159 | # If type == BUILDING then this is the depth of the transaction in the 160 | # blockchain. Zero confirmations: depth = 0, one confirmation: depth = 1 161 | # etc. 162 | depth: int = betterproto.int32_field(4) 163 | broadcast_by: List["PeerAddress"] = betterproto.message_field(5) 164 | source: "TransactionConfidenceSource" = betterproto.enum_field(6) 165 | 166 | 167 | @dataclass 168 | class Transaction(betterproto.Message): 169 | # See Wallet.java for detailed description of pool semantics 170 | version: int = betterproto.int32_field(1) 171 | time: int = betterproto.int32_field(11) 172 | hash: bytes = betterproto.bytes_field(2) 173 | # If pool is not present, that means either: - This Transaction is either 174 | # not in a wallet at all (the proto is re-used elsewhere) - Or it is stored 175 | # but for other purposes, for example, because it is the overriding 176 | # transaction of a double spend. - Or the Pool enum got a new value which 177 | # your software is too old to parse. 178 | pool: "TransactionPool" = betterproto.enum_field(3) 179 | lock_time: int = betterproto.uint32_field(4) 180 | updated_at: int = betterproto.int64_field(5) 181 | transaction_input: List["TransactionInput"] = betterproto.message_field(6) 182 | transaction_output: List["TransactionOutput"] = betterproto.message_field( 183 | 7) 184 | # A list of blocks in which the transaction has been observed (on any chain). 185 | # Also, a number used to disambiguate ordering within a block. 186 | block_hash: List[bytes] = betterproto.bytes_field(8) 187 | block_relativity_offsets: List[int] = betterproto.int32_field(9) 188 | # Data describing where the transaction is in the chain. 189 | confidence: "TransactionConfidence" = betterproto.message_field(10) 190 | token_id: int = betterproto.int32_field(12) 191 | 192 | 193 | @dataclass 194 | class AddressStatus(betterproto.Message): 195 | address: str = betterproto.string_field(1) 196 | status: str = betterproto.string_field(2) 197 | 198 | 199 | @dataclass 200 | class WalletPocket(betterproto.Message): 201 | """* A wallet pocket""" 202 | 203 | network_identifier: str = betterproto.string_field(1) 204 | # A UTF8 encoded text description of the wallet that is intended for end user 205 | # provided text. 206 | description: str = betterproto.string_field(2) 207 | key: List["Key"] = betterproto.message_field(3) 208 | # The SHA256 hash of the head of the best chain seen by this wallet. 209 | last_seen_block_hash: bytes = betterproto.bytes_field(4) 210 | # The height in the chain of the last seen block. 211 | last_seen_block_height: int = betterproto.uint32_field(5) 212 | last_seen_block_time_secs: int = betterproto.int64_field(6) 213 | transaction: List["Transaction"] = betterproto.message_field(7) 214 | address_status: List["AddressStatus"] = betterproto.message_field(8) 215 | 216 | 217 | @dataclass 218 | class ScryptParameters(betterproto.Message): 219 | """ 220 | * The parameters used in the scrypt key derivation function. The default 221 | values are taken from http://www.tarsnap.com/scrypt/scrypt-slides.pdf. 222 | They can be increased - n is the number of iterations performed and r and 223 | p can be used to tweak the algorithm - see: 224 | http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work- 225 | factors 226 | """ 227 | 228 | salt: bytes = betterproto.bytes_field(1) 229 | n: int = betterproto.int64_field(2) 230 | r: int = betterproto.int32_field(3) 231 | p: int = betterproto.int32_field(4) 232 | 233 | 234 | @dataclass 235 | class Wallet(betterproto.Message): 236 | """* A bitcoin wallet""" 237 | 238 | # The version number of the wallet - used to detect wallets that were 239 | # produced in the future (i.e the wallet may contain some future format this 240 | # protobuf/ code does not know about) 241 | version: int = betterproto.int32_field(1) 242 | seed: "Key" = betterproto.message_field(2) 243 | master_key: "Key" = betterproto.message_field(3) 244 | encryption_type: "WalletEncryptionType" = betterproto.enum_field(4) 245 | encryption_parameters: "ScryptParameters" = betterproto.message_field(5) 246 | pockets: List["WalletPocket"] = betterproto.message_field(6) 247 | -------------------------------------------------------------------------------- /walletlib/crypto.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from Crypto.Cipher import AES 3 | 4 | 5 | def doublesha256(bytestring): 6 | return hashlib.sha256(hashlib.sha256(bytestring).digest()).digest() 7 | 8 | 9 | def doublesha256_checksum(bytestring): 10 | return doublesha256(bytestring)[:4] 11 | 12 | 13 | def ripemd160_sha256(key): 14 | return hashlib.new("ripemd160", hashlib.sha256(key).digest()).digest() 15 | 16 | 17 | class Crypter(object): 18 | 19 | def __init__(self): 20 | self.chKey = None 21 | self.chIV = None 22 | 23 | @staticmethod 24 | def append_PKCS7_padding(s): 25 | """return s padded to a multiple of 16-bytes by PKCS7 padding""" 26 | numpads = 16 - (len(s) % 16) 27 | return s + numpads * chr(numpads) 28 | 29 | def keyfrompassphrase( 30 | self, vKeyData, vSalt, nDerivIterations, nDerivationMethod 31 | ): 32 | if nDerivationMethod != 0: 33 | return 0 34 | data = vKeyData + vSalt 35 | for i in range(nDerivIterations): 36 | data = hashlib.sha512(data).digest() 37 | self.SetKey(data[0:32]) 38 | self.SetIV(data[32: 32 + 16]) 39 | return len(data) 40 | 41 | def SetKey(self, key): 42 | self.chKey = key 43 | 44 | def SetIV(self, iv): 45 | self.chIV = iv[0:16] 46 | 47 | def encrypt(self, data): 48 | return AES.new(self.chKey, AES.MODE_CBC, self.chIV).encrypt( 49 | Crypter.append_PKCS7_padding(data) 50 | ) 51 | 52 | def decrypt(self, data): 53 | return AES.new(self.chKey, AES.MODE_CBC, self.chIV).decrypt(data)[0:32] 54 | -------------------------------------------------------------------------------- /walletlib/exceptions.py: -------------------------------------------------------------------------------- 1 | class WalletDatError(Exception): 2 | 3 | def __init__(self, file=None, message=None): 4 | if message is None: 5 | message = "Error processing {}".format(file) 6 | super(WalletDatError, self).__init__(message) 7 | self.file = file 8 | 9 | 10 | class SerializationError(WalletDatError): 11 | pass 12 | 13 | 14 | class DatabaseError(WalletDatError): 15 | pass 16 | 17 | 18 | class KeypairError(WalletDatError): 19 | pass 20 | 21 | 22 | class PasswordError(WalletDatError): 23 | pass 24 | -------------------------------------------------------------------------------- /walletlib/protobufwallet.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from typing import List, Union, Any, Optional, Dict 3 | from pathlib import PurePath 4 | from .bitcoinj_compat import * 5 | import hashlib 6 | import arrow 7 | from coincurve import PrivateKey, PublicKey 8 | import base58 9 | import ipaddress 10 | import json 11 | import base64 12 | from .crypto import ripemd160_sha256 13 | from .exceptions import SerializationError, PasswordError 14 | from hashlib import md5 15 | 16 | 17 | class ProtobufWallet(object): 18 | 19 | def __init__(self, data: Any) -> None: 20 | self.raw_data = data 21 | self.wallet = Wallet() 22 | self.default_wifnetwork = 0 23 | self.keypairs = [] 24 | self.description = "" 25 | self.pool = [] 26 | self.txes = [] 27 | self.mnemonics = [] 28 | self.bestblock = {} 29 | self.tags = [] 30 | self.extensions = [] 31 | self.lastblockhash = None 32 | self.encryption_params = None 33 | self.encrypted = False 34 | 35 | @classmethod 36 | def load(cls, filename: Union[str, PurePath]): 37 | with open(filename, "rb") as d: 38 | data = d.read() 39 | if b'org.' in data: 40 | return cls(data) 41 | else: 42 | data = base64.b64decode(data) 43 | 44 | return cls(data) 45 | 46 | def parse(self, passphrase: Optional[str] = None): 47 | if self.raw_data[:8] == b'Salted__': 48 | if passphrase is None: 49 | raise PasswordError 50 | else: 51 | salt = self.raw_data[8:16] 52 | newdata = b'' 53 | tmp2 = b'' 54 | tmp = passphrase.encode() + salt 55 | while len(newdata) < 32 + AES.block_size: 56 | msg = tmp2 + tmp 57 | tmp2 = md5(msg).digest() 58 | newdata += tmp2 59 | key = newdata[:32] 60 | iv = newdata[32:32 + AES.block_size] 61 | cipher = AES.new(key, AES.MODE_CBC, iv) 62 | try: 63 | padded_plain = cipher.decrypt( 64 | self.raw_data[AES.block_size:]) 65 | pad_len = padded_plain[-1] 66 | if isinstance(pad_len, str): 67 | pad_len = ord(pad_len) 68 | plain_data = padded_plain[:-pad_len] 69 | self.wallet.parse(plain_data) 70 | except BaseException: 71 | raise PasswordError 72 | else: 73 | self.wallet.parse(self.raw_data) 74 | 75 | if self.wallet.network_identifier == "org.dogecoin.production": 76 | self.default_wifnetwork = 30 77 | elif self.wallet.network_identifier == "org.bitcoin.production": 78 | self.default_wifnetwork = 0 79 | elif self.wallet.network_identifier == "org.litecoin.production": 80 | self.default_wifnetwork = 48 81 | elif self.wallet.network_identifier == "org.feathercoin.production": 82 | self.default_wifnetwork = 14 83 | else: 84 | self.default_wifnetwork = 0 85 | """ 86 | Not sure what other variants have been made into bitcoinj-compatible 87 | """ 88 | self.encryption_params = self.wallet.encryption_parameters.to_dict() 89 | if self.wallet.encryption_type > 1: 90 | self.encrypted = True 91 | if passphrase is None: 92 | for k in self.wallet.key: 93 | if k.type == 3: 94 | mnemonic = k.secret_bytes.decode() 95 | deterministic_seed = k.deterministic_seed.hex() 96 | self.mnemonics.append( 97 | {"mnemonic": mnemonic, "deterministic_seed": deterministic_seed}) 98 | elif k.type == 4 or k.type == 1: 99 | creation_timestamp = arrow.get( 100 | int(k.creation_timestamp)).isoformat() 101 | keydict = {"creation_timestamp": creation_timestamp} 102 | if k.type == 4: 103 | deterministic_key = k.deterministic_key.to_dict() 104 | deterministic_key["chainCode"] = base64.b64decode( 105 | deterministic_key['chainCode']).hex() 106 | keydict["deterministic_key"] = deterministic_key 107 | prefix = bytes([self.default_wifnetwork]) 108 | pubkey = base58.b58encode_check( 109 | prefix + ripemd160_sha256(k.public_key)).decode() 110 | keydict["pubkey"] = pubkey 111 | if len(k.secret_bytes) > 0: 112 | privkey_prefix = int.to_bytes( 113 | self.default_wifnetwork + 128, 1, byteorder='big') 114 | compressed = base58.b58encode_check( 115 | privkey_prefix + k.secret_bytes).decode() 116 | uncompressed = base58.b58encode_check( 117 | privkey_prefix + k.secret_bytes + b'\x01').decode() 118 | keydict["compressed_private_key"] = compressed 119 | keydict["uncompressed_private_key"] = uncompressed 120 | keydict["label"] = k.label 121 | self.keypairs.append(keydict) 122 | 123 | if hasattr(self.wallet, 'lastSeenBlockHash'): 124 | self.lastblockhash = self.wallet.last_seen_block_hash.hex() 125 | if hasattr(self.wallet, 'transaction'): 126 | for t in self.wallet.transaction: 127 | txjson = t.to_json() 128 | print(txjson) 129 | tx = t.to_dict() 130 | tx["hash"] = base64.b64decode(tx['hash']).hex() 131 | tx["updatedAt"] = arrow.get( 132 | int(tx["updatedAt"])).isoformat() 133 | txinputs = [] 134 | for txinput in tx['transactionInput']: 135 | inputdict = {} 136 | if 'transactionOutPointHash' in txinput.keys(): 137 | inputdict['transactionOutPointHash'] = base64.b64decode( 138 | txinput['transactionOutPointHash']).hex() 139 | if 'transactionOutPointIndex' in txinput.keys(): 140 | inputdict['transactionOutPointIndex'] = txinput['transactionOutPointIndex'] 141 | if 'scriptBytes' in txinput.keys(): 142 | inputdict['scriptBytes'] = base64.b64decode( 143 | txinput['scriptBytes']).hex() 144 | txinputs.append(inputdict) 145 | tx['transactionInput'] = txinputs 146 | txoutputs = [] 147 | for txoutput in tx['transactionOutput']: 148 | outputdict = {} 149 | if 'spentByTransactionHash' in txoutput.keys(): 150 | outputdict['spentByTransactionHash'] = base64.b64decode( 151 | txoutput['spentByTransactionHash']).hex() 152 | if 'spentByTransactionIndex' in txoutput.keys(): 153 | outputdict['spentByTransactionIndex'] = txoutput['spentByTransactionIndex'] 154 | if 'scriptBytes' in txoutput.keys(): 155 | outputdict['scriptBytes'] = base64.b64decode( 156 | txoutput['scriptBytes']).hex() 157 | if 'value' in txoutput.keys(): 158 | outputdict['value'] = txoutput['value'] 159 | txoutputs.append(outputdict) 160 | tx['transactionOutput'] = txoutputs 161 | bhashes = [] 162 | for bhash in tx['blockHash']: 163 | bhashes.append(base64.b64decode(bhash).hex()) 164 | tx['blockHash'] = bhashes 165 | self.txes.append(tx) 166 | self.description = self.wallet.description 167 | self.extensions = self.wallet.extension 168 | self.tags = self.wallet.tags 169 | else: 170 | self.keypairs = self.wallet.to_dict()['key'] 171 | 172 | def dump_all(self, filepath: Optional[str] = None) -> Dict: 173 | output_items = { 174 | "keys": self.keypairs, 175 | "tx": self.txes, 176 | "mnemonics": self.mnemonics, 177 | "description": self.description} 178 | if filepath is not None: 179 | with open(filepath, "a") as ft: 180 | ft.write(json.dumps(output_items, sort_keys=True, indent=4)) 181 | return output_items 182 | 183 | def dump_keys(self, filepath: Optional[str] = None) -> List: 184 | output_keys = list(self.mnemonics) 185 | output_keys.extend(iter(self.keypairs)) 186 | if filepath is not None: 187 | with open(filepath, "a") as ft: 188 | ft.write(json.dumps(output_keys, sort_keys=True, indent=4)) 189 | return output_keys 190 | -------------------------------------------------------------------------------- /walletlib/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimtje/walletlib/73032d226bf5d8871a6604e1b1c2216c316ea4a1/walletlib/scripts/__init__.py -------------------------------------------------------------------------------- /walletlib/scripts/dumpwallet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """CLI Interface to Walletlib 5 | 6 | This is a simple implementation to allow walletlib to be used from the cli. 7 | It will certainly gain more features as they are added. Currently it takes a wallet.dat file and dumps either a full 8 | seet of its contents or just the keys out. 9 | 10 | """ 11 | import click 12 | from walletlib import Walletdat, ProtobufWallet 13 | import json 14 | 15 | 16 | @click.command() 17 | @click.argument("filename", type=click.Path(exists=True)) 18 | @click.option("-p", "--password", help="Password if any", type=click.STRING) 19 | @click.option("-o", "--output", 20 | help="File to save to. If not set, results only will be displayed") 21 | @click.option( 22 | "-v", 23 | "--versionprefix", 24 | type=int, 25 | help="Force output to use this p2pkh version byte", 26 | ) 27 | @click.option("-s", "--secretprefix", type=int, 28 | help="Force output to use this WIF version byte") 29 | @click.option("--keys", is_flag=True, help="Only dump keys.") 30 | def main(filename, password, output, versionprefix, secretprefix, keys): 31 | if filename.endswith(".dat"): 32 | w = Walletdat.load(filename) 33 | click.echo("Loaded file") 34 | if password: 35 | w.parse(passphrase=str(password)) 36 | else: 37 | w.parse() 38 | click.echo("Found {} keypairs and {} transactions".format( 39 | len(w.keypairs), len(w.txes))) 40 | click.echo("Default version byte: {}".format(w.default_wifnetwork)) 41 | if keys: 42 | if not output: 43 | d = w.dump_keys( 44 | version=versionprefix, 45 | privkey_prefix_override=secretprefix) 46 | click.echo(json.dumps(d, sort_keys=True, indent=4)) 47 | else: 48 | w.dump_keys( 49 | output, 50 | version=versionprefix, 51 | privkey_prefix_override=secretprefix) 52 | else: 53 | if not output: 54 | d = w.dump_all( 55 | version=versionprefix, 56 | privkey_prefix_override=secretprefix) 57 | click.echo(json.dumps(d, sort_keys=True, indent=4)) 58 | else: 59 | w.dump_all( 60 | output, 61 | version=versionprefix, 62 | privkey_prefix_override=secretprefix) 63 | click.echo("Done") 64 | else: 65 | try: 66 | w = ProtobufWallet.load(filename) 67 | click.echo("Loaded file") 68 | if password: 69 | w.parse(passphrase=str(password)) 70 | else: 71 | w.parse() 72 | click.echo("Found {} keypairs and {} transactions".format( 73 | len(w.keypairs), len(w.txes))) 74 | click.echo("Default version byte: {}".format(w.default_wifnetwork)) 75 | if keys: 76 | if not output: 77 | d = w.dump_keys() 78 | click.echo(json.dumps(d, sort_keys=True, indent=4)) 79 | else: 80 | w.dump_keys(output) 81 | else: 82 | if not output: 83 | d = w.dump_all() 84 | click.echo(json.dumps(d, sort_keys=True, indent=4)) 85 | else: 86 | w.dump_all(output) 87 | click.echo("Done") 88 | except BaseException: 89 | click.echo("Error, cannot read wallet file") 90 | -------------------------------------------------------------------------------- /walletlib/utils.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | from .exceptions import * 4 | 5 | 6 | class BCDataStream(object): 7 | """BCDataStream from pywallet""" 8 | 9 | def __init__(self, input): 10 | self.input = bytes(input) 11 | self.read_cursor = 0 12 | 13 | def read_string(self): 14 | # Strings are encoded depending on length: 15 | # 0 to 252 : 1-byte-length followed by bytes (if any) 16 | # 253 to 65,535 : byte'253' 2-byte-length followed by bytes 17 | # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes 18 | # ... and the Bitcoin client is coded to understand: 19 | # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string 20 | # ... but I don't think it actually handles any strings that big. 21 | try: 22 | length = self.read_compact_size() 23 | except IndexError: 24 | raise SerializationError("attempt to read past end of buffer") 25 | 26 | return self.read_bytes(length) 27 | 28 | def read_bytes(self, length): 29 | try: 30 | result = self.input[self.read_cursor: self.read_cursor + length] 31 | self.read_cursor += length 32 | return result 33 | except IndexError: 34 | raise SerializationError("attempt to read past end of buffer") 35 | 36 | def read_boolean(self): 37 | return self.read_bytes(1)[0] != chr(0) 38 | 39 | def read_int16(self): 40 | return self._read_num(" bytes: 118 | if len(privkey) == 279: 119 | return privkey[9:41] 120 | else: 121 | return privkey[8:40] 122 | -------------------------------------------------------------------------------- /walletlib/wallet_segwit.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # sources: wallet_segwit.proto 3 | # plugin: python-betterproto 4 | from dataclasses import dataclass 5 | from typing import List 6 | 7 | import betterproto 8 | 9 | 10 | class KeyType(betterproto.Enum): 11 | ORIGINAL = 1 12 | ENCRYPTED_SCRYPT_AES = 2 13 | DETERMINISTIC_MNEMONIC = 3 14 | DETERMINISTIC_KEY = 4 15 | 16 | 17 | class KeyOutputScriptType(betterproto.Enum): 18 | P2PKH = 1 19 | P2WPKH = 2 20 | 21 | 22 | class TransactionConfidenceType(betterproto.Enum): 23 | UNKNOWN = 0 24 | BUILDING = 1 25 | PENDING = 2 26 | NOT_IN_BEST_CHAIN = 3 27 | DEAD = 4 28 | IN_CONFLICT = 5 29 | 30 | 31 | class TransactionConfidenceSource(betterproto.Enum): 32 | SOURCE_UNKNOWN = 0 33 | SOURCE_NETWORK = 1 34 | SOURCE_SELF = 2 35 | 36 | 37 | class TransactionPool(betterproto.Enum): 38 | UNSPENT = 4 39 | SPENT = 5 40 | INACTIVE = 2 41 | DEAD = 10 42 | PENDING = 16 43 | PENDING_INACTIVE = 18 44 | 45 | 46 | class TransactionPurpose(betterproto.Enum): 47 | UNKNOWN = 0 48 | USER_PAYMENT = 1 49 | KEY_ROTATION = 2 50 | ASSURANCE_CONTRACT_CLAIM = 3 51 | ASSURANCE_CONTRACT_PLEDGE = 4 52 | ASSURANCE_CONTRACT_STUB = 5 53 | RAISE_FEE = 6 54 | 55 | 56 | class WalletEncryptionType(betterproto.Enum): 57 | UNENCRYPTED = 1 58 | ENCRYPTED_SCRYPT_AES = 2 59 | 60 | 61 | @dataclass 62 | class PeerAddress(betterproto.Message): 63 | ip_address: bytes = betterproto.bytes_field(1) 64 | port: int = betterproto.uint32_field(2) 65 | services: int = betterproto.uint64_field(3) 66 | 67 | 68 | @dataclass 69 | class EncryptedData(betterproto.Message): 70 | initialisation_vector: bytes = betterproto.bytes_field(1) 71 | encrypted_private_key: bytes = betterproto.bytes_field(2) 72 | 73 | 74 | @dataclass 75 | class DeterministicKey(betterproto.Message): 76 | """ 77 | * Data attached to a Key message that defines the data needed by the BIP32 78 | deterministic key hierarchy algorithm. 79 | """ 80 | 81 | # Random data that allows us to extend a key. Without this, we can't figure 82 | # out the next key in the chain and should just treat it as a regular 83 | # ORIGINAL type key. 84 | chain_code: bytes = betterproto.bytes_field(1) 85 | # The path through the key tree. Each number is encoded in the standard form: 86 | # high bit set for private derivation and high bit unset for public 87 | # derivation. 88 | path: List[int] = betterproto.uint32_field(2) 89 | # How many children of this key have been issued, that is, given to the user 90 | # when they requested a fresh key? For the parents of keys being handed out, 91 | # this is always less than the true number of children: the difference is 92 | # called the lookahead zone. These keys are put into Bloom filters so we can 93 | # spot transactions made by clones of this wallet - for instance when 94 | # restoring from backup or if the seed was shared between devices. If this 95 | # field is missing it means we're not issuing subkeys of this key to users. 96 | issued_subkeys: int = betterproto.uint32_field(3) 97 | lookahead_size: int = betterproto.uint32_field(4) 98 | # * Flag indicating that this key is a root of a following chain. This chain 99 | # is following the next non-following chain. Following/followed chains 100 | # concept is used for married keychains, where the set of keys combined 101 | # together to produce a single P2SH multisignature address 102 | is_following: bool = betterproto.bool_field(5) 103 | # Number of signatures required to spend. This field is needed only for 104 | # married keychains to reconstruct KeyChain and represents the N value from 105 | # N-of-M CHECKMULTISIG script. For regular single keychains it will always be 106 | # 1. 107 | sigs_required_to_spend: int = betterproto.uint32_field(6) 108 | 109 | 110 | @dataclass 111 | class Key(betterproto.Message): 112 | """ 113 | * A key used to control Bitcoin spending. Either the private key, the 114 | public key or both may be present. It is recommended that if the private 115 | key is provided that the public key is provided too because deriving it is 116 | slow. If only the public key is provided, the key can only be used to watch 117 | the blockchain and verify transactions, and not for spending. 118 | """ 119 | 120 | type: "KeyType" = betterproto.enum_field(1) 121 | # Either the private EC key bytes (without any ASN.1 wrapping), or the 122 | # deterministic root seed. If the secret is encrypted, or this is a "watching 123 | # entry" then this is missing. 124 | secret_bytes: bytes = betterproto.bytes_field(2) 125 | # If the secret data is encrypted, then secret_bytes is missing and this 126 | # field is set. 127 | encrypted_data: "EncryptedData" = betterproto.message_field(6) 128 | # The public EC key derived from the private key. We allow both to be stored 129 | # to avoid mobile clients having to do lots of slow EC math on startup. For 130 | # DETERMINISTIC_MNEMONIC entries this is missing. 131 | public_key: bytes = betterproto.bytes_field(3) 132 | # User-provided label associated with the key. 133 | label: str = betterproto.string_field(4) 134 | # Timestamp stored as millis since epoch. Useful for skipping block bodies 135 | # before this point. The reason it's optional is that keys derived from a 136 | # parent don't have this data. 137 | creation_timestamp: int = betterproto.int64_field(5) 138 | deterministic_key: "DeterministicKey" = betterproto.message_field(7) 139 | # The seed for a deterministic key hierarchy. Derived from the mnemonic, but 140 | # cached here for quick startup. Only applicable to a DETERMINISTIC_MNEMONIC 141 | # key entry. 142 | deterministic_seed: bytes = betterproto.bytes_field(8) 143 | # Encrypted version of the seed 144 | encrypted_deterministic_seed: "EncryptedData" = betterproto.message_field( 145 | 9) 146 | # The path to the root. Only applicable to a DETERMINISTIC_MNEMONIC key 147 | # entry. 148 | account_path: List[int] = betterproto.uint32_field(10) 149 | # Type of addresses (aka output scripts) to generate for receiving. 150 | output_script_type: "KeyOutputScriptType" = betterproto.enum_field(11) 151 | 152 | 153 | @dataclass 154 | class Script(betterproto.Message): 155 | program: bytes = betterproto.bytes_field(1) 156 | # Timestamp stored as millis since epoch. Useful for skipping block bodies 157 | # before this point when watching for scripts on the blockchain. 158 | creation_timestamp: int = betterproto.int64_field(2) 159 | 160 | 161 | @dataclass 162 | class ScriptWitness(betterproto.Message): 163 | data: List[bytes] = betterproto.bytes_field(1) 164 | 165 | 166 | @dataclass 167 | class TransactionInput(betterproto.Message): 168 | # Hash of the transaction this input is using. 169 | transaction_out_point_hash: bytes = betterproto.bytes_field(1) 170 | # Index of transaction output used by this input. 171 | transaction_out_point_index: int = betterproto.uint32_field(2) 172 | # Script that contains the signatures/pubkeys. 173 | script_bytes: bytes = betterproto.bytes_field(3) 174 | # Sequence number. 175 | sequence: int = betterproto.uint32_field(4) 176 | # Value of connected output, if known 177 | value: int = betterproto.int64_field(5) 178 | # script witness 179 | witness: "ScriptWitness" = betterproto.message_field(6) 180 | 181 | 182 | @dataclass 183 | class TransactionOutput(betterproto.Message): 184 | value: int = betterproto.int64_field(1) 185 | script_bytes: bytes = betterproto.bytes_field(2) 186 | # If spent, the hash of the transaction doing the spend. 187 | spent_by_transaction_hash: bytes = betterproto.bytes_field(3) 188 | # If spent, the index of the transaction input of the transaction doing the 189 | # spend. 190 | spent_by_transaction_index: int = betterproto.int32_field(4) 191 | 192 | 193 | @dataclass 194 | class TransactionConfidence(betterproto.Message): 195 | """ 196 | * A description of the confidence we have that a transaction cannot be 197 | reversed in the future. Parsing should be lenient, since this could change 198 | for different applications yet we should maintain backward compatibility. 199 | """ 200 | 201 | # This is optional in case we add confidence types to prevent parse errors - 202 | # backwards compatible. 203 | type: "TransactionConfidenceType" = betterproto.enum_field(1) 204 | # If type == BUILDING then this is the chain height at which the transaction 205 | # was included. 206 | appeared_at_height: int = betterproto.int32_field(2) 207 | # If set, hash of the transaction that double spent this one into oblivion. A 208 | # transaction can be double spent by multiple transactions in the case of 209 | # several inputs being re-spent by several transactions but we don't bother 210 | # to track them all, just the first. This only makes sense if type = DEAD. 211 | overriding_transaction: bytes = betterproto.bytes_field(3) 212 | # If type == BUILDING then this is the depth of the transaction in the 213 | # blockchain. Zero confirmations: depth = 0, one confirmation: depth = 1 214 | # etc. 215 | depth: int = betterproto.int32_field(4) 216 | broadcast_by: List["PeerAddress"] = betterproto.message_field(6) 217 | # Millis since epoch the transaction was last announced to us. 218 | last_broadcasted_at: int = betterproto.int64_field(8) 219 | source: "TransactionConfidenceSource" = betterproto.enum_field(7) 220 | 221 | 222 | @dataclass 223 | class Transaction(betterproto.Message): 224 | # See Wallet.java for detailed description of pool semantics 225 | version: int = betterproto.int32_field(1) 226 | hash: bytes = betterproto.bytes_field(2) 227 | # If pool is not present, that means either: - This Transaction is either 228 | # not in a wallet at all (the proto is re-used elsewhere) - Or it is stored 229 | # but for other purposes, for example, because it is the overriding 230 | # transaction of a double spend. - Or the Pool enum got a new value which 231 | # your software is too old to parse. 232 | pool: "TransactionPool" = betterproto.enum_field(3) 233 | lock_time: int = betterproto.uint32_field(4) 234 | updated_at: int = betterproto.int64_field(5) 235 | transaction_input: List["TransactionInput"] = betterproto.message_field(6) 236 | transaction_output: List["TransactionOutput"] = betterproto.message_field( 237 | 7) 238 | # A list of blocks in which the transaction has been observed (on any chain). 239 | # Also, a number used to disambiguate ordering within a block. 240 | block_hash: List[bytes] = betterproto.bytes_field(8) 241 | block_relativity_offsets: List[int] = betterproto.int32_field(11) 242 | # Data describing where the transaction is in the chain. 243 | confidence: "TransactionConfidence" = betterproto.message_field(9) 244 | purpose: "TransactionPurpose" = betterproto.enum_field(10) 245 | # Exchange rate that was valid when the transaction was sent. 246 | exchange_rate: "ExchangeRate" = betterproto.message_field(12) 247 | # Memo of the transaction. It can be used to record the memo of the payment 248 | # request that initiated the transaction. 249 | memo: str = betterproto.string_field(13) 250 | 251 | 252 | @dataclass 253 | class ScryptParameters(betterproto.Message): 254 | """ 255 | * The parameters used in the scrypt key derivation function. The default 256 | values are taken from http://www.tarsnap.com/scrypt/scrypt-slides.pdf. 257 | They can be increased - n is the number of iterations performed and r and 258 | p can be used to tweak the algorithm - see: 259 | http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work- 260 | factors 261 | """ 262 | 263 | salt: bytes = betterproto.bytes_field(1) 264 | n: int = betterproto.int64_field(2) 265 | r: int = betterproto.int32_field(3) 266 | p: int = betterproto.int32_field(4) 267 | 268 | 269 | @dataclass 270 | class Extension(betterproto.Message): 271 | """* An extension to the wallet""" 272 | 273 | id: str = betterproto.string_field(1) 274 | data: bytes = betterproto.bytes_field(2) 275 | # If we do not understand a mandatory extension, abort to prevent data loss. 276 | # For example, this could be applied to a new type of holding, such as a 277 | # contract, where dropping of an extension in a read/write cycle could cause 278 | # loss of value. 279 | mandatory: bool = betterproto.bool_field(3) 280 | 281 | 282 | @dataclass 283 | class Tag(betterproto.Message): 284 | """ 285 | * A simple key->value mapping that has no interpreted content at all. A bit 286 | like the extensions mechanism except an extension is keyed by the ID of a 287 | piece of code that's loaded with the given data, and has the concept of 288 | being mandatory if that code isn't found. Whereas this is just a blind 289 | key/value store. 290 | """ 291 | 292 | tag: str = betterproto.string_field(1) 293 | data: bytes = betterproto.bytes_field(2) 294 | 295 | 296 | @dataclass 297 | class Wallet(betterproto.Message): 298 | """* A bitcoin wallet""" 299 | 300 | network_identifier: str = betterproto.string_field(1) 301 | # The SHA256 hash of the head of the best chain seen by this wallet. 302 | last_seen_block_hash: bytes = betterproto.bytes_field(2) 303 | # The height in the chain of the last seen block. 304 | last_seen_block_height: int = betterproto.uint32_field(12) 305 | last_seen_block_time_secs: int = betterproto.int64_field(14) 306 | key: List["Key"] = betterproto.message_field(3) 307 | transaction: List["Transaction"] = betterproto.message_field(4) 308 | watched_script: List["Script"] = betterproto.message_field(15) 309 | encryption_type: "WalletEncryptionType" = betterproto.enum_field(5) 310 | encryption_parameters: "ScryptParameters" = betterproto.message_field(6) 311 | # The version number of the wallet - used to detect wallets that were 312 | # produced in the future (i.e. the wallet may contain some future format this 313 | # protobuf or parser code does not know about). A version that's higher than 314 | # the default is considered from the future. 315 | version: int = betterproto.int32_field(7) 316 | extension: List["Extension"] = betterproto.message_field(10) 317 | # A UTF8 encoded text description of the wallet that is intended for end user 318 | # provided text. 319 | description: str = betterproto.string_field(11) 320 | # UNIX time in seconds since the epoch. If set, then any keys created before 321 | # this date are assumed to be no longer wanted. Money sent to them will be 322 | # re-spent automatically to the first key that was created after this time. 323 | # It can be used to recover a compromised wallet, or just as part of 324 | # preventative defence-in-depth measures. 325 | key_rotation_time: int = betterproto.uint64_field(13) 326 | tags: List["Tag"] = betterproto.message_field(16) 327 | 328 | 329 | @dataclass 330 | class ExchangeRate(betterproto.Message): 331 | """* An exchange rate between Bitcoin and some fiat currency.""" 332 | 333 | # This much of satoshis (1E-8 fractions)… 334 | coin_value: int = betterproto.int64_field(1) 335 | # …is worth this much of fiat (1E-4 fractions). 336 | fiat_value: int = betterproto.int64_field(2) 337 | # ISO 4217 currency code (if available) of the fiat currency. 338 | fiat_currency_code: str = betterproto.string_field(3) 339 | -------------------------------------------------------------------------------- /walletlib/walletdat.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import datetime 3 | import json 4 | import socket 5 | from typing import Dict, List, Optional 6 | 7 | from bsddb3.db import * 8 | from coincurve import PrivateKey, PublicKey 9 | import base58 10 | from .crypto import Crypter, doublesha256, doublesha256_checksum, ripemd160_sha256 11 | from .exceptions import * 12 | from .utils import BCDataStream, parse_CAddress, parse_BlockLocator, privkey_to_secret 13 | import ipaddress 14 | 15 | 16 | class Walletdat(object): 17 | def __init__(self, db: collections.OrderedDict) -> None: 18 | self.db_parsed = db 19 | self.keypairs = [] 20 | self.rawkeys = [] 21 | self.pool = [] 22 | self.txes = [] 23 | self.keymetas = [] 24 | self.defaultkey = None 25 | self.default_wifnetwork = None 26 | self.addressbook = [] 27 | self.version = 0 28 | self.minversion = 0 29 | self.setting = {} 30 | self.hdchain = {} 31 | self.bestblock = {} 32 | self.destdata = [] 33 | self.orderposnext = 0 34 | self.flags = 0 35 | self.decrypter = Crypter() 36 | self.mkey = None 37 | self.encrypted = False 38 | self.decrypted = True 39 | 40 | @classmethod 41 | def load(cls, filename): 42 | """ 43 | 44 | :param filename: name of the file to load, file should be a BerkeleyDB Wallet.dat file 45 | :type filename: string 46 | :return: cls 47 | :rtype:Walletdat 48 | """ 49 | try: 50 | db = DB() 51 | db.open(filename, "main", DB_BTREE, DB_THREAD | DB_RDONLY) 52 | coll = collections.OrderedDict((k, db[k]) for k in db.keys()) 53 | return cls(coll) 54 | except (DBNoSuchFileError, DBError): 55 | raise DatabaseError(file=filename) 56 | 57 | def parse(self, passphrase: Optional[str] = None) -> None: 58 | """Parse the raw bytes of the db's contents. A bit of a mess right now so API likely to change 59 | 60 | :param passphrase: Passphrase to the wallet 61 | :type passphrase: string 62 | :return: None 63 | :rtype: None 64 | """ 65 | for key, value in self.db_parsed.items(): 66 | kds = BCDataStream(key) 67 | vds = BCDataStream(value) 68 | keytype = kds.read_string().decode() 69 | if keytype == "key": 70 | try: 71 | keypair = KeyPair.parse_fromwallet(kds, vds) 72 | self.keypairs.append(keypair) 73 | except KeypairError: 74 | print( 75 | "Error: Pubkey data doesn't match pubkey derived from private key" 76 | ) 77 | elif keytype == "wkey": 78 | keypair = KeyPair.parse_fromwallet(kds, vds) 79 | created = vds.read_int64() 80 | expires = vds.read_int64() 81 | comment = vds.read_string().decode() 82 | keypair.parse_wkeyinfo(created, expires, comment) 83 | self.keypairs.append(keypair) 84 | elif keytype == "keymeta": 85 | pubkey = kds.read_bytes(kds.read_compact_size()) 86 | if len(pubkey) == 33: 87 | compressed = True 88 | else: 89 | compressed = False 90 | version = vds.read_int32() 91 | createtime = vds.read_int64() 92 | if version != 10: 93 | hdkeypath = "No HD Key Found" 94 | hdmasterkey = None 95 | else: 96 | hdkeypath = vds.read_string().decode("utf-8") 97 | hdmasterkey = vds.read_bytes(20).hex() 98 | if version != 12: 99 | fingerprint = None 100 | has_keyorigin = False 101 | else: 102 | fingerprint = vds.read_uint32() 103 | has_keyorigin = vds.read_boolean() 104 | self.keymetas.append( 105 | { 106 | "version": version, "createtime": createtime, "hdkeypath": hdkeypath, 107 | "hdmasterkey": hdmasterkey, "fingerprint": fingerprint, "has_keyorigin": has_keyorigin, 108 | } 109 | ) 110 | for key in self.keypairs: 111 | if key.publickey == PublicKey( 112 | pubkey).format(compressed=compressed): 113 | key.set_keymeta( 114 | version, 115 | createtime, 116 | hdkeypath, 117 | hdmasterkey, 118 | fingerprint, 119 | has_keyorigin, 120 | ) 121 | elif keytype == "defaultkey": 122 | pk = vds.read_bytes(vds.read_compact_size()) 123 | if len(pk) == 33: 124 | self.defaultkey = PublicKey(pk).format() 125 | else: 126 | self.defaultkey = PublicKey(pk).format(compressed=False) 127 | elif keytype == "name": 128 | if len(self.addressbook) > 0: 129 | addr = kds.read_string().decode("utf-8") 130 | if any( 131 | item["address"] == addr for item in self.addressbook): 132 | for item in self.addressbook: 133 | if item["address"] == addr: 134 | item.update( 135 | {"label": vds.read_string().decode("utf-8")} 136 | ) 137 | else: 138 | self.addressbook.append( 139 | { 140 | "address": addr, 141 | "label": vds.read_string().decode("utf-8"), 142 | } 143 | ) 144 | else: 145 | addr = kds.read_string().decode("utf-8") 146 | self.addressbook.append( 147 | {"address": addr, "label": vds.read_string().decode("utf-8")} 148 | ) 149 | if ord(base58.b58decode_check(addr)[:1]) == 5: 150 | self.default_wifnetwork = 0 151 | else: 152 | self.default_wifnetwork = ord(base58.b58decode_check(addr)[:1]) 153 | elif type == "purpose": 154 | if len(self.addressbook) > 0: 155 | addr = kds.read_string().decode("utf-8") 156 | if any( 157 | item["address"] == addr for item in self.addressbook): 158 | for item in self.addressbook: 159 | if item["address"] == addr: 160 | item.update( 161 | {"purpose": vds.read_string().decode("utf-8")} 162 | ) 163 | else: 164 | self.addressbook.append( 165 | { 166 | "address": addr, 167 | "purpose": vds.read_string().decode("utf-8"), 168 | } 169 | ) 170 | else: 171 | addr = kds.read_string().decode("utf-8") 172 | self.addressbook.append( 173 | {"address": addr, "purpose": vds.read_string().decode("utf-8")} 174 | ) 175 | 176 | elif keytype == "tx": 177 | # todo: add segwit 178 | try: 179 | txid = invert_txid(kds.read_bytes(32)) 180 | self.txes.append(Transaction.parse(txid, vds)) 181 | except BaseException: 182 | pass 183 | elif keytype == "hdchain": 184 | version = vds.read_uint32() 185 | chain_counter = vds.read_uint32() 186 | master_keyid = vds.read_bytes(20).hex() 187 | self.hdchain = { 188 | "version": version, 189 | "chain_counter": chain_counter, 190 | "master_keyid": master_keyid, 191 | } 192 | if version > 2: 193 | self.hdchain["internal_counter"] = vds.read_uint32() 194 | elif keytype == "version": 195 | self.version = vds.read_uint32() 196 | elif keytype == "minversion": 197 | self.minversion = vds.read_uint32() 198 | elif keytype == "setting": 199 | setname = kds.read_string().decode() 200 | if setname[0] == "f": 201 | value = vds.read_boolean() 202 | elif setname == "addrIncoming": 203 | value = str(ipaddress.ip_address(vds.read_bytes(4))) 204 | elif setname.startswith("addr"): 205 | d = {"ip": "0.0.0.0", "port": 0, "nTime": 0} 206 | try: 207 | d["nVersion"] = vds.read_int32() 208 | d["nTime"] = vds.read_uint32() 209 | d["nServices"] = vds.read_uint64() 210 | d["pchReserved"] = vds.read_bytes(12) 211 | d["ip"] = str(ipaddress.ip_address(vds.read_bytes(4))) 212 | d["port"] = vds.read_uint16() 213 | except Exception: 214 | pass 215 | value = d 216 | elif setname == "nTransactionFee": 217 | value = vds.read_int64() 218 | elif setname == "nLimitProcessors": 219 | value = vds.read_int32() 220 | self.setting[setname] = value 221 | elif keytype == "bestblock": 222 | version = vds.read_int32() 223 | hashes = [] 224 | for _ in range(vds.read_compact_size()): 225 | hashes.append(vds.read_bytes(32).hex()) 226 | self.bestblock = {"version": version, "hashes": hashes} 227 | elif keytype == "bestblock_nomerkle": 228 | version = vds.read_int32() 229 | hashes = [] 230 | self.bestblock = {"version": version, "hashes": hashes} 231 | elif keytype == "pool": 232 | n = kds.read_int64() 233 | nversion = vds.read_int32() 234 | ntime = vds.read_int64() 235 | pubkey = vds.read_bytes(vds.read_compact_size()) 236 | if len(pubkey) == 33: 237 | compressed = True 238 | else: 239 | compressed = False 240 | self.pool.append( 241 | { 242 | "n": n, 243 | "nversion": nversion, 244 | "ntime": ntime, 245 | "publickey": PublicKey(pubkey).format(compressed=compressed), 246 | } 247 | ) 248 | elif keytype == "destdata": 249 | publickey = kds.read_string().decode() 250 | key = kds.read_string().decode() 251 | # destination = vds.read_string().decode() 252 | self.destdata.append({"publickey": publickey, "key": key}) 253 | elif keytype == "orderposnext": 254 | self.orderposnext = vds.read_int64() 255 | elif keytype == "flags": 256 | self.flags = vds.read_uint64() 257 | elif keytype == "mkey": 258 | nid = kds.read_uint32() 259 | encrypted_key = vds.read_string() 260 | salt = vds.read_string() 261 | derivationmethod = vds.read_uint32() 262 | derivationiters = vds.read_uint32() 263 | self.mkey = { 264 | "nID": nid, 265 | "encrypted_key": encrypted_key, 266 | "salt": salt, 267 | "derivationmethod": derivationmethod, 268 | "derivationiterations": derivationiters, 269 | } 270 | self.encrypted = True 271 | 272 | elif keytype == "ckey": 273 | publickey = kds.read_bytes(kds.read_compact_size()) 274 | encrypted_privkey = vds.read_bytes(vds.read_compact_size()) 275 | self.rawkeys.append( 276 | {"publickey": publickey, "encrypted_privkey": encrypted_privkey}) 277 | else: 278 | print("{} type not implemented".format(type)) 279 | if self.encrypted: 280 | if passphrase is not None: 281 | self.decrypter.keyfrompassphrase( 282 | bytes( 283 | passphrase, 284 | "utf-8"), 285 | self.mkey["salt"], 286 | self.mkey["derivationiterations"], 287 | self.mkey["derivationmethod"]) 288 | masterkey = self.decrypter.decrypt(self.mkey["encrypted_key"]) 289 | self.decrypter.SetKey(masterkey) 290 | for rawkey in self.rawkeys: 291 | try: 292 | self.decrypter.SetIV(doublesha256(rawkey["publickey"])) 293 | dec = self.decrypter.decrypt( 294 | rawkey["encrypted_privkey"]) 295 | self.keypairs.append( 296 | KeyPair.parse_fromckey( 297 | pubkey=rawkey["publickey"], 298 | privkey=dec, 299 | encryptedkey=rawkey["encrypted_privkey"], 300 | crypted=False, 301 | )) 302 | self.decrypted = True 303 | 304 | except BaseException: 305 | raise PasswordError 306 | else: 307 | print("No passphrase set for encrypted wallet") 308 | self.decrypted = False 309 | for rawkey in self.rawkeys: 310 | self.keypairs.append( 311 | KeyPair.parse_fromckey( 312 | pubkey=rawkey["publickey"], 313 | privkey=None, 314 | encryptedkey=rawkey["encrypted_privkey"], 315 | ) 316 | ) 317 | if self.default_wifnetwork is None: 318 | self.default_wifnetwork = 0 319 | 320 | 321 | def dump_keys( 322 | self, 323 | filepath: Optional[str] = None, 324 | version: Optional[int] = None, 325 | privkey_prefix_override: Optional[int] = None, 326 | compression_override: Optional[bool] = None, 327 | ) -> List: 328 | """ Dump just pubkey:privatekey either as a list, write to a file, or both. 329 | 330 | 331 | :param filepath: The output file. Leave as None to not write to file 332 | :type filepath: string 333 | :param version: Version byte for the p2pkh key being generated. Should be between 0 and 127 334 | :type version: int 335 | :param privkey_prefix_override: WIF Version bytes for secret. Should be between 128 and 255 336 | :type privkey_prefix_override: int 337 | :return: List of dicts with pubkey and privkey in bytesm or if not decrypted, pubkey and encrypted key 338 | :rtype: List 339 | :param compression_override: Compression setting for output keys, use None to automatically determine 340 | :type compression_override: bool 341 | """ 342 | if len(self.keypairs) == 0 and self.db_parsed is not None: 343 | self.parse() 344 | # Run parse to populate if forgot 345 | output_list = [] 346 | prefix = self.default_wifnetwork if version is None else version 347 | for keypair in self.keypairs: 348 | if privkey_prefix_override is not None: 349 | wif_prefix = privkey_prefix_override - 128 350 | else: 351 | wif_prefix = prefix 352 | pkey = keypair.pubkey_towif(prefix) 353 | if keypair.encryptedkey is None: 354 | if compression_override is not None: 355 | priv = keypair.privkey_towif( 356 | wif_prefix, compressed=compression_override 357 | ) 358 | else: 359 | priv = keypair.privkey_towif( 360 | wif_prefix, compressed=keypair.compressed) 361 | output_pair = {"public_key": pkey, "private_key": priv} 362 | else: 363 | output_pair = {"public_key": pkey, 364 | "encrypted_privkey": keypair.encryptedkey.hex()} 365 | output_list.append(output_pair) 366 | if filepath is not None: 367 | with open(filepath, "a") as fq: 368 | if "private_key" in output_pair: 369 | fq.write(pkey.decode() + ":" + priv.decode() + "\n") 370 | else: 371 | fq.write( 372 | pkey.decode() + 373 | ":" + 374 | keypair.encryptedkey.hex() + 375 | "\n") 376 | 377 | return output_list 378 | 379 | def dump_all( 380 | self, 381 | filepath: Optional[str] = None, 382 | version: Optional[int] = None, 383 | privkey_prefix_override: Optional[int] = None, 384 | ) -> Dict: 385 | """ Dump all data from wallet 386 | 387 | :param filepath: The output file. Leave as None to not write to file 388 | :type filepath: String 389 | :param version: Version byte for the p2pkh key being generated. Should be between 0 and 127 390 | :type version: Int 391 | :param privkey_prefix_override: WIF Version byte override value just for the private key 392 | :type privkey_prefix_override: Int 393 | :return: A dict with the following key:values - keys: lists of dicts with compressed_private_key, 394 | public_key, uncompressed_private_key, label, created; pool: list of dicts with keys: n, nversion, ntime, 395 | publickey; tx: list of dicts with keys: txid, txin, txout, locktime; minversion, bestblock, 396 | default_network_version, orderposnext 397 | :rtype: 398 | """ 399 | if len(self.keypairs) == 0 and self.db_parsed is not None: 400 | self.parse() 401 | 402 | structures = { 403 | "keys": [], 404 | "pool": [], 405 | "tx": [], 406 | "minversion": self.minversion, 407 | "version": self.version, 408 | "bestblock": self.bestblock, 409 | "default_network_version": self.default_wifnetwork, 410 | "orderposnext": self.orderposnext, 411 | } 412 | 413 | prefix = self.default_wifnetwork if version is None else version 414 | for keypair in self.keypairs: 415 | pkey = keypair.pubkey_towif(prefix) 416 | if keypair.encryptedkey is None: 417 | if privkey_prefix_override is not None: 418 | wif_prefix = privkey_prefix_override - 128 419 | else: 420 | wif_prefix = prefix 421 | priv_compressed = keypair.privkey_towif( 422 | wif_prefix, compressed=True) 423 | priv_uncompressed = keypair.privkey_towif( 424 | wif_prefix, compressed=False) 425 | keyd = { 426 | "public_key": pkey.decode(), 427 | "compressed_private_key": priv_compressed.decode(), 428 | "uncompressed_private_key": priv_uncompressed.decode(), 429 | } 430 | else: 431 | priv_encrypted = keypair.encryptedkey.hex() 432 | keyd = { 433 | "public_key": pkey.decode(), 434 | "encrypted_private_key": priv_encrypted, 435 | } 436 | if len(self.addressbook) > 0: 437 | for a in self.addressbook: 438 | if a["address"] == pkey.decode(): 439 | keyd["label"] = a["label"] 440 | if "purpose" in a.keys(): 441 | keyd["purpose"] = a["purpose"] 442 | if keypair.createtime > 0: 443 | keyd["created"] = datetime.datetime.utcfromtimestamp( 444 | keypair.createtime 445 | ).isoformat() 446 | structures["keys"].append(keyd) 447 | 448 | for tx in self.txes: 449 | apg = { 450 | "txid": tx.txid, 451 | "txin": tx.txin, 452 | "txout": tx.txout, 453 | "locktime": tx.locktime, 454 | } 455 | structures["tx"].append(apg) 456 | 457 | z = bytes([prefix]) 458 | pools = [{ 459 | "n": p["n"], 460 | "nversion": p["nversion"], 461 | "ntime": datetime.datetime.utcfromtimestamp( 462 | p["ntime"]).isoformat(), 463 | "public_key": base58.b58encode_check( 464 | z + 465 | ripemd160_sha256( 466 | p["publickey"])).decode(), 467 | } for p in self.pool] 468 | sorted(pools, key=lambda i: (i["n"], i["ntime"])) 469 | structures["pool"] = pools 470 | if self.defaultkey is not None: 471 | defkey = base58.b58encode_check(z + ripemd160_sha256(self.defaultkey)).decode() 472 | structures["default_key"] = defkey 473 | 474 | if filepath is not None: 475 | with open(filepath, "a") as fq: 476 | fq.write(json.dumps(structures, sort_keys=True, indent=4)) 477 | return structures 478 | 479 | 480 | class KeyPair(object): 481 | """Keypair object, should not be called directly 482 | 483 | 484 | """ 485 | 486 | def __init__( 487 | self, 488 | rawkey, 489 | rawvalue, 490 | pubkey, 491 | sec, 492 | compressed, 493 | privkey=None, 494 | encryptedkey=None): 495 | self.rawkey = rawkey 496 | self.rawvalue = rawvalue 497 | self.publickey = pubkey 498 | self.privkey = privkey 499 | self.secret = sec 500 | self.version = None 501 | self.createtime = 0 502 | self.hdkeypath = None 503 | self.hdmasterkey = None 504 | self.compressed = compressed 505 | self.fingerprint = None 506 | self.has_keyorigin = False 507 | self.encryptedkey = encryptedkey 508 | self.expiretime = 0 509 | self.comment = "" 510 | 511 | @classmethod 512 | def parse_fromwallet(cls, kds, vds): 513 | """Class method to parse entry from wallet entry 514 | 515 | :param kds: BCDatastream object for keys 516 | :type kds: BCDataStream 517 | :param vds: BCDataStream object for values 518 | :type vds: BCDataStream 519 | :return: KeyPair 520 | :rtype: KeyPair 521 | """ 522 | pubkeyraw = kds.read_bytes(kds.read_compact_size()) 523 | privkeyraw = vds.read_bytes(vds.read_compact_size()) 524 | if len(privkeyraw) == 279: 525 | sec = privkeyraw[9:41] 526 | else: 527 | sec = privkeyraw[8:40] 528 | privkey = PrivateKey(sec) 529 | pubkey = PublicKey(pubkeyraw) 530 | if len(pubkeyraw) == 33: 531 | compress = True 532 | else: 533 | compress = False 534 | if pubkey == privkey.public_key: 535 | pubkey = privkey.public_key.format(compressed=compress) 536 | return cls( 537 | rawkey=pubkeyraw, 538 | rawvalue=privkeyraw, 539 | pubkey=pubkey, 540 | privkey=privkey, 541 | sec=sec, 542 | compressed=compress, 543 | ) 544 | else: 545 | raise KeypairError( 546 | message="Pubkey {} error".format( 547 | pubkey.format( 548 | compressed=compress).hex())) 549 | 550 | def parse_wkeyinfo( 551 | self, 552 | createtime: int, 553 | expiretime: int, 554 | comment: str) -> None: 555 | self.createtime = createtime 556 | self.expiretime = expiretime 557 | self.comment = comment 558 | 559 | @classmethod 560 | def parse_fromckey(cls, pubkey, privkey, encryptedkey, crypted=True): 561 | """Parse keypair from ckey (encrypted) values from wallet 562 | 563 | :param pubkey: 564 | :type pubkey: 565 | :param privkey: 566 | :type privkey: 567 | :param encryptedkey: 568 | :type encryptedkey: 569 | :param crypted: 570 | :type crypted: 571 | :return: 572 | :rtype: 573 | """ 574 | pkey = PublicKey(pubkey) 575 | if len(pubkey) == 33: 576 | compress = True 577 | else: 578 | compress = False 579 | if crypted: 580 | return cls( 581 | rawkey=pubkey, 582 | rawvalue=None, 583 | pubkey=pkey.format(compressed=compress), 584 | sec=None, 585 | encryptedkey=encryptedkey, 586 | compressed=compress, 587 | ) 588 | else: 589 | if len(privkey) == 279: 590 | sec = privkey[9:41] 591 | elif len(privkey) == 278: 592 | sec = privkey[8:40] 593 | else: 594 | sec = privkey 595 | prkey = PrivateKey(sec) 596 | if pkey == prkey.public_key: 597 | pkey = prkey.public_key.format(compressed=compress) 598 | return cls( 599 | rawkey=pubkey, 600 | rawvalue=privkey, 601 | pubkey=pkey, 602 | privkey=prkey, 603 | sec=sec, 604 | compressed=compress, 605 | ) 606 | else: 607 | print("Wrong decryption password") 608 | return cls( 609 | rawkey=pubkey, 610 | rawvalue=privkey, 611 | pubkey=pubkey, 612 | sec=sec, 613 | encryptedkey=encryptedkey, 614 | compressed=compress, 615 | privkey=prkey 616 | ) 617 | 618 | def set_keymeta( 619 | self, 620 | version: int, 621 | createtime: int, 622 | hdkeypath: Optional[str], 623 | hdmasterkey: Optional[str], 624 | fingerprint: int, 625 | has_keyorigin: bool = False, 626 | ) -> None: 627 | """Set keymeta field 628 | 629 | :param version: version parameter 630 | :type version: int 631 | :param createtime:created time 632 | :type createtime: int 633 | :param hdkeypath: hd key path 634 | :type hdkeypath: str 635 | :param hdmasterkey: hd master key 636 | :type hdmasterkey: str 637 | :param fingerprint: fingerprint value from wallet 638 | :type fingerprint: int 639 | :param has_keyorigin: whether has keyorigin field 640 | :type has_keyorigin: bool 641 | :return: None 642 | :rtype: 643 | """ 644 | self.version = version 645 | self.createtime = createtime 646 | self.hdkeypath = hdkeypath 647 | self.hdmasterkey = hdmasterkey 648 | self.fingerprint = fingerprint 649 | self.has_keyorigin = has_keyorigin 650 | 651 | def pubkey_towif(self, network_version: int = 0) -> bytes: 652 | """ 653 | 654 | :param network_version: version byte 655 | :type network_version: int 656 | :return: 657 | :rtype: 658 | """ 659 | prefix = bytes([network_version]) 660 | return base58.b58encode_check( 661 | prefix + ripemd160_sha256(self.publickey)) 662 | 663 | def privkey_towif(self, network_version: int = 0, 664 | compressed: bool = True) -> bytes: 665 | """ 666 | 667 | :param network_version: version byte 668 | :type network_version: int 669 | :param compressed: whether the key is compressed 670 | :type compressed: bool 671 | :return: 672 | :rtype: 673 | """ 674 | if self.privkey is not None: 675 | prefix = bytes([network_version + 128]) 676 | if compressed: 677 | suffix = b"\x01" 678 | else: 679 | suffix = b"" 680 | return base58.b58encode_check( 681 | prefix + self.privkey.secret + suffix) 682 | elif self.encryptedkey is not None: 683 | return self.encryptedkey 684 | 685 | def __repl__(self): 686 | if self.privkey is None and self.encryptedkey is not None: 687 | return "Pubkey: {} Encrypted Privkey: {}".format( 688 | self.publickey.hex(), self.encryptedkey.hex() 689 | ) 690 | elif self.privkey is not None and self.encryptedkey is None: 691 | return "Pubkey: {} Privkey: {}".format( 692 | self.publickey.hex(), self.privkey.hex() 693 | ) 694 | else: 695 | return "Pubkey: {}".format(self.publickey.hex()) 696 | 697 | 698 | def invert_txid(txid: bytes) -> str: 699 | """invert txid string from bytes 700 | 701 | :param txid: txid byte string from wallet 702 | :type txid: bytes 703 | :return: inverted txid string 704 | :rtype: str 705 | """ 706 | tx = txid.hex() 707 | if len(tx) != 64: 708 | raise ValueError("txid %r length != 64" % tx) 709 | new_txid = "" 710 | for i in range(32): 711 | new_txid += tx[62 - 2 * i] 712 | new_txid += tx[62 - 2 * i + 1] 713 | return new_txid 714 | 715 | 716 | class Transaction(object): 717 | """Transaction object - not to be called directly.""" 718 | 719 | def __init__( 720 | self, 721 | txid: str, 722 | version: int, 723 | txin: List, 724 | txout: List, 725 | locktime: int, 726 | tx: bytes) -> None: 727 | """ 728 | 729 | :param txid: transaction id string 730 | :type txid: str 731 | :param version: version byte 732 | :type version: int 733 | :param txin: txin list of dicts 734 | :type txin: list 735 | :param txout: txout list of dicts 736 | :type txout: list 737 | :param locktime: int 738 | :type locktime: 739 | :param tx: bytes 740 | :type tx: 741 | """ 742 | self.txid = txid 743 | self.version = version 744 | self.txin = txin 745 | self.txout = txout 746 | self.locktime = locktime 747 | self.tx = tx 748 | 749 | @classmethod 750 | def parse(cls, txid, vds): 751 | start = vds.read_cursor 752 | version = vds.read_int32() 753 | n_vin = vds.read_compact_size() 754 | txin = [] 755 | for _ in range(n_vin): 756 | d = { 757 | "prevout_hash": vds.read_bytes(32).hex(), 758 | "prevout_n": vds.read_uint32(), 759 | "scriptSig": vds.read_bytes(vds.read_compact_size()).hex(), 760 | "sequence": vds.read_uint32(), 761 | } 762 | txin.append(d) 763 | n_vout = vds.read_compact_size() 764 | txout = [] 765 | for _ in range(n_vout): 766 | d = { 767 | "value": vds.read_int64() / 1e8, 768 | "scriptPubKey": vds.read_bytes(vds.read_compact_size()).hex(), 769 | } 770 | txout.append(d) 771 | locktime = vds.read_uint32() 772 | tx = vds.input[start: vds.read_cursor] 773 | return cls( 774 | txid, 775 | version, 776 | sorted(txin, key=lambda i: (i["prevout_n"], i["sequence"])), 777 | txout, 778 | locktime, 779 | tx, 780 | ) 781 | --------------------------------------------------------------------------------