├── .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 | 
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 |
--------------------------------------------------------------------------------