diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..9ff1c755d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,44 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Note Regarding Scammers** +Scammers monitor the issues section of this Github and automatically reply to new issues with links to phishing websites, etc. (And Github moderation tools are very poor) You should disregard all comments recieved before a response from @3rdIteration... + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Sample Wallet** +Please provide a test wallet that demonstrates this issue. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..04845ce49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Free Support: CryptoGuide YouTube channel + url: https://www.youtube.com/channel/UCEviBQwLv-yfv3BErm0ojHg/ + about: BTCRecover Tutorials, free support happens in the comments section. (I reply to everything) + - name: Paid Support: CryptoGuide Recovery Services + url: https://cryptoguide.tips/recovery-services-consultations/ + about: Request Paid Private support or Trusted Recoveries. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..d550406df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- +**Note Regarding Scammers** +Scammers monitor the issues section of this Github and automatically reply to new issues with links to phishing websites, etc. (And Github moderation tools are very poor) You should disregard all comments recieved before a response from @3rdIteration... + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/Latest-Run-All-Tests_Base.yml b/.github/workflows/Latest-Run-All-Tests_Base.yml new file mode 100644 index 000000000..c6f5d1f56 --- /dev/null +++ b/.github/workflows/Latest-Run-All-Tests_Base.yml @@ -0,0 +1,33 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Last Push - All Tests (Base Modules) + +on: + push: + branches: [ master ] + +jobs: + build: + timeout-minutes: 20 # allow extra time for occasional slow tests + runs-on: ${{matrix.os}} + continue-on-error: ${{ matrix.python-version == '3.9' || matrix.python-version == '3.14' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04] # Test Ubuntu Only + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] # Test all suppoorted versions of Python + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run All Tests + run: | + python run-all-tests.py -vv diff --git a/.github/workflows/Latest-Run-All-Tests_Full.yml b/.github/workflows/Latest-Run-All-Tests_Full.yml new file mode 100644 index 000000000..930d4bdce --- /dev/null +++ b/.github/workflows/Latest-Run-All-Tests_Full.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Last Push - All Tests (Full Modules) + +env: + PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 + +on: + push: + branches: [ master ] + +jobs: + build: + timeout-minutes: 20 # allow extra time for occasional slow tests + runs-on: ${{matrix.os}} + continue-on-error: ${{ matrix.python-version == '3.9' || matrix.python-version == '3.14.0-rc.3' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04] # Test Ubuntu Only + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14.0-rc.3'] # Test all suppoorted versions of Python + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --require-hashes -r requirements-full.txt + sudo apt install python3-bsddb3 + - name: Run All Tests + run: | + python run-all-tests.py -vv diff --git a/.github/workflows/PoCL-tests-full.yml b/.github/workflows/PoCL-tests-full.yml new file mode 100644 index 000000000..592daece0 --- /dev/null +++ b/.github/workflows/PoCL-tests-full.yml @@ -0,0 +1,32 @@ +name: OpenCL PoCL Tests (Full) + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + pocl-full: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.12' + - name: Install OpenCL runtime + run: | + sudo apt-get update + sudo apt-get install -y pocl-opencl-icd ocl-icd-opencl-dev opencl-headers clinfo + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --require-hashes -r requirements-full.txt + pip install pyopencl + - name: Run tests with PoCL + env: + PYOPENCL_COMPILER_OUTPUT: '1' + run: python run-all-tests.py -vv diff --git a/.github/workflows/PoCL-tests.yml b/.github/workflows/PoCL-tests.yml new file mode 100644 index 000000000..c4d4fbcb9 --- /dev/null +++ b/.github/workflows/PoCL-tests.yml @@ -0,0 +1,32 @@ +name: OpenCL PoCL Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + pocl: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.12' + - name: Install OpenCL runtime + run: | + sudo apt-get update + sudo apt-get install -y pocl-opencl-icd ocl-icd-opencl-dev opencl-headers clinfo + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pyopencl + - name: Run tests with PoCL + env: + PYOPENCL_COMPILER_OUTPUT: '1' + run: python run-all-tests.py -vv diff --git a/.github/workflows/Weekly-Run-All-Tests-Full.yml b/.github/workflows/Weekly-Run-All-Tests-Full.yml new file mode 100644 index 000000000..33e4fe9d2 --- /dev/null +++ b/.github/workflows/Weekly-Run-All-Tests-Full.yml @@ -0,0 +1,52 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Weekly - All Tests (+Optional Modules) + +env: + PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1 + +on: + schedule: + - cron: "0 0 * * 6" + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + timeout-minutes: 20 # allow extra time for occasional slow tests + runs-on: ${{matrix.os}} + continue-on-error: ${{ matrix.python-version == '3.9' || matrix.python-version == '3.14.0-rc.3' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-latest, macos-latest] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14.0-rc.3'] # Test all supported versions of Python + exclude: + - os: windows-latest + python-version: '3.13' + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Build Environment + run: | + if [ "$RUNNER_OS" == "macOS" ]; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install autoconf automake libffi libtool pkg-config gnu-sed swig + fi + shell: bash + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --require-hashes -r requirements-full.txt + - name: Install green on Windows (workaround for hanging tests on Github Actions) + if: runner.os == 'Windows' + run: pip install green + - name: Run All Tests + run: | + python run-all-tests.py -vv diff --git a/.github/workflows/Weekly-Run-All-Tests_Base.yml b/.github/workflows/Weekly-Run-All-Tests_Base.yml new file mode 100644 index 000000000..937ec8f08 --- /dev/null +++ b/.github/workflows/Weekly-Run-All-Tests_Base.yml @@ -0,0 +1,49 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Weekly - All Tests (Base Modules) + +on: + schedule: + - cron: "0 0 * * 6" + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + timeout-minutes: 20 # allow extra time for occasional slow tests + runs-on: ${{matrix.os}} + continue-on-error: ${{ matrix.python-version == '3.9' || matrix.python-version == '3.14' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-latest, macos-latest] # Test all supported operating systems + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] # Test all supported versions of Python + exclude: + - os: windows-latest + python-version: '3.13' + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Build Environment + run: | + if [ "$RUNNER_OS" == "macOS" ]; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install autoconf automake libffi libtool pkg-config gnu-sed swig + fi + shell: bash + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Install green on Windows (workaround for hanging tests on Github Actions) + if: runner.os == 'Windows' + run: pip install green + - name: Run All Tests + run: | + python run-all-tests.py -vv diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 000000000..5489cc06c --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,26 @@ +name: Documentation Build + +on: + push: + branches: [ master ] + pull_request: + +jobs: + build-docs: + runs-on: ubuntu-24.04 + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + pip install --require-hashes -r docs/requirements.txt + - name: Build documentation + env: + MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdocs build --strict diff --git a/.github/workflows/termux-tests.yml b/.github/workflows/termux-tests.yml new file mode 100644 index 000000000..1b9e26626 --- /dev/null +++ b/.github/workflows/termux-tests.yml @@ -0,0 +1,99 @@ +name: Termux CI Tests + +on: + schedule: + - cron: "0 0 * * 6" + pull_request: + branches: [ master ] + workflow_dispatch: + push: + branches: [ master ] + +jobs: + termux: + name: Termux Base + strategy: + matrix: + include: + - runner: ubuntu-24.04-arm + architecture: aarch64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 + container: + image: termux/termux-docker:${{ matrix.architecture }} + volumes: + - /tmp/node20:/__e/node20 + env: + TERMUX_MAIN_PACKAGE_FORMAT: debian + ANDROID_ROOT: /system + ANDROID_DATA: /data + PREFIX: /data/data/com.termux/files/usr + HOME: /data/data/com.termux/files/home + PATH: /data/data/com.termux/files/usr/bin + TMPDIR: /data/data/com.termux/files/usr/tmp + LANG: en_US.UTF-8 + TZ: UTC + steps: + - name: set pkg mirror + run: ln -sf ${PREFIX}/etc/termux/mirrors/default ${PREFIX}/etc/termux/chosen_mirrors + - name: upgrade packages + run: /entrypoint.sh bash -c "yes | pkg upgrade -y" + - name: fix executable bit + run: chmod -R o+x ${PREFIX}/bin + - name: install nodejs + run: | + /entrypoint.sh pkg install -y nodejs-lts + ln -sf ${PREFIX}/bin /__e/node20/bin + - uses: actions/checkout@v4 + - name: fix permissions + run: chown -R 1000:1000 . + - name: Install dependencies + run: | + /entrypoint.sh pkg install -y python git autoconf automake build-essential libtool pkg-config binutils-is-llvm rust swig + /entrypoint.sh pip install -r requirements.txt + - name: Run tests + run: /entrypoint.sh python run-all-tests.py -vv + + termux-full: + name: Termux Full + strategy: + matrix: + include: + - runner: ubuntu-24.04-arm + architecture: aarch64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 + container: + image: termux/termux-docker:${{ matrix.architecture }} + volumes: + - /tmp/node20:/__e/node20 + env: + TERMUX_MAIN_PACKAGE_FORMAT: debian + ANDROID_ROOT: /system + ANDROID_DATA: /data + PREFIX: /data/data/com.termux/files/usr + HOME: /data/data/com.termux/files/home + PATH: /data/data/com.termux/files/usr/bin + TMPDIR: /data/data/com.termux/files/usr/tmp + LANG: en_US.UTF-8 + TZ: UTC + steps: + - name: set pkg mirror + run: ln -sf ${PREFIX}/etc/termux/mirrors/default ${PREFIX}/etc/termux/chosen_mirrors + - name: upgrade packages + run: /entrypoint.sh bash -c "yes | pkg upgrade -y" + - name: fix executable bit + run: chmod -R o+x ${PREFIX}/bin + - name: install nodejs + run: | + /entrypoint.sh pkg install -y nodejs-lts + ln -sf ${PREFIX}/bin /__e/node20/bin + - uses: actions/checkout@v4 + - name: fix permissions + run: chown -R 1000:1000 . + - name: Install dependencies + run: | + /entrypoint.sh pkg install -y python git autoconf automake build-essential libtool pkg-config binutils-is-llvm rust swig + /entrypoint.sh pip install -r requirements-full.txt + - name: Run tests + run: /entrypoint.sh python run-all-tests.py -vv diff --git a/.gitignore b/.gitignore index 7e45f9bec..41f69ccce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ *.pyc *.pyo /addresses.db +.idea/ +python +/addresses-BTC.db +venv/ +HashCheck.txt +bitcoinlib/.coveragerc +wallet.aes.json +test.txt +tokenlist.txt +seeds.txt +btcrecover-tokens-auto.txt +site/ +possible_passwords.log +passwordlist.txt +batch_seeds.txt.progress +batch_seeds.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..6825d4558 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,22 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 +build: + os: "ubuntu-24.04" + tools: + python: "3.13" + +mkdocs: + configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt diff --git a/.travis.install-dependencies.sh b/.travis.install-dependencies.sh deleted file mode 100644 index 052d95c3b..000000000 --- a/.travis.install-dependencies.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# Best practice for Travis CI is to use Python virtualenv instead of the system Python. -# Unfortunately, the virtualenv which Travis CI sets up has a broken bsddb module, -# doesn't have PyCrypto preinstalled, and doesn't play nice with dependencies from -# the Armory .deb distribution, so we just use the system Python instead. - -set -e - -# Download and install Armory v0.93.3 plus prerequisites -# (v0.94+ is unsupported on Ubuntu 12.04 w/o recompiling libstdc++6) - -LATEST="https://github.com/goatpig/BitcoinArmory/releases/download/v0.93.3/armory_0.93.3_ubuntu-64bit.deb" - -curl -LfsS --retry 10 -o 'armory.deb' "$LATEST" - -sudo apt-get -q update -sudo apt-get -yq install gdebi-core -sudo gdebi -nq armory.deb - -# Download, compile, and install prerequisites for bitcoinj wallets - -curl -fsS --retry 10 https://bootstrap.pypa.io/get-pip.py | sudo python -sudo /usr/local/bin/pip install -q protobuf scrypt pylibscrypt coincurve pysha3 green diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b4f4d8f75..000000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: python -python: "2.7" -install: bash .travis.install-dependencies.sh -script: /usr/bin/python2.7 run-all-tests.py --no-pause -vv diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..535f083fc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,8 @@ +# Repository Instructions + +## Code Quality +- Ensure that all code changes conform to PEP 8 guidelines, including converting any tab-based indentation to spaces. + +## Required Tests +- Always run `python run-all-tests.py` before completing work on this repository. + diff --git a/Licences/shamir-mnemonic.txt b/Licences/shamir-mnemonic.txt new file mode 100644 index 000000000..68565b6cb --- /dev/null +++ b/Licences/shamir-mnemonic.txt @@ -0,0 +1,18 @@ +Copyright 2019 SatoshiLabs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 32b1e3421..9efec2b15 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,175 @@ -# *btcrecover* [![Build Status](https://travis-ci.org/gurnec/btcrecover.svg?branch=master)](https://travis-ci.org/gurnec/btcrecover) ![license](https://img.shields.io/badge/license-GPLv2-blue.svg) # +# BTCRecover +[![Last Push - All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Last%20Push%20-%20All%20Tests%20(Base%20Modules)/badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Last+Push+-+All+Tests+%28Base+Modules%29%22) [![Weekly - All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Weekly%20-%20All%20Tests%20(Base%20Modules)/badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Weekly+-+All+Tests+%28Base+Modules%29%22) [![Weekly Run All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Weekly%20-%20All%20Tests%20(+Optional%20Modules)//badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Weekly+-+All+Tests+%28%2BOptional+Modules%29%22) [![Documentation Status](https://readthedocs.org/projects/btcrecover/badge/?version=latest)](https://btcrecover.readthedocs.io/en/latest/?badge=latest) ![license](https://img.shields.io/badge/license-GPLv2-blue.svg) -*btcrecover* is an open source Bitcoin wallet password and seed recovery tool. It is designed for the case where you already know most of your password or seed, but need assistance in trying different possible combinations. +*BTCRecover* is an open source wallet password and seed recovery tool. +For seed based recovery, this is primarily useful in situations where you have lost/forgotten parts of your mnemonic, or have made an error transcribing it. (So you are either seeing an empty wallet or getting an error that your seed is invalid) -## Quick Start ## - -To try recovering your password, please start with the **[Password Recovery Quick Start](TUTORIAL.md#btcrecover-tutorial)**. +For wallet password or passphrase recovery, it is primarily useful if you have a reasonable idea about what your password might be. -If you mostly know your recovery seed/mnemonic (12-24 recovery words), but think there may be a mistake in it, please see the **[Seed Recovery Quick Start](docs/Seedrecover_Quick_Start_Guide.md)**. +# Documentation: +### Instructions for installation, usage & examples: [https://btcrecover.readthedocs.io/](https://btcrecover.readthedocs.io/) -If you find *btcrecover* helpful, please consider a small donation: -**[3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4](bitcoin:3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4?label=btcrecover)** - -**Thank You!** +[(You can also view the documentation in your browser locally by following the instructions here. )](docs/local_mkdocs.md) +If you need help, [your best bet is to look at my BTCRecover playlist on YouTube](https://www.youtube.com/playlist?list=PL7rfJxwogDzmd1IanPrmlTg3ewAIq-BZJ) and ask a question in the comments section for any of video closest to your situation. ## Features ## +* BIP39 Seed/Passphrase Recovery when for: (Recovery without a known address requires an [Address Database](docs/Creating_and_Using_AddressDB.md)) + * Avalanche + * Bitcoin + * Bitcoin Cash + * Cardano (Shelley Era Addresses) + * Cosmos (Atom) Any many other Cosmos Chains (Nym, GravityBridge, etc) + * Dash + * DigiByte + * Dogecoin + * Ethereum + * Groestlcoin + * Helium + * Litecoin + * Monacoin + * MultiversX + * Polkadot (sr25519, like those produced by polkadot.js) + * Ripple + * Secret Network + * Solana + * Stacks + * Stellar + * Tezos + * Tron + * Vertcoin + * Zilliqa + * And many other 'Bitcoin Like' cryptos + * SLIP39 Passphrase Recovery for most coins supported by the Trezor T + * SLIP39 Seed Share recovery with `seedrecover.py` + * Bitcoin + * Bitcoin Cash + * Dash + * Digibyte + * Dogecoin + * Ethereum + * Litecoin + * Ripple + * Vertcoin + * [Descrambling 12 word seeds](docs/BIP39_descrambling_seedlists.md) (Using Tokenlist feature for BIP39 seeds via seedrecover.py) + * Wallet File password recovery for a range of wallets +* Seed Phrase (Mnemonic) Recovery for the following wallets + * [Electrum](https://electrum.org/) (1.x, 2.x, 3.x and 4.x) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) + * [Electron-Cash](https://www.electroncash.org/) (2.x, 3.x and 4.x) + * BIP-32/39 compliant wallets ([bitcoinj](https://bitcoinj.github.io/)), including: + * [MultiBit HD](https://multibit.org/) + * [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schildbach.wallet) (with seeds previously extracted by [decrypt\_bitcoinj\_seeds](https://github.com/gurnec/decrypt_bitcoinj_seed)) + * [Hive for Android](https://play.google.com/store/apps/details?id=com.hivewallet.hive.cordova), [for iOS](https://github.com/hivewallet/hive-ios), and [Hive Web](https://hivewallet.com/) + * [Breadwallet](https://brd.com/) + * BIP-32/39/44 Bitcoin & Ethereum compliant wallets, including: + * [Mycelium for Android](https://wallet.mycelium.com/) + * [TREZOR](https://www.bitcointrezor.com/) + * [Ledger](https://www.ledgerwallet.com/) + * [Keepkey](https://shapeshift.io/keepkey/) + * [ColdCard](https://coldcardwallet.com/) + * [Blockstream Jade](https://blockstream.com/jade/) + * [Jaxx](https://jaxx.io/) + * [Coinomi](https://www.coinomi.com/) + * [Exodus](https://www.exodus.io/) + * [MyEtherWallet](https://www.myetherwallet.com/) + * [Trust Wallet](https://trustwallet.com/) + * [Bither](https://bither.net/) + * [Blockchain.com](https://blockchain.com/wallet) + * Ethereum Validator BIP39 Seed Recovery * Bitcoin wallet password recovery support for: - * [Armory](https://btcarmory.com/) - * [Bitcoin Unlimited](https://www.bitcoinunlimited.info/)/[Classic](https://bitcoinclassic.com/)/[XT](https://bitcoinxt.software/)/[Core](https://bitcoincore.org/) + * [Bitcoin Core](https://bitcoincore.org/) * [MultiBit HD](https://multibit.org/) and [MultiBit Classic](https://multibit.org/help/v0.5/help_contents.html) - * [Electrum](https://electrum.org/) (1.x and 2.x) + * [Electrum](https://electrum.org/) (1.x, 2.x, 3.x and 4.x) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) * Most wallets based on [bitcoinj](https://bitcoinj.github.io/), including [Hive for OS X](https://github.com/hivewallet/hive-mac/wiki/FAQ) - * BIP-39 passphrases, Bitcoin & Ethereum supported (e.g. [TREZOR](https://www.bitcointrezor.com/) & [Ledger](https://www.ledgerwallet.com/) passphrases) + * BIP-39 passphrases (Also supports all cryptos supported for seed recovery, as well as recovering "Extra Words" for Electrum seeds) * [mSIGNA (CoinVault)](https://ciphrex.com/products/) - * [Blockchain.info](https://blockchain.info/wallet) + * [Blockchain.com](https://blockchain.com/wallet) + * [block.io](https://block.io/) (Recovery of wallet "Secret PIN") + * [btc.com (aka blocktrail)](https://btc.com) (Recovery of wallet password, needed to decrypt the PDF backup sheets) * [pywallet --dumpwallet](https://github.com/jackjack-jj/pywallet) of Bitcoin Unlimited/Classic/XT/Core wallets * [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schildbach.wallet) spending PINs and encrypted backups * [KnC Wallet for Android](https://github.com/kncgroup/bitcoin-wallet) encrypted backups * [Bither](https://bither.net/) - * Altcoin password support for most wallets derived from one of those above, including: + * [Encrypted (BIP-38) Paper Wallet Support (Eg: From Bitaddress.org)](https://bitaddress.org) Also works with altcoin forks like liteaddress.org, paper.dash.org, etc... + * Brainwallets + * Sha256(Passphrase) brainwallets (eg: Bitaddress.org, liteaddress.org, paper.dash.org) + * sCrypt Secured Brainwallets (Eg: Warpwallet, Memwallet) + * Altcoin password recovery support for most wallets derived from one of those above, including: + * [Coinomi](https://www.coinomi.com/en/) (Only supports password protected wallets) + * [imToken](https://token.im/) + * [Metamask](https://metamask.io/) (And Metamask clones like Binance Chain Wallet, Ronin Wallet, etc.) * [Litecoin Core](https://litecoin.org/) - * [Electrum-LTC](https://electrum-ltc.org/) + * [Electrum-LTC](https://electrum-ltc.org/) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) + * [Electron-Cash](https://www.electroncash.org/) (2.x, 3.x and 4.x) * [Litecoin Wallet for Android](https://litecoin.org/) encrypted backups * [Dogecoin Core](http://dogecoin.com/) * [MultiDoge](http://multidoge.org/) + * [Dogechain.info](https://dogechain.info/) * [Dogecoin Wallet for Android](http://dogecoin.com/) encrypted backups - * Bitcoin & Ethereum seed recovery support for: - * [Electrum](https://electrum.org/) (1.x and 2.x, plus wallet file loading support) - * BIP-32/39 compliant wallets ([bitcoinj](https://bitcoinj.github.io/)), including: - * [MultiBit HD](https://multibit.org/) - * [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schildbach.wallet) (with seeds previously extracted by [decrypt\_bitcoinj\_seeds](https://github.com/gurnec/decrypt_bitcoinj_seed)) - * [Hive for Android](https://play.google.com/store/apps/details?id=com.hivewallet.hive.cordova), [for iOS](https://github.com/hivewallet/hive-ios), and [Hive Web](https://hivewallet.com/) - * [breadwallet for iOS](https://breadwallet.com/) - * BIP-32/39/44 Bitcoin & Ethereum compliant wallets, including: - * [Mycelium for Android](https://wallet.mycelium.com/) - * [TREZOR](https://www.bitcointrezor.com/) - * [Ledger](https://www.ledgerwallet.com/) - * [Jaxx](https://jaxx.io/) - * [MyEtherWallet](https://www.myetherwallet.com/) - * [Bither](https://bither.net/) - * [Blockchain.info](https://blockchain.info/wallet) + * [Yoroi Wallet for Cardano](https://yoroi-wallet.com/#/) Master_Passwords extracted from the wallet data (In browser or on rooted/jailbroken phones) + * [Ethereum UTC Keystore Files](https://myetherwallet.com) Ethereum Keystore files, typically used by wallets like MyEtherWallet, MyCrypto, etc. (Also often used by Eth clones like Theta, etc) + * [Damaged Raw Eth Private Keys]() Individual Private keys that are missing characters. + * [Toastwallet](https://toastwallet.github.io/browser/) Toastwallet Passphrase * [Free and Open Source](http://en.wikipedia.org/wiki/Free_and_open-source_software) - anyone can download, inspect, use, and redistribute this software - * Supported on Windows, Linux, and OS X + * Supported on Windows, Linux, and MacOS * Support for Unicode passwords and seeds * Multithreaded searches, with user-selectable thread count - * Experimental [GPU acceleration](docs/GPU_Acceleration.md) for Bitcoin Unlimited/Classic/XT/Core, Armory, and derived altcoin wallets + * Ability to spread search workload over multiple devices + * [GPU acceleration](docs/GPU_Acceleration.md) for Bitcoin Core Passwords, Blockchain.com (Main and Second Password), Electrum Passwords + BIP39 and Electrum Seeds * Wildcard expansion for passwords * Typo simulation for passwords and seeds * Progress bar and ETA display (at the command line) * Optional autosave - interrupt and continue password recoveries without losing progress * Automated seed recovery with a simple graphical user interface + * Ability to search multiple derivation paths simultaneously for a given seed via --pathlist command (example pathlist files in the ) * “Offline” mode for nearly all supported wallets - use one of the [extract scripts (click for more information)](docs/Extract_Scripts.md) to extract just enough information to attempt password recovery, without giving *btcrecover* or whoever runs it access to *any* of the addresses or private keys in your Bitcoin wallet. - * “Nearly offline” mode for Armory - use an [extract script (click for more information)](docs/Extract_Scripts.md) to extract a single private key for attempting password recovery. *btcrecover* and whoever runs it will only have access to this one address/private key from your Bitcoin wallet (read the link above for an important caveat). + +## Setup and Usage Tutorials ## +BTCRecover is a Python (3.9, 3.10, 3.11, 3.12 and 3.13) script so will run on Windows, Linux, Mac and even Android environments. [See the installation guide for more info](docs/INSTALL.md) + +[I have created a growing playlist](https://www.youtube.com/playlist?list=PL7rfJxwogDzmd1IanPrmlTg3ewAIq-BZJ) that covers a number of usage examples for using this tool to recover seed phrases, BIP39 passphrases, etc. + +This repository also included some example commands and file templates in the ./docs/ folder of the repository. + +My suggestion is that you find a scenario that is most-like your situation and try to replicate my examples to ensure that you have the tool set up and running correctly. If you have a specific situation that isn't covered in these tutorials, let me know and I can look into creating a video for that. + +If you don't know an address in the wallet that you are searching for, you can create and use an [Address Database (click here for guide)](docs/Creating_and_Using_AddressDB.md) _There is no real performance penalty for doing this, it just takes a bit more work to set up_. + +## Quick Start ## + +To try recovering your password or a BIP39 passphrase, please start with the **[Password Recovery Quick Start](docs/TUTORIAL.md#btcrecover-tutorial)**. + +If you mostly know your recovery seed/mnemonic (12-24 recovery words), but think there may be a mistake in it, please see the **[Seed Recovery Quick Start](docs/Seedrecover_Quick_Start_Guide.md)**. + +## Audible Alerts ## + +Command-line recoveries support an optional `--beep-on-find` flag that plays a two-tone pattern—two beeps one and a half seconds apart that repeat every ten seconds—after a successful recovery and emits a single beep when the search finishes without a match. The alert still prints the ASCII bell character so terminals can raise their own notification, but *btcrecover* now also tries to engage the Linux console tone generator so the on-board PC speaker can sound even when the terminal would stay silent. The helper first attempts to open `/dev/console` (or the device named by `BTCRECOVER_CONSOLE_BELL`) for the tone ioctl; if that fails but the [`beep`](https://linux.die.net/man/1/beep) utility is available it will run that before falling back to writing the bell character directly, exactly as before. Set the environment variable to an empty string to disable the console access entirely or to another device path if your distribution exposes the speaker elsewhere. + +## If this tool or other content on my YouTube channel was helpful, feel free to send a tip to: ## + +![Donate Bitcoin](docs/Images/donate-btc-qr.png) + +BTC: 37N7B7sdHahCXTcMJgEnHz7YmiR4bEqCrS + +![Donate Bitcoin Cash](docs/Images/donate-bch-qr.png) + +BCH: qpvjee5vwwsv78xc28kwgd3m9mnn5adargxd94kmrt + +![Donate Litecoin](docs/Images/donate-ltc-qr.png) + +LTC: M966MQte7agAzdCZe5ssHo7g9VriwXgyqM + +![Donate Ethereum](docs/Images/donate-eth-qr.png) + +ETH: 0x72343f2806428dbbc2C11a83A1844912184b4243 + +## Thanks to Gurnec ## +This tool builds on the original work of Gurnec who created it and maintained it until late 2017. If you find *btcrecover* helpful, please consider a small donation to them too. (I will also be passing on a portion of any tips I recieve at the addys above to them too) + +![Donate Bitcoin](docs/Images/gurnec-donate-btc-qr.png) + +BTC: 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 + +**Thank You!** diff --git a/TUTORIAL.md b/TUTORIAL.md deleted file mode 100644 index 7ea08f7cb..000000000 --- a/TUTORIAL.md +++ /dev/null @@ -1,559 +0,0 @@ -# *btcrecover* Tutorial # - - -*btcrecover* is a free and open source multithreaded wallet password recovery tool with support for Armory, Bitcoin Unlimited/Classic/XT/Core, MultiBit (Classic and HD), Electrum (1.x and 2.x), mSIGNA (CoinVault), Hive for OS X, Blockchain.info (v1-v3 wallet formats, both main and second passwords), Bither, and Bitcoin & KNC Wallets for Android. It is designed for the case where you already know most of your password, but need assistance in trying different possible combinations. This tutorial will guide you through the features it has to offer. - -If you find *btcrecover* helpful, please consider a small donation to help support my efforts: -**[3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4](bitcoin:3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4?label=btcrecover)** - -#### Thank You! #### - - -## Quick Start ## - -This tutorial is pretty long... you don't have to read the whole thing. Here are some places to start. - - 1. Read the [Installation Guide](docs/INSTALL.md) for instructions and download links. - 2. (optional) Run the unit tests by double-clicking on `run-all-tests.py`. If you encounter any failures, please [report them here](https://github.com/gurnec/btcrecover/issues). - 3. If you already have a `btcrecover-tokens-auto.txt` file, skip straight to step 6. If not, and you need help creating passwords from different combinations of smaller pieces you remember, start with step 4. If you you think there's a typo in your password, or if you mostly know what your whole password is and only need to try different variations of it, read step 5. - 4. Read [The Token File](#the-token-file) section (at least the beginning), which describes how *btcrecover* builds up a whole password you don't remember from smaller pieces you do remember. Once you're done, you'll know how to create a `tokens.txt` file you'll need later. - 5. Read the [Typos](#typos) section, which describes how *btcrecover* can make variations to a whole password to create different password guesses. Once you're done, you'll have a list of command-line options which will create the variations you want to test. - * If you skipped step 4 above, read the simple [Passwordlist](#the-passwordlist) section instead. - 6. Read the [Running *btcrecover*](#running-btcrecover) section to see how to put these pieces together and how to run *btcrecover* in a Command Prompt window. - * (optional) Read the [Testing your config](#testing-your-config) section to view the passwords that will be tested. - * (optional) If you're testing a lot of combinations that will take a long time, use the [Autosave](#autosave) feature to safeguard against losing your progress. - 7. (optional, but highly recommended) Donate huge sums of Bitcoin to the donation address above once your password's been found. - - -## The Token File ## - -*btcrecover* can accept as input a text file which has a list of what are called password “tokens”. A token is simply a portion of a password which you do remember, even if you don't remember where that portion appears in the actual password. It will combine these tokens in different ways to create different whole password guesses to try. - -This file, typically named `tokens.txt`, can be created in any basic text editor, such as Notepad on Windows or TextEdit on OS X, and should probably be saved into the same folder as the `btcrecover.py` script (just to keep things simple). Note that if your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII) (non-English) characters, you should read the section on [Unicode Support](#unicode-support) before continuing. - -### Basics ### - -Let’s say that you remember your password contains 3 parts, you just can’t remember in what order you used them. Here are the contents of a simple `tokens.txt` file: - - Cairo - Beetlejuice - Hotel_california - -When used with these contents, *btcrecover* will try all possible combinations using one or more of these three tokens, e.g. `Hotel_california` (just one token), `BettlejuiceCairo` (two tokens pasted together), etc. - -Note that lines which start with a `#` are ignored as comments, but only if the `#` is at the *very beginning* of the line: - - # This line is a comment, it's ignored. - # The line at the bottom is not a comment because the - # first character on the line is a space, and not a # - #a_single_token_starting_with_the_#_symbol - -### Mutual Exclusion ### - -Maybe you’re not sure about how you spelled or capitalized one of those words. Take this token file: - - Cairo - Beetlejuice beetlejuice Betelgeuse betelgeuse - Hotel_california - -Tokens listed on the same line, separated by spaces, are mutually exclusive and will never be tried together in a password guess. *btcrecover* will try `Cairo` and `bettlejuiceCairoHotel_california`, but it will skip over `Betelgeusebetelgeuse`. Had all four Beetlejuice versions been listed out on separate lines, this would have resulted in trying thousands of additional passwords which we know to be incorrect. As is, this token file only needs to try 48 passwords to account for all possible combinations. Had they all been on separate lines, it would have had to try 1,956 different combinations. - -In short, when you’re sure that certain tokens or variations of a token have no chance of appearing together in a password, placing them all on the same line can save a lot of time. - -### Required Tokens ### - -What if you’re certain that `Cairo` appears in the password, but you’re not so sure about the other tokens? - - + Cairo - Beetlejuice beetlejuice Betelgeuse betelgeuse - Hotel_california - -Placing a `+` (and some space after it) at the beginning of a line tells *btcrecover* to only try passwords that include `Cairo` in them. You can also combine these two last features. Here’s a longer example: - - Cairo cairo Katmai katmai - + Beetlejuice beetlejuice Betelgeuse betelgeuse - Hotel_california hotel_california - -In this example above, passwords will be constructed by taking at most one token from the first line, exactly one token from the second line (it’s required), and at most one token from the third line. So `Hotel_californiaBetelgeuse` would be tried, but `cairoKatmaiBetelgeuse` would be skipped (`cairo` and `Katmai` are on the same line, so they’re never tried together) and `katmaiHotel_california` is also skipped (because one token from the second line is required in every try). - -This file will create a total of just 244 different combinations. Had all ten of those tokens been listed on separate lines, it would have produced 9,864,100 guesses, which could take days longer to test! - -### Anchors ### - -#### Beginning and Ending Anchors #### - -Another way to save time is to use “anchors”. You can tell *btcrecover* that certain tokens, if they are present at all, are definitely at the beginning or end of the password: - - ^Cairo - Beetlejuice beetlejuice Betelgeuse betelgeuse - Hotel_california$ - -In this example above, the `^` symbol is considered special if it appears at the beginning of any token (it’s not actually a part of the password), and the `$` symbol is special if it appears at the end of any token. `Cairo`, if it is tried, is only tried at the beginning of a password, and `Hotel_california`, if it is tried, is only tried at the end. Note that neither is required to be tried in password guesses with the example above. As before, all of these options can be combined: - - Cairo - Beetlejuice beetlejuice Betelgeuse betelgeuse - + ^Hotel_california ^hotel_california - -In this example above, either `Hotel_california` or `hotel_california` is *required* at the beginning of every password that is tried (and the other tokens are tried normally after that). - -#### Positional Anchors #### - -Tokens with positional anchors may only appear at one specific position in the password -- there are always a specific number of other tokens which precede the anchored one. In the example below you'll notice a number in between the two `^` symbols added to the very beginning to create positionally anchored tokens (with no spaces): - - ^2^Second_or_bust - ^3^Third_or_bust - Cairo - Beetlejuice - Hotel_california - -As you can guess, `Second_or_bust`, if it is tried, is only tried as the second token in a password, and `Third_or_bust`, if it is tried, is only tried as the third. (Neither token is required because there is no `+` at the beginning these of these lines.) - -#### Middle Anchors #### - -Middle anchors are a bit like positional anchors, only more flexible: the anchored tokens may appear once throughout a specific *range* of positions in the password. - -**Note** that placing a middle anchor on a token introduces a special restriction: it *forces* the token into the *middle* of a password. A token with a middle anchor (unlike any of the other anchors described above) will *never* be tried as the first or last token of a password. - -You specify a middle anchor by adding a comma and two numbers (between the `^` symbols) at the very beginning of a token (all with no spaces): - - ^2,3^Second_or_third_(but_never_last) - ^2,4^Second_to_fourth_(but_never_last) - Cairo - Beetlejuice - Hotel_california - - As mentioned above, neither of those middle-anchored tokens will ever be tried as the last token in a password, so something (one or more of the non-anchored tokens) will appear after the middle-anchored ones in every guess in which they appear. Since tokens with middle anchors never appear at the beginning either, the smallest value you can use for that first number is 2. Finally, when you specify the range, you can leave out one (or even both) of the numbers, like this: - - ^3,^Third_or_after_(but_never_last) - ^,3^Third_or_earlier(but_never_first_or_last) - ^,^Anywhere_in_the_middle - Cairo - Beetlejuice - Hotel_california - -You can't leave out the comma (that's what makes it a middle anchor instead of a positional anchor). Leaving out a number doesn't change the “never at the beginning or the end” rule which always applies to middle anchors. If you do need a token with a middle anchor to also possibly appear at the beginning or end of a password, you can add second copy to the same line with a beginning or end anchor (because at most one token on a line can appear in any guess): - - ^,^Anywhere_in_the_middle_or_end Anywhere_in_the_middle_or_end$ - ^,^Anywhere_in_the_middle_or_beginning ^Anywhere_in_the_middle_or_beginning - -#### Relative Anchors #### - -Relative anchors restrict the position of tokens relative to one another. They are only affected by other tokens which also have relative anchors. They look like positional anchors, except they have a single `r` preceding the relative number value: - - ^r1^Earlier - ^r2^Middlish_A - ^r2^Middlish_B - ^r3^Later - Anywhere - -In this example above, if two or more relative-anchored tokens appear together in a single password guess, they appear in their specified order. `Earlier Anywhere Later` and `Anywhere Middlish_A Later` would be tried, however `Later Earlier` would not. Note that `Middlish_A` and `Middlish_B` can appear in the same guess, and they can appear with either being first since they have a matching relative value, e.g. `Middlish_B Middlish_A Later` would be tried. - -You cannot specify a single token with both a positional and relative anchor at the same time. - -### Token Counts ### - -There are a number of command-line options that affect the combinations tried. The `--max-tokens` option limits the number of tokens that are added together and tried. With `--max-tokens` set to 2, `Hotel_californiaCairo`, made from two tokens, would be tried from the earlier example, but `Hotel_californiaCairoBeetlejuice` would be skipped because it’s made from three tokens. You can still use *btcrecover* even if you have a large number of tokens, as long as `--max-tokens` is set to something reasonable. If you’d like to re-run *btcrecover* with a larger number of `--max-tokens` if at first it didn’t succeed, you can also specify `--min-tokens` to avoid trying combinations you’ve already tried. - -### Expanding Wildcards ### - -What if you think one of the tokens has a number in it, but you’re not sure what that number is? For example, if you think that Cairo is definitely followed by a single digit, you could do this: - - Cairo0 Cairo1 Cairo2 Cairo3 Cairo4 Cairo5 Cairo6 Cairo7 Cairo8 Cairo9 - Beetlejuice - Hotel_california - -While this definitely works, it’s not very convenient. This next token file has the same effect, but it’s easier to write: - - Cairo%d - Beetlejuice - Hotel_california - -The `%d` is a wildcard which is replaced by all combinations of a single digit. Here are some examples of the different types of wildcards you can use: - - * `%d` - a single digit - * `%2d` - exactly 2 digits - * `%1,3d` - between 1 and 3 digits (all possible permutations thereof) - * `%0,2d` - between 0 and 2 digits (in other words, the case where there are no digits is also tried) - * `%a` - a single ASCII lowercase letter - * `%1,3a` - between 1 and 3 lowercase letters - * `%A` - a single ASCII uppercase letter - * `%n` - a single digit or lowercase letter - * `%N` - a single digit or uppercase letter - * `%ia` - a “case-insensitive” version of %a: a single lower or uppercase letter - * `%in` - a single digit, lower or uppercase letter - * `%1,2in`- between 1 and 2 characters long of digits, lower or uppercase letters - * `%[chars]` - exactly 1 of the characters between `[` and `]` (e.g. either a `c`, `h`, `a`, `r`, or `s`) - * `%1,3[chars]` - between 1 and 3 of the characters between `[` and `]` - * `%[0-9a-f]` - exactly 1 of these characters: `0123456789abcdef` - * `%2i[0-9a-f]` - exactly 2 of these characters: `0123456789abcdefABCDEF` - * `%s` - a single space - * `%l` - a single line feed character - * `%r` - a single carriage return character - * `%R` - a single line feed or carriage return character - * `%t` - a single tab character - * `%T` - a single space or tab character - * `%w` - a single space, line feed, or carriage return character - * `%W` - a single space, line feed, carriage return, or tab character - * `%y` - any single ASCII symbol - * `%Y` - any single ASCII digit or symbol - * `%p` - any single ASCII letter, digit, or symbol - * `%P` - any single character from either `%p` or `%W` (pretty much everything) - * `%c` - a single character from a custom set specified at the command line with `--custom-wild characters` - * `%C` - an uppercased version of `%c` (the same as `%c` if `%c` has no lowercase letters) - * `%ic` - a case-insensitive version of `%c` - * `%%` - a single `%` (so that `%`’s in your password aren’t confused as wildcards) - * `%^` - a single `^` (so it’s not confused with an anchor if it’s at the beginning of a token) - * `%S` - a single `$` (yes, that’s `%` and a capital `S` that gets replaced by a dollar sign, sorry if that’s confusing) - -Up until now, most of the features help by reducing the number of passwords that need to be tried by exploiting your knowledge of what’s probably in the password. Wildcards significantly expand the number of passwords that need to be tried, so they’re best used in moderation. - -### Backreference Wildcards ### - -Backreference wildcards copy one or more characters which appear somewhere earlier in the password. In the simplest case, they're not very useful. For example, in the token `Z%b`, the `%b` simply copies the character which immediately precedes it, resulting in `ZZ`. - -Consider the case where the password contains patterns such as `AA`, `BB`, up through `ZZ`, but would never contain `AZ`. You could use `%2A` to generate these patterns, but then you'd end up with `AZ` being tried. `%2A` generates 676 different combinations, but in this example we only want to try 26. Instead you can use two wildcards together: `%A%b`. The `%A` will expand into a single letter (from `A` to `Z`), and *after* this expansion happens, the `%b` will copy that letter, resulting in only the 26 patterns we want. - -As with normal wildcards, backreference wildcards may contain a copy length, for example: - - * `Test%d%b` - `Test00` through `Test99`, but never `Test12` - * `Test%d%2b` - `Test000` through `Test999`, but never `Test123` - * `Test%d%0,3b` - `Test0` to `Test9` (the backreference length is 0), `Test00` to `Test99`, etc., `Test0000` to `Test9999` - -In the examples so far, the copying starts with the character immediately to the left of the `%b`, but this can be changed by adding a `;#` just before the `b`, for example: - - * `Test%b` - `Testt` - * `Test%;1b` - starts 1 back, same as above, `Testt` - * `Test%;2b` - starts 2 back, `Tests` - * `Test%;4b` - starts 4 back, `TestT` - * `Test%2;4b` - starts 4 back, with a copy length of 2: `TestTe` - * `Test%8;4b` - starts 4 back, with a copy length of 8: `TestTestTest` - * `Test%0,2;4b` - starts 4 back, with a copy length from 0 to 2: `Test`, `TestT`, and `TestTe` - * `%2Atest%2;6b` - patterns such as `ABtestAB` and `XKtestXK` where the two capital letters before and after `test` match each other, but never `ABtestXK` where they don't match - -To summarize, wildcards to the left of a `%b` are expanded first, and then the `%b` is replaced by copying one or more characters from the left, and then wildcards towards the right (if any) are examined. - -### Contracting Wildcards ### - -Instead of adding new characters to a password guess, contracting wildcards remove one or more characters. Here's an example: - - Start%0,2-End - -The `%0,2-` contracting wildcard will remove between 0 and 2 adjacent characters from either side, so that each of `StartEnd` (removes 0), `StarEnd` (removes 1 from left), `StaEnd` (removes 2 from left), `Starnd` (removes 1 from left and 1 from right), `Startnd` (removes 1 from right), and `Startd` (removes 2 from right) will be tried. This can be useful when considering copy-paste errors, for example: - - %0,20-A/Long/Password/with/symbols/that/maybe/was/partially/copy/pasted%0,20- - -Different versions of this password will be tried removing up to 20 characters from either end. - -Here are the three types of contracting wildcards: - - * `%0,5-` - removes between 0 and 5 adjacent characters (total) taken from either side of the wildcard - * `%0,5<` - removes between 0 and 5 adjacent characters only from the wildcard's left - * `%0,5>` - removes between 0 and 5 adjacent characters only from the wildcard's right - -You may want to note that a contracting wildcard in one token can potentially remove characters from other tokens, but it will never remove or cross over another wildcard. Here's an example to fully illustrate this (feel free to skip to the next section if you're not interested in these specific details): - - AAAA%0,10>BBBB - xxxx%dyyyy - -These two tokens each have eight normal letters. The first token has a contracting wildcard which removes up to 10 characters from its right, and the second token has an expanding wildcard which expands to a single digit. - -One of the passwords generated from these tokens is `AAAABBxxxx5yyyy`, which comes from selecting the first token followed by the second token, and then applying the wildcards with the contracting wildcard removing two characters. Another is `AAAAxx5yyyy` which comes from the same tokens, but the contracting wildcard now is removing six characters, two of which are from the second token. - -The digit and the `yyyy` will never be removed by the contracting wildcard because other wildcards are never removed or crossed over. Even though the contracting wildcard is set to remove up to 10 characters, `AAAAyyy` will never be produced because the `%d` blocks it. - -### Keyboard Walking — Backreference Wildcards, revisited ### - -This feature combines traits of both backreference wildcards and typos maps into a single function. If you haven't read about typos maps below (or about backreference wildcards above), you should probably skip this section for now and come back later. - -Consider a complex password pattern such as this: `00test11`, `11test22`, etc. up through `88test99`. In other words, the pattern is generated by combining these 5 strings: `#` `#` `test` `#+1` `#+1`. Using simple backreference wildcards, we can almost produce such a pattern with this token: `%d%btest%d%b`. This produces everything from our list, but it also produced a lot more that we don't want, for example `33test55` is produced even though it doesn't match the pattern because 3+1 is not 5. - -Instead a way is needed for a backreference wildcard to do more than simply copy a previous character, it must be able to create a *modified copy* of a previous character. It can do this the same way that a typos map replaces characters by using a separate map file to determine the replacement. So to continue this example, a new map file is needed, `nextdigit.txt`: - - 0 1 - 1 2 - 2 3 - 3 4 - 4 5 - 5 6 - 6 7 - 7 8 - 8 9 - -Finally, here's a token that makes use of this map file to generate the pattern we're looking for: `%d%btest%2;nextdigit.txt;6b`. That's pretty complicated, so let's break it down: - - * `%d` - expands to `0` through `9` - * `%b` - copies the previous character, so no we have `00` through `99` - * `test` - now we have `00test` through `99test` - * `%2;nextdigit.txt;6b` - a single backreference wildcard which is made up of: - * `2` - the copy length (the length of the result after expansion) - * `nextdigit.txt` - the map file used determine how to modify characters - * `6` - how far to the left of the wildcard to start copying; 6 characters counting leftwards from the end of `00test` is the first `0` - - The result of expanding this wildcard when the token starts off with `00test` is `00test11`. It expands into *two* `1`'s because the copy length is 2, and it expands into modified `1`'s instead of just copying the `0`'s because the file maps a `0` (in its first column) to a `1` (in the second column). Likewise, a `77test` is expanded into `77test88`. `99test` is expanded into `99test99` because the the lookup character, a `9`, isn't present in (the first column of) the map file, and so it's copied unmodified. - -Note that when you use a map file inside a backreference wildcard, the file name always has a semicolon (`;`) on either side. These are all valid backreference wildcards (but they're all different because the have different copy lengths and starting positions): `%;file.txt;b`, `%2;file.txt;b`, `%;file.txt;6b`, `%2;file.txt;6b`. - -The final example involves something called keyboard walking. Consider a password pattern where a typist starts with any letter, and then chooses the next character by moving their finger using a particular pattern, for example by always going either diagonal up and right, or diagonal down and right, and then repeating until the result is a certain length. A single backreference wildcard that uses a map file can create this pattern. - -Here's what the beginning of a map file for this pattern, `pattern.txt`, would look like: - - q 2a - a wz - z s - 2 w - w 3s - ... - -So if the last letter is a `q`, the next letter in the pattern is either a `2` or an `a` (for going upper-right or lower-right). If the last letter is a `z`, there's only one direction available for the next letter, upper-right to `s`. With this map file, and the following token, all combinations which follow this pattern between 4 and 6 characters long would be tried: `%a%3,5;pattern.txt;b` - - -## The Passwordlist ## - -If you already have a simple list of whole passwords you'd like to test, and you don't need any of the features described above, you can use the `--passwordlist` command-line option (instead of the `--tokenlist` option as described later in the [Running *btcrecover*](#running-btcrecover) section). If your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII) (non-English) characters, you should read the section on [Unicode Support](#unicode-support) before continuing. - -If you specify `--passwordlist` without a file, *btcrecover* will prompt you to type in a list of passwords, one per line, in the Command Prompt window. If you already have a text file with the passwords in it, you can use `--passwordlist FILE` instead (replacing `FILE` with the file name). - -Be sure not to add any extra spaces, unless those spaces are actually a part of a password. - -Each line is used verbatim as a single password when using the `--passwordlist` option (and none of the features from above are applied). You can however use any of the Typos features described below to try different variations of the passwords in the passwordlist. - - -## Typos ## - -*btcrecover* can generate different variations of passwords to find typos or mistakes you may have inadvertently made while typing a password in or writing one down. This feature is enabled by including one or more command-line options when you run *btcrecover*. - -If you'd just like some specific examples of command-line options you can add, please see the [Typos Quick Start Guide](docs/Typos_Quick_Start_Guide.md). - -With the `--typos #` command-line option (with `#` replaced with a count of typos), you tell *btcrecover* up to how many typos you’d like it to add to each password (that has been either generated from a token file or taken from a passwordlist as described above). You must also specify the types of typos you’d like it to generate, and it goes through all possible combinations for you (including the no-typos-present possibility). Here is a summary of the basic types of typos along with the command-line options which enable each: - - * `--typos-capslock` - tries the whole password with caps lock turned on - * `--typos-swap` - swaps two adjacent characters - * `--typos-repeat` - repeats (doubles) a character - * `--typos-delete` - deletes a character - * `--typos-case` - changes the case (upper/lower) of a single letter - -For example, with `--typos 2 --typos-capslock --typos-repeat` options specified on the command line, all combinations containing up to two typos will be tried, e.g. `Cairo` (no typos), `cAIRO` (one typo: caps lock), `CCairoo` (two typos: both repeats), and `cAIROO` (two typos: one of each type) will be tried. Adding lots of typo types to the command line can significantly increase the number of combinations, and increasing the `--typos` count can be even more dramatic, so it’s best to tread lightly when using this feature unless you have a small token file or passwordlist. - -Here are some additional types of typos that require a bit more explanation: - - * `--typos-closecase` - Like `--typos-case`, but it only tries changing the case of a letter if that letter is next to another letter with a different case, or if it's at the beginning or the end. This produces fewer combinations to try so it will run faster, and it will still catch the more likely instances of someone holding down shift for too long or for not long enough. - - * `--typos-replace s` - This tries replacing each single character with the specified string (in the example, an `s`). The string can be a single character, or some longer string (in which case each single character is replaced by the entire string), or even a string with one or more [expanding wildcards](#expanding-wildcards) in it. For example, `--typos 1 --typos-replace %a` would try replacing each character (one at a time) with a lower-case letter, working through all possible combinations. Using wildcards can drastically increase the total number of combinations. - - * `--typos-insert s` - Just like `--typos-replace`, but instead of replacing a character, this tries inserting a single copy of the string (or the wildcard substitutions) in between each pair of characters, as well as at the beginning and the end. - - Even when `--typos` is greater than 1, `--typos-insert` will not normally try inserting multiple copies of the string at the same position. For example, with `--typos 2 --typos-insert Z` specified, guesses such as `CaiZro` and `CZairoZ` are tried, but `CaiZZro` is not. You can change this by using `--max-adjacent-inserts #` with a number greater than 1. - -#### Typos Map #### - - * `--typos-map typos.txt` - This is a relatively complicated, but also flexible type of typo. It tries replacing certain specific characters with certain other specific characters, using a separate file (in this example, named `typos.txt`) to spell out the details. For example, if you know that you often make mistakes with punctuation, you could create a typos-map file which has these two lines in it: - - . ,/; - ; [‘/. - - - In this example, *btcrecover* will try replacing each `.` with one of the three punctuation marks which follow the spaces on the same line, and it will try replacing each `;` with one of the four punctuation marks which follow it. - - This feature can be used for more than just typos. If for example you’re a fan of “1337” (leet) speak in your passwords, you could create a typos-map along these lines: - - aA @ - sS $5 - oO 0 - - This would try replacing instances of `a` or `A` with `@`, instances of `s` or `S` with either a `$` or a `5`, etc., up to the maximum number of typos specified with the `--typos #` option. For example, if the token file contained the token `Passwords`, and if you specified `--typos 3`, `P@55words` and `Pa$sword5` would both be tried because they each have three or fewer typos/replacements, but `P@$$w0rd5` with its 5 typos would not be tried. - - The *btcrecover* package includes a few typos-map example files in the `typos` directory. You can read more about them in the [Typos Quick Start Guide](docs/Typos_Quick_Start_Guide.md#typos-maps). - -### Max Typos by Type ### - -As described above, the `--typos #` command-line option limits the total number of typos, regardless of type, that will ever be applied to a single guess. You can also set limits which are only applied to specific types of typos. For each of the `--typos-xxxx` command-line options above there is a corresponding `--max-typos-xxxx #` option. - -For example, with `--typos 3 --typos-delete --typos-insert %a --max-typos-insert 1`, up to three typos will be tried. All of them could be delete typos, but at most only one will ever be an insert typo (which would insert a single lowercase letter in this case). This is particularly useful when `--typos-insert` and `--typos-replace` are used with wildcards as in this example, because it can greatly decrease the total number of combinations that need to be tried, turning a total number that would take far too long to test into one that is much more reasonable. - - -## Autosave ## - -Depending on the number of passwords which need to be tried, running *btcrecover* might take a very long time. If it is interrupted in the middle of testing (with Ctrl-C (see below), due to a reboot, accidentally closing the Command Prompt, or for any other reason), you might lose your progress and have to start the search over from the beginning. To safeguard against this, you can add the `--autosave savefile` option when you first start *btcrecover*. It will automatically save its progress about every 5 minutes to the file that you specify (in this case, it was named `savefile` – you can just make up any file name, as long as it doesn’t already exist). - -If interrupted, you can restart testing by either running it with the exact same options, or by providing this option and nothing else: `--restore savefile`. *btcrecover* will then begin testing exactly where it had left off. (Note that the token file, as well as the typos-map file, if used, must still be present and must be unmodified for this to work. If they are not present or if they’ve been changed, *btcrecover* will refuse to start.) - -The autosave feature is not currently supported with passwordlists, only with token files. - - -### Interrupt and Continue ### - -If you need to interrupt *btcrecover* in the middle of testing, you can do so with Ctrl-C (hold down the Ctrl key and press C) and it will respond with a message such this and then it will exit: - - Interrupted after finishing password # 357449 - -If you didn't have the autosave feature enabled, you can still manually start testing where you left off. You need to start *btcrecover* with the *exact same* token file or passwordlist, typos-map file (if you were using one), and command-line options plus one extra option, `--skip 357449`, and it will start up right where it had left off. - - -## Unicode Support ## - -If your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII#ASCII_printable_code_chart) (non-English) characters, you will need to add the `--utf8` command-line option to enable Unicode support. - -Please note that all input to and output from *btcrecover* must be UTF-8 encoded (either with or without a Byte Order Mark, or "BOM"), so be sure to change the Encoding to UTF-8 when you save any text files. For example in Windows Notepad, the file *Encoding* setting is right next to the *Save* button in the *File* -> *Save As...* dialog. - -On Windows (but usually not on Linux or OS X), you may have trouble if any of the command line options you need to use contain any non-ASCII characters. Usually, if it displays in the command prompt window correctly when you type it in, it will work correctly with `btcrecover.py`. If it doesn't display correctly, please read the section describing how to put [command-line options inside the tokens file](#command-line-options-inside-the-tokens-file). - -Also on Windows (but usually not on Linux or OS X), if your password is found it may not be displayed correctly in the command prompt window. Here is an example of what an incorrect output might look like: - - Password found: 'btcr-????-??????' - HTML encoded: 'btcr-тест-пароль' - -As you can see, the Windows command prompt was incapable of rendering some of the characters (and they were replaced with `?` characters). To view the password that was found, copy and paste the `HTML encoded` line into a text file, and save it with a name that ends with `.html` instead of the usual `.txt`. Double-click the new `.html` file and it will open in your web browser to display the correct password: - - HTML encoded: 'btcr-тест-пароль' - - -## Running *btcrecover* ## - -(Also see the [Quick Start](#quick-start) section.) After you've installed all of the requirements (above) and have downloaded the latest version: - - 1. Unzip the `btcrecover-master.zip` file, it contains a single directory named "btcrecover-master". Inside the btcrecover-master directory is the Python script (program) file `btcrecover.py`. - 2. **Make a copy of your wallet file** into the directory which contains `btcrecover.py`. On Windows, you can usually find your wallet file by clicking on the Start Menu, then “Run...” (or for Windows 8+ by holding down the *Windows* key and pressing `r`), and then typing in one of the following paths and clicking OK. Some wallet software allows you to create multiple wallets, for example Armory wallets have an ID which you can view in the Armory interface, and the wallet file names contain this ID. Of course, you need to be sure to copy the correct wallet file. - * Armory - `%appdata%\Armory` (it's a `.wallet` file) - * Bitcoin Unlimited/Classic/XT/Core - `%appdata%\Bitcoin` (it's named `wallet.dat`) - * Bitcoin Wallet for Android/BlackBerry, lost spending PINs - Please see the [Bitcoin Wallet for Android/BlackBerry Spending PINs](#bitcoin-wallet-for-androidblackberry-spending-pins) section below. - * MultiBit Classic - Please see the [Finding MultiBit Classic Wallet Files](#finding-multibit-classic-wallet-files) section below. - * MultiBit HD - `%appdata%\MultiBitHD` (it's in one of the folders here, it's named `mbhd.wallet.aes`) - * Electrum - `%appdata%\Electrum\wallets` - * BIP-39 passphrases (e.g. TREZOR) - Please see the [BIP-39 Passphrases](#bip-39-passphrases) section below. - * mSIGNA - `%homedrive%%homepath%` (it's a `.vault` file) - * Bither - `%appdata%\Bither` (it's named `address.db`) - * Blockchain.info - it's usually named `wallet.aes.json`; if you don't have a backup of your wallet file, you can download one by running the `download-blockchain-wallet.py` tool in the `extract-scripts` directory if you know your wallet ID (and 2FA if enabled) - * Litecoin-Qt - `%appdata%\Litecoin` (it's named `wallet.dat`) - 3. If you have a `btcrecover-tokens-auto.txt` file, you're almost done. Copy it into the directory which contains `btcrecover.py`, and then simply double-click the `btcrecover.py` file, and *btcrecover* should begin testing passwords. (You may need to rename your wallet file if it doesn't match the file name listed insided the `btcrecover-tokens-auto.txt` file.) If you don't have a `btcrecover-tokens-auto.txt` file, continue reading below. - 4. Copy your `tokens.txt` file, or your passwordlist file if you're using one, into the directory which contains `btcrecover.py`. - 5. You will need to run `btcrecover.py` with at least two command-line options, `--wallet FILE` to identify the wallet file name and either `--tokenlist FILE` or `--passwordlist FILE` (the FILE is optional for `--passwordlist`), depending on whether you're using a [Token File](#the-token-file) or [Passwordlist](#the-passwordlist). If you're using [Typos](#typos) or [Autosave](#autosave), please refer the sections above for additional options you'll want to add. - 6. Here's an example for both Windows and OS X. The details for your system will be different, for example the download location may be different, or the wallet file name may differ, so you'll need to make some changes. Any additional options are all placed at the end of the *btcrecover* line. - * *Windows*: Open a Command Prompt window (click the Start Menu and type "command"), and type in the two lines below. - - cd Downloads\btcrecover-master - C:\python27\python btcrecover.py --wallet wallet.dat --tokenlist tokens.txt [other-options...] - - * *OS X*: Open a terminal window (open the Launchpad and search for "terminal"), and type in the two lines below. - - cd Downloads/btcrecover-master - python btcrecover.py --wallet wallet.dat --tokenlist tokens.txt [other-options...] - -After a short delay, *btcrecover* should begin testing passwords and will display a progress bar and an ETA as shown below. If it appears to be stuck just counting upwards with the message `Counting passwords ...` and no progress bar, please read the [Memory limitations](docs/Limitations_and_Caveats.md#memory) section. If that doesn't help, then you've probably chosen too many tokens or typos to test resulting in more combinations than your system can handle (although the [`--max-tokens`](#token-counts) option may be able to help). - - Counting passwords ... - Done - Using 4 worker threads - 439 of 7661527 [-------------------------------] 0:00:10, ETA: 2 days, 0:25:56 - -If one of the combinations is the correct password for the wallet, the password will eventually be displayed and *btcrecover* will stop running: - - 1298935 of 7661527 [####-----------------------] 8:12:42, ETA: 1 day, 16:13:24 - Password found: 'Passwd42' - -If all of the password combinations are tried, and none of them were correct for the wallet, this message will be dislayed instead: - - 7661527 of 7661527 [########################################] 2 days, 0:26:06, - Password search exhausted - -Running `btcrecover.py` with the `--help` option will give you a summary of all of the available command-line options, most of which are described in the sections above. - -### Testing your config ### - -If you'd just like to test your token file and/or chosen typos, you can use the `--listpass` option in place of the `--wallet FILE` option as demonstrated below. *btcrecover* will then list out all the passwords to the screen instead of actually testing them against a wallet file. This can also be useful if you have another tool which can test some other type of wallet, and is capable of taking a list of passwords to test from *btcrecover*. Because this option can generate so much output, you may want only use it with short token files and few typo options. - - C:\python27\python btcrecover.py --listpass --tokenlist tokens.txt | more - -The `| more` at the end (the `|` symbol is a shifted `\` backslash) will introduce a pause after each screenful of passwords. - -### Finding MultiBit Classic Wallet Files ### - -*btcrecover* doesn’t operate directly on MultiBit Classic wallet files, instead it operates on MultiBit private key backup files. When you first add a password to your MultiBit Classic wallet, and after that each time you add a new receiving address or change your wallet password, MultiBit creates an encrypted private key backup file in a `key-backup` directory that's near the wallet file. These private key backup files are much faster to try passwords against (by a factor of over 1,000), which is why *btcrecover* uses them. For the default wallet that is created when MultiBit is first installed, this directory is located here: - - %appdata%\MultiBit\multibit-data\key-backup - -The key files have names which look like `walletname-20140407200743.key`. If you've created additional wallets, their `key-backup` directories will be located elsewhere and it's up to you to locate them. Once you have, choose the most recent `.key` file and copy it into the directory containing `btcrecover.py` for it to use. - -For more details on locating your MultiBit private key backup files, see: - -### Bitcoin Wallet for Android/BlackBerry Spending PINs ### - -Bitcoin Wallet for Android/BlackBerry has a *spending PIN* feature which can optionally be enabled. If you lose your spending PIN, you can use *btcrecover* to try to recover it. - - 1. Open the Bitcoin Wallet app, press the menu button, and choose Safety. - 2. Choose *Back up wallet*. - 3. Type in a password to protect your wallet backup file, and press OK. You'll need to remember this password for later. - 4. Press the Archive button in the lower-right corner. - 5. Select a method of sharing the wallet backup file with your PC, for example you might choose Gmail or perhaps Drive. - -This wallet backup file, once saved to your PC, can be used just like any other wallet file in *btcrecover* with one important exception: when you run *btcrecover*, you **must** add the `--android-pin` option. When you do, *btcrecover* will ask you for your backup password (from step 3), and then it will try to recover the spending PIN. - -Because PINs usually just contain digits, your token file will usually just contain something like this (for PINs of up to 6 digits for example): `%1,6d`. (See the section on [Wildcards](#expanding-wildcards) for more details.) - -Note that if you don't include the `--android-pin` option, *btcrecover* will try to recover the backup password instead. - -### BIP-39 Passphrases ### - -Some [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) compliant wallets offer a feature to add a “BIP-39” or “plausible deniability” passphrase to your seed (mnemonic), most notably the TREZOR hardware wallet. (Note that most hardware wallets also offer a PIN feature which is not supported by *btcrecover*.) - -If you know your seed, but don't remember this passphrase, *btcrecover* may be able to help. You will also need to know either: - - 1. Preferably your master public key / “xpub” (for the *first* account in your wallet, if it supports multiple accounts), *or* - 2. a receiving address that was generated by your wallet (in its first account), along with a good estimate of how many addresses you created before the receiving address you'd like to use. - -Once you have this information, run *btcrecover* normally, except that *instead* of providing a wallet file on the command line as described above with the `--wallet wallet.dat` option, use the `--bip39` option, e.g.: - - C:\python27\python btcrecover.py --bip39 --tokenlist tokens.txt [other-options...] - -If you have an Ethereum seed, also add the `--wallet-type ethereum` option. When you run this, you will be prompted for your master public key (or your address), and your seed. - -**Note** that *btcrecover* assumes your wallet software is using both the BIP-39 the BIP-44 standards. If your wallet is not strictly complaint with these standards, *btcrecover* will probably not work correctly to find your passphrase. It may be possible to use the `--bip32-path` option to work correctly with a wallet using different standards—feel free to open an [issue on GitHub](https://github.com/gurnec/btcrecover/issues/new) if you're unsure of your wallet's compatibility with *btcrecover*. - -### GPU acceleration for Bitcoin Unlimited/Classic/XT/Core, Armory, and Litecoin-Qt wallets ### - -*btcrecover* includes experimental support for using one or more graphics cards or dedicated accelerator cards to increase search performance. This can offer on the order of *100x* better performance with Bitcoin Unlimited/Classic/XT/Core or Litecoin-Qt wallets when enabled and correctly tuned. With Armory (which uses a GPU-resistant key derivation function), this can offer a modest improvement of 2x - 5x. - -For more information, please see the [GPU Acceleration Guide](docs/GPU_Acceleration.md). - -### command-line options inside the tokens file ### - -If you'd prefer, you can also place command-line options directly inside the `tokens.txt` file. In order to do this, the very first line of the tokens file must begin with exactly `#--`, and the rest of this line (and only this line) is interpreted as additional command-line options. For example, here's a tokens file which enables autosave, pause-before-exit, and one type of typo: - - #--autosave progress.sav --pause --typos 1 --typos-case - Cairo - Beetlejuice Betelgeuse - Hotel_california - -### btcrecover-tokens-auto.txt ### - -Normally, when you run *btcrecover* it expects you to run it with at least a few options, such as the location of the tokens file and of the wallet file. If you run it without specifying `--tokenlist` or `--passwordlist`, it will check to see if there is a file named `btcrecover-tokens-auto.txt` in the current directory, and if found it will use that for the tokenlist. Because you can specify options inside the tokenlist file if you'd prefer (see above), this allows you to run *btcrecover* without using the command line at all. You may want to consider using the `--pause` option to prevent a Command Prompt window from immediately closing once it's done running if you decide to run it this way. - - -# Limitations & Caveats # - -### Beta Software ### - -Although this software is unlikely to harm any wallet files, **you are strongly encouraged to only run it with copies of your wallets**. In particular, this software is distributed **WITHOUT ANY WARRANTY**; please see the accompanying GPLv2 licensing terms for more details. - -Because this software is beta software, and also because it interacts with other beta software, it’s entirely possible that it may fail to find a password which it’s been correctly configure by you to find. - -### Additional Limitations & Caveats ### - -Please see the separate [Limitations and Caveats](docs/Limitations_and_Caveats.md) documentation for additional details on these topics: - - * Delimiters, Spaces, and Special Symbols in Passwords - * Memory & CPU Usage - * Security Issues - * Typos Details - - -# Copyright and License # - -btcrecover -- Bitcoin wallet password and seed recovery tool - -Copyright (C) 2014-2017 Christopher Gurnee - -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..c74188174 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/addressdb-checklists/BCH.txt b/addressdb-checklists/BCH.txt new file mode 100644 index 000000000..8cdbcbd97 --- /dev/null +++ b/addressdb-checklists/BCH.txt @@ -0,0 +1,27 @@ +qp64jf0pehlqm750nwm50x22zypm464emuyqcltfxu #First seen 2021-12-31 +qzent8tadjpz8vwpqlhf8w04twk2g8vygsdwuy5mr0 #First seen 2021-06-30 +qz05vtxaqnc2s6l0fa7yxnphed9463efauq0f3wr2v #First seen 2020-12-31 +qzhr2tm6xlldj5uje56zrtuawr5hmdsjhgcr3ct900 #First seen 2020-06-30 +qrrzj93vgcl34qmvx9tmwkde3m46g43lsv59aflkqq #First seen 2019-12-31 +ppafawqkmy3sksexphgagnusr4w058kd9c48va326l #First seen 2019-06-30 +qp4dp8aujf89nwe0k9jgeuchk3rg8sy8yuqx3zwkjv #First seen 2018-12-31 +qp64g9u3vrm8qqcetuh5r4phngpjtyy67csv0tsr7m #First seen 2018-06-30 +qzcmum9xcsqq8z3plcs4ffp7gneq2xge9u2p99qvqs #First seen 2017-11-29 +1KCVRqCwe2qmp7o6HpjGXd4S5HAivuW1Gc #First seen 2017-06-24 +17A16QmavnUfCW11DAApiJxp7ARnxN5pGX #First seen 2016-12-12 +1JrNPxbrPFEMPsvdg5ZHUbtbTjSK9vcYwf #First seen 2016-05-20 +11fN7ygwdVVimUp5wPK5RGJRiGdTmUfCP #First seen 2015-12-15 +1A4i946kq8jf24FWaRfDayrsoR2Wg3zU8u #First seen 2015-05-30 +1BbNrDHzyHgapMo76cPt3SEdvEY4ydXju7 #First seen 2015-01-01 +18RB8gmWeHhxpVGBn4SzJrh6s3kXrUkbDu #First seen 2014-06-21 +14CVLsuFowCeoimPMc36KvWtCYPRnQ7i7q #First seen 2014-01-02 +12D2TrbNUk9yfT3w3A1hv9brVaRwqbXWZv #First seen 2013-06-12 +1Gvgbo6wvFZRRGCr9SpdUyyTSRtfjVFTTZ #First seen 2013-01-04 +1Dy36NcYoMLcy1dGncJPMR7m6n382vEoCr #First seen 2012-05-21 +156wLanDk1tVVqug42seGNEb4KfPRMYurA #First seen 2012-01-07 +146ZsxzqNjkeEBS71rXH2q4yi7ztxsoemP #First seen 2011-06-10 +1MvHUYctH9NqsrKh2srxP1RU4UPuA3huZ7 #Frist seen 2010-12-29 +1BQLm936mZ3VktjuuoN27dEYcUFWSqXfYh #First seen 2010-06-09 +1GE6A6MxPQ6hgGWbNermze8ez2U7FsEq23 #First seen 2010-01-16 +1LkBxT5avahfuKSwnKiFG9kKnLqpEGoJcK #First seen 2009-06-11 +12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX #First seen 2009-01-09 \ No newline at end of file diff --git a/addressdb-checklists/BSC.txt b/addressdb-checklists/BSC.txt new file mode 100644 index 000000000..ee618c754 --- /dev/null +++ b/addressdb-checklists/BSC.txt @@ -0,0 +1,20 @@ +0x799EC7A1558110d2956527878A9044c973b927fb #First seen May-29-2022 +0x8e2dc920e4deed172755892344b4f6d59e677c2e #First seen April-25-2022 +0xD5d98c4De531900Aec4FB67b0A8e8987bF1c3823 #First seen Mar-21-2022 +0x1ecCd85c9E21f247FadB70F6cFf94B14cb737d03 #First seen Feb-24-2022 +0xcCA3c17A5EDb7DE9fD89FB5553F5fb3125C1ca0C #First seen Jan-15-2022 +0x1903460aAfbEcBD1f37E365cd830e2Ee6Ae922bA #First seen Dec-15-2021 +0xff631E016c56B34f899B613983feD254ea02691b #First seen Nov-06-2021 +0x5E5aE19CF99485060E291f7E665a241f9932b353 #First seen Oct-09-2021 +0x984d528536ff561e731257195c8efbc4fd07ea3d #First seen Sept-10-2021 +0x277e0fce53806f1fe30d905d5c4346a2263c432e #First seen Aug-25-2021 +0x0658243D9FC864C8957A22370C0Bd6e859915c2A #First seen Jul-16-2021 +0x113e56eadAEF51F4F91C9ACFe477B23a037eC04F #First seen on Jun-18-2021 +0x3a0B0a015bDAEf8E60173a4998e96c9f9152aee4 #First seen on May-05-2021 +0x6aB1aeAF11784223A84BF3D14F489dAa301B6636 #First seen on Apr-12-2021 +0x53fc018b0f7672eb28f658203c8c313e4b39d660 #First seen on Mar-06-2021 +0x2810621e5f7c341459910dd77EbE9592332Fdc72 #First seen on Feb-10-2021 +0xb0b6cdbb4d9c5d5906004d30f36416cb94734dcb #First seen on Jan-15-2021 +0x6f4c90EE8300816af81E1a1F5c95e9FE2F55b84d #First seen Dec-11-2020 +0x1ef2607fea56b8247f86d4fb69d17d54e1f29cbb #First seen Nov-06-2020 +0x6db1a3dbb95874b1840f37f1844729b6d4872b75 #First seen Oct-8-2020 diff --git a/addressdb-checklists/BTC.txt b/addressdb-checklists/BTC.txt new file mode 100644 index 000000000..051a1e4c3 --- /dev/null +++ b/addressdb-checklists/BTC.txt @@ -0,0 +1,28 @@ +33uwGmLpms7tXQvxF5SLMX2ER5SZdnfSFZ #First Seen 2022-05-13 +1DEhCfQxLb1CvNQD6vBHtu4CkZLEeGp2Bi #First seen 2021-12-31 +bc1q2jhx8p4me4qfp2xcraengrky4gvr9t9lyxj8w3 #First seen 2021-06-24 +3GZoy4haRCWH1t4k4FaRd7bip92RJrr7Z9 #First seen 2021-01-11 +3B3Cx5Zdq5QvQXkJoHSovXQPK1s8LTFNWk #First seen 2020-06-05 +1Hbr6KsgqZFTWo7mYtSYN5akyYh5ZgGx1F #First seen 2020-01-13 +bc1q6hhj2glq940v2mk458pwa7d69g7ts3ycdyhmqg #First seen 2019-06-27 +1QJYjSsuPP8WRcJ2anP597NPBpWSfTS8ZV #First seen 2019-01-08 +3Fea9Ppvi6vu2eDRyjEupK9i3XeN9q9BME #First seen 2018-06-15 +14XZ3qYT7PZQBzWzvNX7ftTSDxA48Qr7LL #First seen 2017-12-09 +1KCVRqCwe2qmp7o6HpjGXd4S5HAivuW1Gc #First seen 2017-06-24 +17A16QmavnUfCW11DAApiJxp7ARnxN5pGX #First seen 2016-12-12 +1JrNPxbrPFEMPsvdg5ZHUbtbTjSK9vcYwf #First seen 2016-05-20 +11fN7ygwdVVimUp5wPK5RGJRiGdTmUfCP #First seen 2015-12-15 +1A4i946kq8jf24FWaRfDayrsoR2Wg3zU8u #First seen 2015-05-30 +1BbNrDHzyHgapMo76cPt3SEdvEY4ydXju7 #First seen 2015-01-01 +18RB8gmWeHhxpVGBn4SzJrh6s3kXrUkbDu #First seen 2014-06-21 +14CVLsuFowCeoimPMc36KvWtCYPRnQ7i7q #First seen 2014-01-02 +12D2TrbNUk9yfT3w3A1hv9brVaRwqbXWZv #First seen 2013-06-12 +1Gvgbo6wvFZRRGCr9SpdUyyTSRtfjVFTTZ #First seen 2013-01-04 +1Dy36NcYoMLcy1dGncJPMR7m6n382vEoCr #First seen 2012-05-21 +156wLanDk1tVVqug42seGNEb4KfPRMYurA #First seen 2012-01-07 +146ZsxzqNjkeEBS71rXH2q4yi7ztxsoemP #First seen 2011-06-10 +1MvHUYctH9NqsrKh2srxP1RU4UPuA3huZ7 #Frist seen 2010-12-29 +1BQLm936mZ3VktjuuoN27dEYcUFWSqXfYh #First seen 2010-06-09 +1GE6A6MxPQ6hgGWbNermze8ez2U7FsEq23 #First seen 2010-01-16 +1LkBxT5avahfuKSwnKiFG9kKnLqpEGoJcK #First seen 2009-06-11 +12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX #First seen 2009-01-09 \ No newline at end of file diff --git a/addressdb-checklists/DOGE.txt b/addressdb-checklists/DOGE.txt new file mode 100644 index 000000000..00607477d --- /dev/null +++ b/addressdb-checklists/DOGE.txt @@ -0,0 +1,15 @@ +DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T #First Seen 2021-01-31 +DFgLZmxFnzv2wR4GAGS3GeHvoEeSkz9ubU #First seen 2020-06-29 +DKTHYZwd81xT7qJF33YRi7ftDkvouGdxxN #First seen 2019-12-30 +DPPg5BVqn7Ck5YVf6ei7NbXGVPDSzXnCBL #First seen 2019-05-17 +DBbTFW9PZJj9EsXu5Ji59Tp6ZdKNrTZmWq #First seen 2018-12-05 +DFJRDVzjk7NPbApWsLDreML7RDawp8UmoF #First seen 2018-05-16 +D9dWXJjYb4HDrXpdef234GHDDggrnGsfxm #First seen 2017-11-05 +D6A894uLhQjwSRpEroPMop4MPpUL4BZZHc #First seen 2017-05-19 +DGVxem7KdNBCJWygpRcypS5pMJgJVRZEXD #First seen 2016-12-25 +DMPHyer3WdKrSmwmFarXtXCxbbp4BMwo9J #First seen 2016-05-22 +DRusoAd1Q9PJq3KpkhXjpZAoCqdQzGS6AH #First seen 2015-12-29 +D6sxvQRSriU4pkozdYxDVRKRmoRYCVmqKv #First seen 2015-05-10 +DNULsd2gbojENHtRRx45PUWvPgkrbL2vjE #First seen 2014-12-15 +D5mrYgNeLwVXFyy9t9NhBpTqVaa58gUYAC #First seen 2014-04-29 +DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8 #First seen 2013-12-07 \ No newline at end of file diff --git a/addressdb-checklists/ETH.txt b/addressdb-checklists/ETH.txt new file mode 100644 index 000000000..713d43297 --- /dev/null +++ b/addressdb-checklists/ETH.txt @@ -0,0 +1,22 @@ +0xA7E325d91311aCa6db9c405edAc46EB1Be77e183 #First seed May-13-2025 +0xB0158EF4cb3293dd1d2897aF805Aa2F96586c1CA #ERC20 Transactions Only (No Eth) +0x0Bfd366A8C800C61ca76B6C2866Cc0BEf2f01ef9 #First seen Apr-24-2024 +0xc9e6dBcE15E0fAcBd20ce2bbF4ecBab8C5Dd767C #First seen Sep-26-2023 +0x0584Da0B6cf278B6750dcF9Fa52e564737FdE5c7 #First seen Apr-07-2023 +0xf0A7d5075313264664A023D0fECDAB42a1885Dcc #First seen Dec-16-2022 +0xD5d98c4De531900Aec4FB67b0A8e8987bF1c3823 #First seen Mar-21-2022 +0x277e0fce53806f1fe30d905d5c4346a2263c432e #First seen Sep-18-2021 +0xeF208E0a5BA682217c6AA1f2c73648d179f97Def #First seen May-08-2021 +0xcc74efce9ebbcaf13bf12f04452c1678b258b98e #First seen Jan-13-2021 +0x75B1eE233D5360F96FA5801A276c789BDE675EfE #First seen Jul-30-2020 +0x6632f3597f84a3bf345a1c5ea542ca0288d69445 #First seen Jan-11-2020 +0xacab2ba32583b8c64314fdeee6528136f77743f6 #First seen Dec-25-2019 +0x8f2b36b7fe7e406e988b6caa0de249ae864faf86 #First seen Jun-30-2019 +0xaea0d17f97089f341f942192287a7ba502afb5fd #First seen Jan-13-2019 +0x759c4d89d392ab058d4005de0df38284da61a46a #First seen Jul-30-2018 +0xb8cbcb38f3a57219c3bb2878dbb3b6f82dba7f1d #First seen Jan-05-2018 +0xbf1a6ecca2330466893ffc993c4f9c73b705e01c #First seen Jun-24-2017 +0x762b1983972be34fa8496faf643aefdac9ce0458 #First seen Jan-08-2017 +0xcf981ef75770821bd1ce0358f439176a17067d6e #First seen Jun-24-2016 +0x8764b360076809bba4635c4281c3f44c1677d013 #First seen Nov-06-2015 +0x5df9b87991262f6ba471f09758cde1c0fc1de734 #First seen Aug-07-2015 \ No newline at end of file diff --git a/addressdb-checklists/LTC.txt b/addressdb-checklists/LTC.txt new file mode 100644 index 000000000..cd24e8844 --- /dev/null +++ b/addressdb-checklists/LTC.txt @@ -0,0 +1,23 @@ +ltc1qnpcyhxj77qt389yt2g4x3n4cnhncyk68vrrcwr #First Seen 2022-11-18 +MQmE5smps9ZhXsuqZLRFzFkDP3hnTVCvbd #First Seen 2022-05-03 +LRc7Zj6xnc9frXrWSwcPibpgEz5jQqP8vB #First Seen 2021-11-30 +MCspT7zn9k3bJmpWYJrA84MUx6ArB2Gx98 #First Seen 2021-05-29 +M9Yvo8pTPpw7GraMAozAYNLksuy5YFdCTG #First Seen 2020-12-29 +LWWyigp3sX9kGBEhTPV5q3t8Ls9pCYTahQ #First Seen 2020-05-29 +Ldo1YuJ1gNf74ASDM9RLnpfhvD9vxvajrc #First Seen 2019-12-26 +MPoP4AwYPfr7u9aMJg25CFWXj2kH1C9cYe #First Seen 2019-05-28 +MWgjVcJfaDSGLjKJUyP76hUqwsqEZnstkB #First Seen 2018-12-26 +LVuaMvZDKeLibdQqfhRMx1q1ggLf512JHp #First Seen 2018-06-15 +LgAEimffRTc64ZMNKusRV1kPu6BKniVYfq #First Seen 2017-12-27 +LXdkCPbZK2oMVEh7LAFTqE1GS9WDSF443r #First Seen 2017-06-11 +La2GGJcsKtEWThYyd8nB3fJbhhx1JJ97Zu #First Seen 2016-12-09 +LKLKdATWNxxfAZ2Avw6sFVia4D4rEvyA8r #First Seen 2016-05-11 +LMuQP2Fh1GE9pNk4n9E3FXyRzK1knYPzke #First Seen 2016-01-24 +LWWruj8396xXuF9shG2G6fTHsWceeApEvs #First Seen 2015-06-18 +LThzGATfLdSHhnmXo8i7uTDoEwChSUwCRM #First Seen 2014-12-11 +LgrLPtgXy5SyJ8tKd1WinJdK83f3m8WdMv #First Seen 2014-06-21 +LKgSrkm9pVXiipVBUt3bnwAjHDL2NPZ77e #First Seen 2014-01-02 +LcWswmy8dTe8tFLqzZFSousLmQkGJMuaBQ #First Seen 2013-06-13 +LLhcJ96X3h6Kh66FuGZuFcjvyRcYv5xSJA #First Seen 2012-12-14 +LKramjSJDSaLVyEn2sn1dUAB2bVra3KdSA #First Seen 2012-06-10 +LYMu658aDbntgTBz77bF6upvCxz65p95kp #First Seen 2011-12-15 \ No newline at end of file diff --git a/addressdb-checklists/TRX.txt b/addressdb-checklists/TRX.txt new file mode 100644 index 000000000..500b4824f --- /dev/null +++ b/addressdb-checklists/TRX.txt @@ -0,0 +1,14 @@ +TVnD7BWqzuBdcJwQZ9zgBf1dYy1tv44wqa # 2025-05-14 +TZ3GsXLaaLdUBngaZFtrmBdjdwiwL5Nwyz # 2025-05-07 (Tokens only, no TRX) +TGTv7v2Jm96fFkaAWjgEYFKyG61YZv3dW8 # 2024-01-25 16:51:30 +TT4gTiH5RszUB8rWHfZQ84oC3gaT4Mxrmx # 2023-11-26 14:53:12 +TT2T17KZhoDu47i2E4FWxfG79zdkEWkU9N # 2023-05-01 12:09:54 +TASUAUKXCqvwYjesEWv22pFjRsCeF4NKot # 2023-01-13 06:49:36 +TYYbRhLzw3WbAMsnErpLpLrFVA1vvAa5Gk # 2022-11-04 21:37:33 +TWnHGFEjgrJpHXPiPnddPtdCwKGMwpxK8T # 2022-04-03 17:31:48 +TJNtFduS4oebw3jgGKCYmgSpTdyPieb6Ha # 2021-11-08 15:56:57 +TPyjyZfsYaXStgz2NmAraF1uZcMtkgNan5 # 2021-05-27 13:48:24 +TDirN49rpXgs2RXJUudbcRskhoyD29c66U # 2020-02-18 20:28:30 +TERLhS5VxDNbFkxj1FnzeTa3KYRpXNVWKx # 2019-04-20 03:19:33 +TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb # 2018-11-15 09:43:00 +TRUhDn6wpLvYQZN7okfayo2yH54ToLrSoa # 2018-07-02 15:06:42 diff --git a/benchmark-lists/Electrum-tokenlist-50million.txt b/benchmark-lists/Electrum-tokenlist-50million.txt new file mode 100644 index 000000000..d868ba098 --- /dev/null +++ b/benchmark-lists/Electrum-tokenlist-50million.txt @@ -0,0 +1,12 @@ +^1^book +^2^fit +fly +ketchup +also +elevator +scout +mind +edit +fatal +where +rookie \ No newline at end of file diff --git a/benchmark-lists/bip39-12-tokenlist-50million.txt b/benchmark-lists/bip39-12-tokenlist-50million.txt new file mode 100644 index 000000000..67daa023c --- /dev/null +++ b/benchmark-lists/bip39-12-tokenlist-50million.txt @@ -0,0 +1,12 @@ +^1^ocean +^2^hidden +kidney +famous +rich +season +gloom +husband +spring +convince +attitude +boy \ No newline at end of file diff --git a/benchmark-lists/bip39-24-tokenlist-50million.txt b/benchmark-lists/bip39-24-tokenlist-50million.txt new file mode 100644 index 000000000..5f520443b --- /dev/null +++ b/benchmark-lists/bip39-24-tokenlist-50million.txt @@ -0,0 +1,24 @@ +^1^meadow +^2^expand +^3^spy +^4^toe +^5^filter +^6^imitate +^7^carbon +^8^host +^9^regular +^10^social +^11^album +^12^kitchen +^13^reunion +^14^stable +minimum +case +nest +pioneer +roof +budget +penalty +this +answer +finish \ No newline at end of file diff --git a/benchmark-lists/cmd.txt b/benchmark-lists/cmd.txt new file mode 100644 index 000000000..f86be287b --- /dev/null +++ b/benchmark-lists/cmd.txt @@ -0,0 +1,31 @@ +BIP39-12 Standard: (CPU 33kps, GPU 170kps, SaveChecksummed 180kps, LoadChecksummed 10.5kps) ****Optimal**** +python3 seedrecover.py --mnemonic "ocean hidden kidney famous rich season gloom husband spring convince zoo x" --wallet-type bip39 --addr-limit 1 --no-eta --no-dupchecks --dsw --big-typos 2 --bip32-path m/44'/0'/0' --enable-opencl --addrs 13yneuv16TrPnmCiWfcUfp2JV21WhxkvTE + +BIP39-12-Tokenlist (CPU 33kps, GPU 130kps, SaveChecksummed 220kps, LoadChecksummed 10.5kps) ---Bad Generator Bottlekneck--- +python3 seedrecover.py --no-eta --tokenlist "./benchmark-lists/bip39-12-tokenlist-50million.txt" --dsw --mnemonic-length 12 --language en --wallet-type bip39 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --addr-limit 1 --bip32-path "m/44'/0'/0'/0" --enable-opencl + +Electrum Standard: (CPU 200, GPU 366kps, SaveChecksummed 375kps, LoadChecksummed 10.5) ---Generator Bottlekneck--- +python3 seedrecover.py --mnemonic "ocean hidden kidney famous rich season gloom husband spring convince zoo x" --wallet-type Electrum2 --addr-limit 1 --no-eta --no-dupchecks --dsw --big-typos 2 --bip32-path m/0' --enable-opencl --addrs 13yneuv16TrPnmCiWfcUfp2JV21WhxkvTE + +Electrum-Tokenlist (CPU 200, GPU 300+, SaveChecksummed 375, LoadChecksummed 10.) +python3 seedrecover.py --no-eta --tokenlist "./benchmark-lists/bip39-12-tokenlist-50million.txt" --dsw --mnemonic-length 12 --language en --wallet-type Electrum2 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --addr-limit 1 --bip32-path "m/0'" --enable-opencl + +BIP39-24 Standard: (CPU 160kps, GPU 225kps, SaveChecksummed 200kps, LoadChecksummed 7.1kps) ---Generator Bottlekneck--- +python3 seedrecover.py --mnemonic "trim skill build repair either cricket vocal riot vast wink engage puzzle poem bike wall joke august size improve note smooth rubber zoo x" --wallet-type bip39 --addr-limit 1 --no-eta --no-dupchecks --dsw --big-typos 2 --bip32-path m/44'/0'/0' --enable-opencl --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU + +BIP39-24-Tokenlist (CPU 140kps, GPU 160kps, SaveChecksummed 175kps, LoadChecksummed 7.1kps) ---Bad Generator Bottlekneck--- +python3 seedrecover.py --no-eta --tokenlist "./benchmark-lists/bip39-24-tokenlist-50million.txt" --dsw --mnemonic-length 24 --language en --wallet-type bip39 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --addr-limit 1 --bip32-path "m/44'/0'/0'/0" --enable-opencl + +Seed (24 word): +trim skill build repair either cricket vocal riot vast wink engage puzzle poem bike wall joke august size improve note smooth rubber zero artwork + +Produce: +python3 seedrecover.py --mnemonic "trim skill build repair either cricket vocal riot vast wink engage puzzle poem bike wall joke august size improve note smooth rubber x x" --wallet-type bip39 --dsw --big-typos 2 --savevalidseeds d:\validseeds.txt + +python3 seedrecover.py --mnemonic "trim skill build repair either cricket vocal riot vast wink engage puzzle poem bike wall joke august size improve note smooth x x x" --wallet-type bip39 --dsw --big-typos 3 --savevalidseeds d:\validseeds.txt + +Consume: +python3 seedrecover.py --no-eta --mnemonic-length 24 --language en --addrs 1QLSbWFtVNnTFUq5vxDRoCpvvsSqTTS88P --addr-limit 1 --wallet-type bip39 --dsw --skip-worker-checksum --enable-opencl --seedlist d:\validseeds.txt + + + diff --git a/benchmark-lists/tokenlist1800k.txt b/benchmark-lists/tokenlist1800k.txt new file mode 100644 index 000000000..72030caef --- /dev/null +++ b/benchmark-lists/tokenlist1800k.txt @@ -0,0 +1,10 @@ +test +word +- _ ! = @ +- _ ! = @ +- _ ! = @ +2012 2013 2014 2015 2016 2017 2018 2019 2020 +^1^btcr +longlonglonglonglonglongwordwordwordwordwordbreakbreakbreakbreak +testpasswordlongtestpasswordlongtestpasswordlongtestpasswordlongtestpasswordlongtestpasswordlongtestpasswordlong +pass \ No newline at end of file diff --git a/benchmark-lists/tokenlist80k.txt b/benchmark-lists/tokenlist80k.txt new file mode 100644 index 000000000..15f7495a6 --- /dev/null +++ b/benchmark-lists/tokenlist80k.txt @@ -0,0 +1,7 @@ +test +pass +word +- _ ! = @ +- _ ! = @ +2012 2013 2014 2015 2016 2017 2018 2019 2020 +^1^btcr \ No newline at end of file diff --git a/btcrecover.py b/btcrecover.py index 84da3d174..3076a10e0 100755 --- a/btcrecover.py +++ b/btcrecover.py @@ -25,33 +25,70 @@ # PYTHON_ARGCOMPLETE_OK - enables optional bash tab completion -from __future__ import print_function +import compatibility_check -from btcrecover import btcrpass +from btcrecover import btcrpass, success_alert import sys, multiprocessing if __name__ == "__main__": + print() + print( + "Starting", + btcrpass.full_version(), + file=sys.stderr if any(a.startswith("--listp") for a in sys.argv[1:]) else sys.stdout, + ) # --listpass - print("Starting", btcrpass.full_version(), - file=sys.stderr if any(a.startswith("--listp") for a in sys.argv[1:]) else sys.stdout) # --listpass btcrpass.parse_arguments(sys.argv[1:]) (password_found, not_found_msg) = btcrpass.main() - if isinstance(password_found, basestring): + if isinstance(password_found, str): + success_alert.start_success_beep() + print() + print( + "If this tool helped you to recover funds, please consider donating 1% of what you recovered, in your crypto of choice to:" + ) + print("BTC: 37N7B7sdHahCXTcMJgEnHz7YmiR4bEqCrS ") + print("BCH: qpvjee5vwwsv78xc28kwgd3m9mnn5adargxd94kmrt ") + print("LTC: M966MQte7agAzdCZe5ssHo7g9VriwXgyqM ") + print("ETH: 0x72343f2806428dbbc2C11a83A1844912184b4243 ") + + # print("VTC: vtc1qxauv20r2ux2vttrjmm9eylshl508q04uju936n ") + # print("ZEN: znUihTHfwm5UJS1ywo911mdNEzd9WY9vBP7 ") + # print("DASH: Xx2umk6tx25uCWp6XeaD5f7CyARkbemsZG ") + # print("DOGE: DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T ") + # print("XMR: 48wnuLYsPY7ewLQyF4RLAj3N8CHH4oBBcaoDjUQFiR4VfkgPNYBh1kSfLx94VoZSsGJnuUiibJuo7FySmqroAi6c1MLWHYF ") + # print("MONA: mona1q504vpcuyrrgr87l4cjnal74a4qazes2g9qy8mv ") + # print("XVG: DLZDT48wfuaHR47W4kU5PfW1JfJY25c9VJ") + print() + print("Find me on Reddit @ https://www.reddit.com/user/Crypto-Guide") + print() + print( + "You may also consider donating to Gurnec, who created and maintained this tool until late 2017 @ 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4" + ) + print() btcrpass.safe_print("Password found: '" + password_found + "'") if any(ord(c) < 32 or ord(c) > 126 for c in password_found): - print("HTML encoded: '" + password_found.encode("ascii", "xmlcharrefreplace") + "'") + print( + "HTML Encoded Password: '" + + password_found.encode("ascii", "xmlcharrefreplace").decode() + + "'" + ) + success_alert.wait_for_user_to_stop() retval = 0 elif not_found_msg: print(not_found_msg, file=sys.stderr if btcrpass.args.listpass else sys.stdout) + success_alert.beep_failure_once() retval = 0 else: + success_alert.beep_failure_once() retval = 1 # An error occurred or Ctrl-C was pressed # Wait for any remaining child processes to exit cleanly (to avoid error messages from gc) for process in multiprocessing.active_children(): process.join(1.0) + success_alert.stop_success_beep() + sys.exit(retval) diff --git a/btcrecover/addressset.py b/btcrecover/addressset.py index 96a7882b3..a65084188 100644 --- a/btcrecover/addressset.py +++ b/btcrecover/addressset.py @@ -1,5 +1,6 @@ # addressset.py -- btcrecover AddressSet library # Copyright (C) 2017 Christopher Gurnee +# 2019-2021 Stephen Rothery # # This file is part of btcrecover. # @@ -16,34 +17,42 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! -# (all optional futures for 2.7 except unicode_literals) -from __future__ import print_function, absolute_import, division +__version__ = "1.13.0-CryptoGuide" -__version__ = "0.1.3" - -import struct, base64, io, mmap, ast, itertools, sys, gc, glob +import binascii +import struct, base64, io, mmap, ast, itertools, sys, gc, glob, math, os +from typing import Optional from os import path +from datetime import datetime + +import lib.bitcoinlib as bitcoinlib +from lib.base58_tools import base58_tools + +def supportedChains(magic): + switcher={ + b"\xf9\xbe\xb4\xd9":1, #BTC Main Net Magic (Also shared by BCH, BSV, etc) + b"\xbf\x0c\x6b\xbd":0, #Dash + b"\xfb\xc0\xb6\xdb":1, #Litecoin, Monacoin, + b"\xfa\xbf\xb5\xda":1, #Vertcoin + b"\xf7\xa7\x7e\xff":0, #Verge + b"\xc0\xc0\xc0\xc0":0, #dogecoin + b"\xfa\xc3\xb6\xda":1, #digibyte + b"\xf9\xbe\xb4\xd4":1 #groestlcoin + } + return switcher.get(magic,-1) + def bytes_to_int(bytes_rep): - """convert a string of bytes (in big-endian order) to an integer + """Convert a sequence of bytes (big-endian) to an integer. :param bytes_rep: the raw bytes - :type bytes_rep: str + :type bytes_rep: bytes :return: the unsigned integer - :rtype: int or long + :rtype: int """ - bytes_len = len(bytes_rep) - if bytes_len <= 4: - return struct.unpack(">I", (4-bytes_len)*b"\0" + bytes_rep)[0] - return long(base64.b16encode(bytes_rep), 16) + return int.from_bytes(bytes_rep, "big") class AddressSet(object): @@ -71,6 +80,7 @@ def __init__(self, table_len, bytes_per_addr = 8, max_load = 0.75): raise ValueError("bytes_per_addr must be between 1 and 19 inclusive") if not 0.0 < max_load < 1.0: raise ValueError("max_load must be between 0.0 and 1.0 exclusive") + self._dbLength = table_len self._table_bytes = table_len * bytes_per_addr # len of hash table in bytes self._bytes_per_addr = bytes_per_addr # number of bytes per address to store self._null_addr = b"\0" * bytes_per_addr # all 0s is an empty hash table slot @@ -86,6 +96,11 @@ def __init__(self, table_len, bytes_per_addr = 8, max_load = 0.75): raise ValueError("not enough bytes for both hashing and storage; " "reduce either the bytes_per_addr or table_len") + if table_len > 1000 : #only display this if we are creating an addressDB + # Print Timestamp that this step occured + print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ", end="") + print("Creating Address Database with room for", self._max_len, "addresses") + def __getstate__(self): # mmaps can't be pickled, so save only what's needed to recreate the object from scratch later if isinstance(self._data, mmap.mmap): @@ -109,19 +124,43 @@ def __len__(self): def __contains__(self, address): return self._find(address) is True - def add(self, address): - """Adds the address to the set + def add(self, address, textAddresses = False, addressType = None, coin = 0): + """Adds the address to the set or outputs it to a text file :param address: the address in hash160 (length 20) format to add - :type address: bytes or str + :textAddresses address: whether to dump the address to text too + :addressType: only used for formatting the text representation of the address + :coin: only used for formatting the text representation of the address (currently unused) """ - pos = self._find(address) - if pos is not True: + + #Check Address Type and convert to bytes if in str format. (Keeps unit tests working as-is in python 3) + if type(address) is str : + address = address.encode() + + pos = self._find(address) #Check to see if the address is already in the addressDB + if pos is not True: #If the address isn't in the DB, add it + if textAddresses: + if addressType == 'Bech32': + print(bitcoinlib.encoding.pubkeyhash_to_addr_bech32(address),file=open("addresses.txt", "a")) + if addressType == 'Bech32m': + print(bitcoinlib.encoding.pubkeyhash_to_addr_bech32(address, witver = 1),file=open("addresses.txt", "a")) + elif addressType == 'P2SH': + print(bitcoinlib.encoding.pubkeyhash_to_addr_base58(address, b'\x05'),file=open("addresses.txt", "a")) + elif addressType == 'P2PKH': + print(bitcoinlib.encoding.pubkeyhash_to_addr_base58(address),file=open("addresses.txt", "a")) + bytes_to_add = address[ -(self._bytes_per_addr+self._hash_bytes) : -self._hash_bytes] if bytes_to_add.endswith(self._null_addr): return # ignore these invalid addresses - if self._len >= self._max_len: - raise ValueError("addition to AddressSet exceeds load factor") + if self._len >= self._max_len: #If load factor is exceeded, exit and display a helpful error message... + print() + print() + print("*****AddressDB Creation Failed*****") + print() + print("Offline Blockchain too large for AddressDB File... It might work if you retry and increase --dblength value by 1, though this will double the size of the file and RAM required to create it... (eg: 30 => 8GB required space and RAM) dblength for this run was:",int(math.log(self._dbLength,2))) + print("Alternatily you can use --blocks-startdate and --blocks-enddate to narrow the date range to check") + exit() #DB Creation Failed, exit the program... + self._data[pos : pos+self._bytes_per_addr] = bytes_to_add self._len += 1 @@ -133,6 +172,11 @@ def add(self, address): # causing different addresses to appear to be the same and false positives, however # (with high probability) only for invalid addresses (those w/o private keys). def _find(self, addr_to_find): + + #Check Address Type and convert to bytes if in str format (Keeps unit tests working as-is in python 3) + if type(addr_to_find) is str : + addr_to_find = addr_to_find.encode() + pos = self._bytes_per_addr * (bytes_to_int(addr_to_find[ -self._hash_bytes :]) & self._hash_mask) while True: cur_addr = self._data[pos : pos+self._bytes_per_addr] @@ -173,8 +217,8 @@ def _header(self): header_dict = self.__dict__.copy() self._remove_nonheader_attribs(header_dict) header_dict["version"] = self.VERSION - header = repr(header_dict) + b"\r\n" - assert ast.literal_eval(header) == header_dict + header = repr(header_dict).encode() + b"\r\n" + assert ast.literal_eval(header.decode()) == header_dict header = self.MAGIC + header header_len = len(header) assert header_len < self.HEADER_LEN @@ -224,7 +268,7 @@ def fromfile(cls, dbfile, mmap_access = mmap.ACCESS_READ, preload = True): magic_len = len(cls.MAGIC) config_end = header.find(b"\0", magic_len, cls.HEADER_LEN) assert config_end > 0 - config = ast.literal_eval(header[magic_len:config_end]) + config = ast.literal_eval(header[magic_len:config_end].decode()) if config["version"] != cls.VERSION: raise ValueError("can't load address database version {} (only supports {})" .format(config["version"], cls.VERSION)) @@ -235,21 +279,44 @@ def fromfile(cls, dbfile, mmap_access = mmap.ACCESS_READ, preload = True): for attr in self.__dict__.keys(): # only load expected attributes from untrusted data self.__dict__[attr] = config[attr] self._mmap_access = mmap_access - # - # The hash table is memory-mapped directly from the file instead of being loaded - self._data = mmap.mmap(dbfile.fileno(), self._table_bytes, access=mmap_access, - offset= header_pos + cls.HEADER_LEN) + + #Try to create the AddressDB. If the addresset is sufficiently large (eg: BTC) then this requires 64 bit python and will crash if attempted with 32 bit Python... + mmap_fileno = dbfile.fileno() + dup_fileno = None + close_original = mmap_access != mmap.ACCESS_WRITE + if close_original: + try: + dup_fileno = os.dup(mmap_fileno) + mmap_fileno = dup_fileno + except OSError: + dup_fileno = None + close_original = False + try: + # + # The hash table is memory-mapped directly from the file instead of being loaded + self._data = mmap.mmap(mmap_fileno, self._table_bytes, access=mmap_access, + offset= header_pos + cls.HEADER_LEN) + except OverflowError: + print() + exit("AddressDB too large for use with 32 bit Python. You will need to install a 64 bit (x64) version of Python 3 from python.org and try again") + except Exception: + if dup_fileno is not None: + os.close(dup_fileno) + raise + if dup_fileno is not None: + os.close(dup_fileno) if mmap_access == mmap.ACCESS_WRITE: dbfile.seek(header_pos) # prepare for writing an updated header in close() else: - dbfile.close() + if close_original: + dbfile.close() self._dbfile = dbfile # # Most of the time it makes sense to load the file serially instead of letting # the OS load each page as it's touched in random order, especially with HDDs; # reading a byte from each page is sufficient (CPython doesn't optimize this away) if preload: - for i in xrange(self._table_bytes // mmap.PAGESIZE): + for i in range(self._table_bytes // mmap.PAGESIZE): self._data[i * mmap.PAGESIZE] # return self @@ -276,7 +343,7 @@ def __del__(self): # Decodes a Bitcoin-style variable precision integer and # returns a tuple containing its value and incremented offset def varint(data, offset): - b = ord(data[offset]) + b = data[offset] if b <= 252: return b, offset + 1 if b == 253: @@ -287,8 +354,14 @@ def varint(data, offset): return struct.unpack_from(" bytes: + if not key or not data: + return data + m = len(key) + return bytes(b ^ key[(offset + i) % m] for i, b in enumerate(data)) -def create_address_db(dbfilename, blockdir, update = False, progress_bar = True): +def create_address_db(dbfilename, blockdir, table_len, startBlockDate="2019-01-01", endBlockDate="3000-12-31", startBlockFile = 0, addressDB_yolo = False, outputToText = False, update = False, progress_bar = True, addresslistfile = None, multiFile = False, forcegzip = False): """Creates an AddressSet database and saves it to a file :param dbfilename: the file name where the database is saved (overwriting it) @@ -301,10 +374,14 @@ def create_address_db(dbfilename, blockdir, update = False, progress_bar = True) :type progress_bar: bool """ - for filename in glob.iglob(path.join(blockdir, "blk*.dat")): - if path.isfile(filename): break - else: - raise ValueError("no block files exist in blocks directory '{}'".format(blockdir)) + if multiFile: + print("**********************************************") + print("The --multiFile argument is now obselete") + print() + print("The new way to handle multiple address lists to place them all in a folder and then pass the path to this folder with the --inputlistfile argument") + print("**********************************************") + + exit() if update: print("Loading address database ...") @@ -312,11 +389,17 @@ def create_address_db(dbfilename, blockdir, update = False, progress_bar = True) first_filenum = address_set.last_filenum print() else: - first_filenum = 0 + first_filenum = startBlockFile + + if not addresslistfile: + for filename in glob.iglob(path.join(blockdir, "blk*.dat")): + if path.isfile(filename): break + else: + raise ValueError("no block files exist in blocks directory '{}'".format(blockdir)) - filename = "blk{:05}.dat".format(first_filenum) - if not path.isfile(path.join(blockdir, filename)): - raise ValueError("first block file '{}' doesn't exist in blocks directory '{}'".format(filename, blockdir)) + filename = "blk{:05}.dat".format(first_filenum) + if not path.isfile(path.join(blockdir, filename)): + raise ValueError("first block file '{}' doesn't exist in blocks directory '{}'".format(filename, blockdir)) if not update: # Open the file early to make sure we can, but don't overwrite it yet @@ -325,91 +408,264 @@ def create_address_db(dbfilename, blockdir, update = False, progress_bar = True) dbfile = io.open(dbfilename, "r+b") except IOError: dbfile = io.open(dbfilename, "wb") - # With the default bytes_per_addr and max_load, this allocates - # about 4 GiB which is room for a little over 400 million addresses - address_set = AddressSet(1 << 29) - if progress_bar: + #Try to create the AddressDB. If the addresset is sufficiently large (eg: BTC) then this requires 64 bit python and will crash if attempted with 32 bit Python... try: - import progressbar - except ImportError: - progress_bar = False + # With the default bytes_per_addr and max_load, this allocates + # about 8 GiB which is room for a little over 800 million addresses (Required as of 2019) + address_set = AddressSet(1 << table_len) + except OverflowError: + print() + exit("AddressDB too large for use with 32 bit Python. You will need to install a 64 bit (x64) version of Python 3 from python.org and try again") + + if addresslistfile: + import btcrecover.btcrseed + import pathlib + print("Initial AddressDB Contains", len(address_set), "Addresses") + + # Check whether we are looking at a file or a folder + fileList = pathlib.Path(addresslistfile) + if fileList.is_dir(): + fileList = fileList.iterdir() + else: + fileList = [fileList] + + # Iterate through the list files + for addresslistfile in fileList: + # Check whether we are parsing a compressed list or raw text + import gzip + if addresslistfile.suffix == ".gz" or forcegzip: + flexibleOpen = gzip.open + else: + flexibleOpen = open + + try: + # Open the file + print("Loading: ", addresslistfile) + with flexibleOpen(addresslistfile) as addressList_file: + + addresses_loaded = 0 + for address in addressList_file: + try: + # Check whether it's bytes or string (This will be different depending on whether we are looking at a .gz file or plain text) + try: + address = address.decode() + except (UnicodeDecodeError, AttributeError): + pass + + # Strip any and handle JSON data present for some cryptos in data exported from bigquery + if (address[2:11] == 'addresses'): + address = address[15:-4] + + # Ignore nonce and balance in Eth tsv address lists exported from blockchair + addressparts = address.split(" ") + if (len(addressparts) == 3 and len(addressparts[0]) == 40): #Looks like an Eth address + address = '0x' + addressparts[0] + elif (len(addressparts) == 2): #Probably a Bitcoin Address + address = addressparts[0] + + + #if wallet_type == : + # cur_hash160 = binascii.unhexlify(address.rstrip()[2:]) + # address_set.add(cur_hash160) + # addresses_loaded += 1 + #print(address) + #exit() + #else: + # Infer the address type based on the address formatting (This isn't ideal but is good enough for now) + if(address[0:2] != '0x') and (len(address.rstrip()) != 40): + address_set.add(btcrecover.btcrseed.WalletBase._addresses_to_hash160s([address.rstrip()]).pop()) + else: + address_set.add(btcrecover.btcrseed.WalletEthereum._addresses_to_hash160s([address.rstrip()]).pop()) + addresses_loaded += 1 + + + if(addresses_loaded % 1000000 == 0): + print("Checked:", addresses_loaded, "addresses in current file,", len(address_set), "in unique Hash160s in AddressDB") + + except Exception as e: + print("Skipping Invalid Line: ", address.rstrip(), e) + + print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), " ", end="") + print("Checked:", addresses_loaded, "addresses in current file,", len(address_set), + "in unique Hash160s in AddressDB") + + except Exception as e: + print("Error loading file: ", e) + + print("Finished: ", addresslistfile) + + print("Finished AddressDB Contains", len(address_set), "Addresses") - if progress_bar: - print("Parsing block files ...") + else: + if progress_bar: + try: + import progressbar + except ImportError: + progress_bar = False + + xor_detected = False + xor_filename = path.join(blockdir, "xor.dat") # since Bitcoin Core v28 + xor_key = b"\0\0\0\0\0\0\0\0" + if path.isfile(xor_filename): + print("Xor.dat detected") + with open(xor_filename, "rb") as xf: + xor_key = xf.read(8) + + if len(xor_key) == 8 and xor_key != b"\0\0\0\0\0\0\0\0": + xor_detected = True + print("XOR-key: ", ''.join(f'\\x{byte:02x}' for byte in xor_key)) + + if progress_bar: + print("Parsing block files ...") + for filenum in itertools.count(first_filenum): + filename = path.join(blockdir, "blk{:05}.dat".format(filenum)) + if not path.isfile(filename): + break + progress_label = progressbar.FormatLabel(" {:11,} addrs. %(elapsed)s, ".format(len(address_set))) + block_bar_widgets = [progressbar.SimpleProgress(), " ", + progressbar.Bar(left="[", fill="-", right="]"), + progress_label, + progressbar.ETA()] + progress_bar = progressbar.ProgressBar(maxval=filenum-first_filenum, widgets=block_bar_widgets) + progress_bar.start() + else: + print("Started Timestamp Block file Address Count Last Block DateTime") + print("------------------- ------------ ------------- -------------------") + # e.g. blk00943.dat 255,212,706 + + exit_after_lastblockdate = False for filenum in itertools.count(first_filenum): filename = path.join(blockdir, "blk{:05}.dat".format(filenum)) if not path.isfile(filename): break - progress_label = progressbar.FormatLabel(" {:11,} addrs. %(elapsed)s, ".format(len(address_set))) - progress_bar = progressbar.ProgressBar(maxval=filenum-first_filenum, widgets=[ - progressbar.SimpleProgress(), " ", - progressbar.Bar(left="[", fill="-", right="]"), - progress_label, - progressbar.ETA() - ]) - progress_bar.start() - else: - print("Block file Address count") - print("------------ -------------") - # e.g. blk00943.dat 255,212,706 - - for filenum in itertools.count(first_filenum): - filename = path.join(blockdir, "blk{:05}.dat".format(filenum)) - if not path.isfile(filename): - break - address_set.last_filenum = filenum - - with open(filename, "rb") as blockfile: - if not progress_bar: - print(path.basename(filename), end=" ") - - header = blockfile.read(8) # read in the magic and remaining (after these 8 bytes) block length - while len(header) == 8 and header[4:] != b"\0\0\0\0": - assert header[:4] == b"\xf9\xbe\xb4\xd9" # magic - - block = blockfile.read(struct.unpack_from(" OP_EQUALVERIFY OP_CHECKSIG) - if pkscript_len == 25 and block[offset:offset+3] == b"\x76\xa9\x14" and block[offset+23:offset+25] == b"\x88\xac": - # Add the discovered address to the address set - address_set.add(block[offset+3:offset+23]) - - offset += pkscript_len # advances past the pubkey script - if is_bip144: - for txin_num in xrange(txin_count): - stackitem_count, offset = varint(block, offset) - for stackitem_num in xrange(stackitem_count): - stackitem_len, offset = varint(block, offset) - offset += stackitem_len # skips this stack item - offset += 4 # skips the 4-byte locktime - header = blockfile.read(8) # read in the next magic and remaining block length + address_set.last_filenum = filenum + + with open(filename, "rb") as blockfile: + if not progress_bar: + # Print Timestamp that this step occured + print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), " ", end="") + print(path.basename(filename), end=" ") + + if xor_detected: + hdr_off = blockfile.tell() + header = blockfile.read(8) # read in the magic and remaining (after these 8 bytes) block length + if xor_detected: + header = xor_at(header, xor_key, hdr_off) + chain_magic = header[:4] + #print("Found Magic:", chain_magic.encode("hex")) + while len(header) == 8 and header[4:] != b"\0\0\0\0": + if supportedChains(chain_magic) != 1: # Check magic to see if it is a chain we support + if not addressDB_yolo: #Ignore checks on the blockchain type + #Throw an error message and exit if we encounter unsupported magic value + if supportedChains(chain_magic) == -1: + print("Unrecognised Block Protocol (Unrecognised Magic), Found:", chain_magic, " You can force an AddressDB creation attempt by re-running this tool with the flag --dbyolo") + + if supportedChains(chain_magic) == 0: + print("Incompatible Block Protocol, You can force an AddressDB creation attempt by re-running this tool with the flag --dbyolo, but it probably won't work") + + exit() + + if xor_detected: + blk_off = blockfile.tell() + block = blockfile.read(struct.unpack_from("= blockDate: + + for tx_num in range(tx_count): + #txDataPlus = block[offset:offset + 100] + offset += 4 # skips 4-byte tx version + is_bip144 = block[offset] == 0 # bip-144 marker + if is_bip144: + offset += 2 # skips 1-byte marker & 1-byte flag + txin_count, offset = varint(block, offset) + for txin_num in range(txin_count): + sigscript_len, offset = varint(block, offset + 36) # skips 32-byte tx id & 4-byte tx index + offset += sigscript_len + 4 # skips sequence number & sigscript + txout_count, offset = varint(block, offset) + for txout_num in range(txout_count): + pkscript_len, offset = varint(block, offset + 8) # skips 8-byte satoshi count + + # If this is a P2PKH script (OP_DUP OP_HASH160 PUSH(20) <20 address bytes> OP_EQUALVERIFY OP_CHECKSIG) + if pkscript_len == 25 and block[offset:offset+3] == b"\x76\xa9\x14" and block[offset+23:offset+25] == b"\x88\xac": + address_set.add(block[offset+3:offset+23],outputToText,'P2PKH') + elif block[offset:offset+2] == b"\xa9\x14": #Check for Segwit Address + address_set.add(block[offset+2:offset+22],outputToText,'P2SH') + elif block[offset:offset+2] == b"\x00\x14": #Check for Native Segwit Address + address_set.add(block[offset+2:offset+22],outputToText,'Bech32') + elif block[offset:offset+2] == b"\x51\x20": #Check for Taproot Address + address_set.add(block[offset + 2:offset + 34], outputToText, 'Bech32m') + + offset += pkscript_len # advances past the pubkey script + if is_bip144: + for txin_num in range(txin_count): + try: + stackitem_count, offset = varint(block, offset) + for stackitem_num in range(stackitem_count): + stackitem_len, offset = varint(block, offset) + offset += stackitem_len # skips this stack item + except IndexError: + # There was a odd transaction on the LTC network that seemed to break the parsing, TXID:49d38dd2978f1f402c62a6791893557da86cc939eabc6710bbf733333fe42667 + print("Skipping Transaction with Unknown Script Type") # Occasionally BIP144 transactions will have issues being parsed by the code above. + + offset += 4 # skips the 4-byte locktime + + if xor_detected: + hdr_off = blockfile.tell() + header = blockfile.read(8) # read in the next magic and remaining block length + if xor_detected: + header = xor_at(header, xor_key, hdr_off) + + if blockDate > datetime.strptime(endBlockDate + " 23:59:59", '%Y-%m-%d %H:%M:%S'): + # force exit + header = b"\0\0" + exit_after_lastblockdate = True + + if progress_bar: + block_bar_widgets[3] = progressbar.FormatLabel(" {:11,} addrs. %(elapsed)s, ".format(len(address_set))) # updates address count + nextval = progress_bar.currval + 1 + if nextval > progress_bar.maxval: # can happen if the bitcoin client is left running + progress_bar.maxval = nextval + progress_bar.update(nextval) + else: + print(" {:13,}".format(len(address_set)), end="") + print(" ", blockDate) + + if exit_after_lastblockdate: + break if progress_bar: - progress_label.format = " {:11,} addrs. %(elapsed)s, ".format(len(address_set)) # updates address count - nextval = progress_bar.currval + 1 - if nextval > progress_bar.maxval: # can happen if the bitcoin client is left running - progress_bar.maxval = nextval - progress_bar.update(nextval) - else: - print("{:13,}".format(len(address_set))) - - if progress_bar: - progress_bar.widgets.pop() # remove the ETA - progress_bar.finish() + progress_bar.widgets.pop() # remove the ETA + progress_bar.finish() if update: print("\nSaving changes to address database ...") address_set.close() @@ -419,4 +675,6 @@ def create_address_db(dbfilename, blockdir, update = False, progress_bar = True) address_set.tofile(dbfile) dbfile.close() + # Print Timestamp that this step occured + print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ", end="") print("\nDone.") diff --git a/btcrecover/aezeed.py b/btcrecover/aezeed.py new file mode 100644 index 000000000..77daded7d --- /dev/null +++ b/btcrecover/aezeed.py @@ -0,0 +1,848 @@ +"""Utilities for working with aezeed mnemonics.""" + +from __future__ import annotations + +import hashlib +from dataclasses import dataclass +from typing import Iterable, List, Sequence, Tuple + +__all__ = [ + "DEFAULT_PASSPHRASE", + "DecipheredCipherSeed", + "InvalidMnemonicError", + "InvalidPassphraseError", + "decode_mnemonic", + "mnemonic_to_bytes", + "validate_mnemonic", +] + +DEFAULT_PASSPHRASE = "aezeed" + +EncipheredCipherSeedSize = 33 +DecipheredCipherSeedSize = 19 +SaltSize = 5 +CipherTextExpansion = 4 +BitsPerWord = 11 +CipherSeedVersion = 0 + + +class AezeedError(Exception): + """Base class for aezeed errors.""" + + +class InvalidMnemonicError(AezeedError): + """Raised when the mnemonic words fail basic validation.""" + + +class InvalidPassphraseError(AezeedError): + """Raised when decryption fails due to an incorrect passphrase.""" + + +@dataclass(frozen=True) +class DecipheredCipherSeed: + entropy: bytes + salt: bytes + internal_version: int + birthday: int + + +_CRC32C_POLY = 0x82F63B78 + + +def _crc32c_table() -> List[int]: + table: List[int] = [] + for i in range(256): + crc = i + for _ in range(8): + if crc & 1: + crc = (crc >> 1) ^ _CRC32C_POLY + else: + crc >>= 1 + table.append(crc & 0xFFFFFFFF) + return table + + +_CRC32C_TABLE = _crc32c_table() + + +def _crc32c(data: bytes) -> int: + crc = 0xFFFFFFFF + for b in data: + crc = _CRC32C_TABLE[(crc ^ b) & 0xFF] ^ (crc >> 8) + return (~crc) & 0xFFFFFFFF + + +BLOCK_SIZE = 16 +EXTRACTED_KEY_SIZE = 3 * BLOCK_SIZE + + +def _mk_block(size: int = BLOCK_SIZE) -> bytearray: + return bytearray(size) + + +ZERO_BLOCK = _mk_block() + + +def _xor_bytes1x16(a: Sequence[int], b: Sequence[int], dst: bytearray) -> None: + for i in range(BLOCK_SIZE): + dst[i] = a[i] ^ b[i] + + +def _xor_bytes4x16( + a: Sequence[int], b: Sequence[int], c: Sequence[int], d: Sequence[int], dst: bytearray +) -> None: + for i in range(BLOCK_SIZE): + dst[i] = a[i] ^ b[i] ^ c[i] ^ d[i] + + +def _xor_bytes(a: Sequence[int], b: Sequence[int], dst: bytearray) -> None: + for i in range(len(dst)): + dst[i] = a[i] ^ b[i] + + +def _uint32(i: int) -> int: + return i & 0xFFFFFFFF + + +def _uint8(i: int) -> int: + return i & 0xFF + + +def _extract_key(key: bytes) -> bytes: + if len(key) == EXTRACTED_KEY_SIZE: + return key + return hashlib.blake2b(key, digest_size=EXTRACTED_KEY_SIZE).digest() + + +def _mult_block(x: int, src: Sequence[int], dst: bytearray) -> None: + t = _mk_block() + r = _mk_block() + t[:] = src + while x: + if x & 1: + _xor_bytes1x16(r, t, r) + _double_block(t) + x >>= 1 + dst[:] = r + + +def _double_block(p: bytearray) -> None: + tmp = p[0] + for i in range(15): + p[i] = ((p[i] << 1) | (p[i + 1] >> 7)) & 0xFF + p[15] = ((p[15] << 1) & 0xFE) ^ (0x87 if tmp & 0x80 else 0) + + +def _one_zero_pad(src: Sequence[int], size: int, dst: bytearray) -> None: + dst[:] = b"\x00" * len(dst) + if size: + dst[:size] = src[:size] + dst[size] = 0x80 + + + +TE0 = [ + 0XC66363A5, 0XF87C7C84, 0XEE777799, 0XF67B7B8D, 0XFFF2F20D, 0XD66B6BBD, 0XDE6F6FB1, 0X91C5C554, + 0X60303050, 0X02010103, 0XCE6767A9, 0X562B2B7D, 0XE7FEFE19, 0XB5D7D762, 0X4DABABE6, 0XEC76769A, + 0X8FCACA45, 0X1F82829D, 0X89C9C940, 0XFA7D7D87, 0XEFFAFA15, 0XB25959EB, 0X8E4747C9, 0XFBF0F00B, + 0X41ADADEC, 0XB3D4D467, 0X5FA2A2FD, 0X45AFAFEA, 0X239C9CBF, 0X53A4A4F7, 0XE4727296, 0X9BC0C05B, + 0X75B7B7C2, 0XE1FDFD1C, 0X3D9393AE, 0X4C26266A, 0X6C36365A, 0X7E3F3F41, 0XF5F7F702, 0X83CCCC4F, + 0X6834345C, 0X51A5A5F4, 0XD1E5E534, 0XF9F1F108, 0XE2717193, 0XABD8D873, 0X62313153, 0X2A15153F, + 0X0804040C, 0X95C7C752, 0X46232365, 0X9DC3C35E, 0X30181828, 0X379696A1, 0X0A05050F, 0X2F9A9AB5, + 0X0E070709, 0X24121236, 0X1B80809B, 0XDFE2E23D, 0XCDEBEB26, 0X4E272769, 0X7FB2B2CD, 0XEA75759F, + 0X1209091B, 0X1D83839E, 0X582C2C74, 0X341A1A2E, 0X361B1B2D, 0XDC6E6EB2, 0XB45A5AEE, 0X5BA0A0FB, + 0XA45252F6, 0X763B3B4D, 0XB7D6D661, 0X7DB3B3CE, 0X5229297B, 0XDDE3E33E, 0X5E2F2F71, 0X13848497, + 0XA65353F5, 0XB9D1D168, 0X00000000, 0XC1EDED2C, 0X40202060, 0XE3FCFC1F, 0X79B1B1C8, 0XB65B5BED, + 0XD46A6ABE, 0X8DCBCB46, 0X67BEBED9, 0X7239394B, 0X944A4ADE, 0X984C4CD4, 0XB05858E8, 0X85CFCF4A, + 0XBBD0D06B, 0XC5EFEF2A, 0X4FAAAAE5, 0XEDFBFB16, 0X864343C5, 0X9A4D4DD7, 0X66333355, 0X11858594, + 0X8A4545CF, 0XE9F9F910, 0X04020206, 0XFE7F7F81, 0XA05050F0, 0X783C3C44, 0X259F9FBA, 0X4BA8A8E3, + 0XA25151F3, 0X5DA3A3FE, 0X804040C0, 0X058F8F8A, 0X3F9292AD, 0X219D9DBC, 0X70383848, 0XF1F5F504, + 0X63BCBCDF, 0X77B6B6C1, 0XAFDADA75, 0X42212163, 0X20101030, 0XE5FFFF1A, 0XFDF3F30E, 0XBFD2D26D, + 0X81CDCD4C, 0X180C0C14, 0X26131335, 0XC3ECEC2F, 0XBE5F5FE1, 0X359797A2, 0X884444CC, 0X2E171739, + 0X93C4C457, 0X55A7A7F2, 0XFC7E7E82, 0X7A3D3D47, 0XC86464AC, 0XBA5D5DE7, 0X3219192B, 0XE6737395, + 0XC06060A0, 0X19818198, 0X9E4F4FD1, 0XA3DCDC7F, 0X44222266, 0X542A2A7E, 0X3B9090AB, 0X0B888883, + 0X8C4646CA, 0XC7EEEE29, 0X6BB8B8D3, 0X2814143C, 0XA7DEDE79, 0XBC5E5EE2, 0X160B0B1D, 0XADDBDB76, + 0XDBE0E03B, 0X64323256, 0X743A3A4E, 0X140A0A1E, 0X924949DB, 0X0C06060A, 0X4824246C, 0XB85C5CE4, + 0X9FC2C25D, 0XBDD3D36E, 0X43ACACEF, 0XC46262A6, 0X399191A8, 0X319595A4, 0XD3E4E437, 0XF279798B, + 0XD5E7E732, 0X8BC8C843, 0X6E373759, 0XDA6D6DB7, 0X018D8D8C, 0XB1D5D564, 0X9C4E4ED2, 0X49A9A9E0, + 0XD86C6CB4, 0XAC5656FA, 0XF3F4F407, 0XCFEAEA25, 0XCA6565AF, 0XF47A7A8E, 0X47AEAEE9, 0X10080818, + 0X6FBABAD5, 0XF0787888, 0X4A25256F, 0X5C2E2E72, 0X381C1C24, 0X57A6A6F1, 0X73B4B4C7, 0X97C6C651, + 0XCBE8E823, 0XA1DDDD7C, 0XE874749C, 0X3E1F1F21, 0X964B4BDD, 0X61BDBDDC, 0X0D8B8B86, 0X0F8A8A85, + 0XE0707090, 0X7C3E3E42, 0X71B5B5C4, 0XCC6666AA, 0X904848D8, 0X06030305, 0XF7F6F601, 0X1C0E0E12, + 0XC26161A3, 0X6A35355F, 0XAE5757F9, 0X69B9B9D0, 0X17868691, 0X99C1C158, 0X3A1D1D27, 0X279E9EB9, + 0XD9E1E138, 0XEBF8F813, 0X2B9898B3, 0X22111133, 0XD26969BB, 0XA9D9D970, 0X078E8E89, 0X339494A7, + 0X2D9B9BB6, 0X3C1E1E22, 0X15878792, 0XC9E9E920, 0X87CECE49, 0XAA5555FF, 0X50282878, 0XA5DFDF7A, + 0X038C8C8F, 0X59A1A1F8, 0X09898980, 0X1A0D0D17, 0X65BFBFDA, 0XD7E6E631, 0X844242C6, 0XD06868B8, + 0X824141C3, 0X299999B0, 0X5A2D2D77, 0X1E0F0F11, 0X7BB0B0CB, 0XA85454FC, 0X6DBBBBD6, 0X2C16163A, +] + +TE1 = [ + 0XA5C66363, 0X84F87C7C, 0X99EE7777, 0X8DF67B7B, 0X0DFFF2F2, 0XBDD66B6B, 0XB1DE6F6F, 0X5491C5C5, + 0X50603030, 0X03020101, 0XA9CE6767, 0X7D562B2B, 0X19E7FEFE, 0X62B5D7D7, 0XE64DABAB, 0X9AEC7676, + 0X458FCACA, 0X9D1F8282, 0X4089C9C9, 0X87FA7D7D, 0X15EFFAFA, 0XEBB25959, 0XC98E4747, 0X0BFBF0F0, + 0XEC41ADAD, 0X67B3D4D4, 0XFD5FA2A2, 0XEA45AFAF, 0XBF239C9C, 0XF753A4A4, 0X96E47272, 0X5B9BC0C0, + 0XC275B7B7, 0X1CE1FDFD, 0XAE3D9393, 0X6A4C2626, 0X5A6C3636, 0X417E3F3F, 0X02F5F7F7, 0X4F83CCCC, + 0X5C683434, 0XF451A5A5, 0X34D1E5E5, 0X08F9F1F1, 0X93E27171, 0X73ABD8D8, 0X53623131, 0X3F2A1515, + 0X0C080404, 0X5295C7C7, 0X65462323, 0X5E9DC3C3, 0X28301818, 0XA1379696, 0X0F0A0505, 0XB52F9A9A, + 0X090E0707, 0X36241212, 0X9B1B8080, 0X3DDFE2E2, 0X26CDEBEB, 0X694E2727, 0XCD7FB2B2, 0X9FEA7575, + 0X1B120909, 0X9E1D8383, 0X74582C2C, 0X2E341A1A, 0X2D361B1B, 0XB2DC6E6E, 0XEEB45A5A, 0XFB5BA0A0, + 0XF6A45252, 0X4D763B3B, 0X61B7D6D6, 0XCE7DB3B3, 0X7B522929, 0X3EDDE3E3, 0X715E2F2F, 0X97138484, + 0XF5A65353, 0X68B9D1D1, 0X00000000, 0X2CC1EDED, 0X60402020, 0X1FE3FCFC, 0XC879B1B1, 0XEDB65B5B, + 0XBED46A6A, 0X468DCBCB, 0XD967BEBE, 0X4B723939, 0XDE944A4A, 0XD4984C4C, 0XE8B05858, 0X4A85CFCF, + 0X6BBBD0D0, 0X2AC5EFEF, 0XE54FAAAA, 0X16EDFBFB, 0XC5864343, 0XD79A4D4D, 0X55663333, 0X94118585, + 0XCF8A4545, 0X10E9F9F9, 0X06040202, 0X81FE7F7F, 0XF0A05050, 0X44783C3C, 0XBA259F9F, 0XE34BA8A8, + 0XF3A25151, 0XFE5DA3A3, 0XC0804040, 0X8A058F8F, 0XAD3F9292, 0XBC219D9D, 0X48703838, 0X04F1F5F5, + 0XDF63BCBC, 0XC177B6B6, 0X75AFDADA, 0X63422121, 0X30201010, 0X1AE5FFFF, 0X0EFDF3F3, 0X6DBFD2D2, + 0X4C81CDCD, 0X14180C0C, 0X35261313, 0X2FC3ECEC, 0XE1BE5F5F, 0XA2359797, 0XCC884444, 0X392E1717, + 0X5793C4C4, 0XF255A7A7, 0X82FC7E7E, 0X477A3D3D, 0XACC86464, 0XE7BA5D5D, 0X2B321919, 0X95E67373, + 0XA0C06060, 0X98198181, 0XD19E4F4F, 0X7FA3DCDC, 0X66442222, 0X7E542A2A, 0XAB3B9090, 0X830B8888, + 0XCA8C4646, 0X29C7EEEE, 0XD36BB8B8, 0X3C281414, 0X79A7DEDE, 0XE2BC5E5E, 0X1D160B0B, 0X76ADDBDB, + 0X3BDBE0E0, 0X56643232, 0X4E743A3A, 0X1E140A0A, 0XDB924949, 0X0A0C0606, 0X6C482424, 0XE4B85C5C, + 0X5D9FC2C2, 0X6EBDD3D3, 0XEF43ACAC, 0XA6C46262, 0XA8399191, 0XA4319595, 0X37D3E4E4, 0X8BF27979, + 0X32D5E7E7, 0X438BC8C8, 0X596E3737, 0XB7DA6D6D, 0X8C018D8D, 0X64B1D5D5, 0XD29C4E4E, 0XE049A9A9, + 0XB4D86C6C, 0XFAAC5656, 0X07F3F4F4, 0X25CFEAEA, 0XAFCA6565, 0X8EF47A7A, 0XE947AEAE, 0X18100808, + 0XD56FBABA, 0X88F07878, 0X6F4A2525, 0X725C2E2E, 0X24381C1C, 0XF157A6A6, 0XC773B4B4, 0X5197C6C6, + 0X23CBE8E8, 0X7CA1DDDD, 0X9CE87474, 0X213E1F1F, 0XDD964B4B, 0XDC61BDBD, 0X860D8B8B, 0X850F8A8A, + 0X90E07070, 0X427C3E3E, 0XC471B5B5, 0XAACC6666, 0XD8904848, 0X05060303, 0X01F7F6F6, 0X121C0E0E, + 0XA3C26161, 0X5F6A3535, 0XF9AE5757, 0XD069B9B9, 0X91178686, 0X5899C1C1, 0X273A1D1D, 0XB9279E9E, + 0X38D9E1E1, 0X13EBF8F8, 0XB32B9898, 0X33221111, 0XBBD26969, 0X70A9D9D9, 0X89078E8E, 0XA7339494, + 0XB62D9B9B, 0X223C1E1E, 0X92158787, 0X20C9E9E9, 0X4987CECE, 0XFFAA5555, 0X78502828, 0X7AA5DFDF, + 0X8F038C8C, 0XF859A1A1, 0X80098989, 0X171A0D0D, 0XDA65BFBF, 0X31D7E6E6, 0XC6844242, 0XB8D06868, + 0XC3824141, 0XB0299999, 0X775A2D2D, 0X111E0F0F, 0XCB7BB0B0, 0XFCA85454, 0XD66DBBBB, 0X3A2C1616, +] + +TE2 = [ + 0X63A5C663, 0X7C84F87C, 0X7799EE77, 0X7B8DF67B, 0XF20DFFF2, 0X6BBDD66B, 0X6FB1DE6F, 0XC55491C5, + 0X30506030, 0X01030201, 0X67A9CE67, 0X2B7D562B, 0XFE19E7FE, 0XD762B5D7, 0XABE64DAB, 0X769AEC76, + 0XCA458FCA, 0X829D1F82, 0XC94089C9, 0X7D87FA7D, 0XFA15EFFA, 0X59EBB259, 0X47C98E47, 0XF00BFBF0, + 0XADEC41AD, 0XD467B3D4, 0XA2FD5FA2, 0XAFEA45AF, 0X9CBF239C, 0XA4F753A4, 0X7296E472, 0XC05B9BC0, + 0XB7C275B7, 0XFD1CE1FD, 0X93AE3D93, 0X266A4C26, 0X365A6C36, 0X3F417E3F, 0XF702F5F7, 0XCC4F83CC, + 0X345C6834, 0XA5F451A5, 0XE534D1E5, 0XF108F9F1, 0X7193E271, 0XD873ABD8, 0X31536231, 0X153F2A15, + 0X040C0804, 0XC75295C7, 0X23654623, 0XC35E9DC3, 0X18283018, 0X96A13796, 0X050F0A05, 0X9AB52F9A, + 0X07090E07, 0X12362412, 0X809B1B80, 0XE23DDFE2, 0XEB26CDEB, 0X27694E27, 0XB2CD7FB2, 0X759FEA75, + 0X091B1209, 0X839E1D83, 0X2C74582C, 0X1A2E341A, 0X1B2D361B, 0X6EB2DC6E, 0X5AEEB45A, 0XA0FB5BA0, + 0X52F6A452, 0X3B4D763B, 0XD661B7D6, 0XB3CE7DB3, 0X297B5229, 0XE33EDDE3, 0X2F715E2F, 0X84971384, + 0X53F5A653, 0XD168B9D1, 0X00000000, 0XED2CC1ED, 0X20604020, 0XFC1FE3FC, 0XB1C879B1, 0X5BEDB65B, + 0X6ABED46A, 0XCB468DCB, 0XBED967BE, 0X394B7239, 0X4ADE944A, 0X4CD4984C, 0X58E8B058, 0XCF4A85CF, + 0XD06BBBD0, 0XEF2AC5EF, 0XAAE54FAA, 0XFB16EDFB, 0X43C58643, 0X4DD79A4D, 0X33556633, 0X85941185, + 0X45CF8A45, 0XF910E9F9, 0X02060402, 0X7F81FE7F, 0X50F0A050, 0X3C44783C, 0X9FBA259F, 0XA8E34BA8, + 0X51F3A251, 0XA3FE5DA3, 0X40C08040, 0X8F8A058F, 0X92AD3F92, 0X9DBC219D, 0X38487038, 0XF504F1F5, + 0XBCDF63BC, 0XB6C177B6, 0XDA75AFDA, 0X21634221, 0X10302010, 0XFF1AE5FF, 0XF30EFDF3, 0XD26DBFD2, + 0XCD4C81CD, 0X0C14180C, 0X13352613, 0XEC2FC3EC, 0X5FE1BE5F, 0X97A23597, 0X44CC8844, 0X17392E17, + 0XC45793C4, 0XA7F255A7, 0X7E82FC7E, 0X3D477A3D, 0X64ACC864, 0X5DE7BA5D, 0X192B3219, 0X7395E673, + 0X60A0C060, 0X81981981, 0X4FD19E4F, 0XDC7FA3DC, 0X22664422, 0X2A7E542A, 0X90AB3B90, 0X88830B88, + 0X46CA8C46, 0XEE29C7EE, 0XB8D36BB8, 0X143C2814, 0XDE79A7DE, 0X5EE2BC5E, 0X0B1D160B, 0XDB76ADDB, + 0XE03BDBE0, 0X32566432, 0X3A4E743A, 0X0A1E140A, 0X49DB9249, 0X060A0C06, 0X246C4824, 0X5CE4B85C, + 0XC25D9FC2, 0XD36EBDD3, 0XACEF43AC, 0X62A6C462, 0X91A83991, 0X95A43195, 0XE437D3E4, 0X798BF279, + 0XE732D5E7, 0XC8438BC8, 0X37596E37, 0X6DB7DA6D, 0X8D8C018D, 0XD564B1D5, 0X4ED29C4E, 0XA9E049A9, + 0X6CB4D86C, 0X56FAAC56, 0XF407F3F4, 0XEA25CFEA, 0X65AFCA65, 0X7A8EF47A, 0XAEE947AE, 0X08181008, + 0XBAD56FBA, 0X7888F078, 0X256F4A25, 0X2E725C2E, 0X1C24381C, 0XA6F157A6, 0XB4C773B4, 0XC65197C6, + 0XE823CBE8, 0XDD7CA1DD, 0X749CE874, 0X1F213E1F, 0X4BDD964B, 0XBDDC61BD, 0X8B860D8B, 0X8A850F8A, + 0X7090E070, 0X3E427C3E, 0XB5C471B5, 0X66AACC66, 0X48D89048, 0X03050603, 0XF601F7F6, 0X0E121C0E, + 0X61A3C261, 0X355F6A35, 0X57F9AE57, 0XB9D069B9, 0X86911786, 0XC15899C1, 0X1D273A1D, 0X9EB9279E, + 0XE138D9E1, 0XF813EBF8, 0X98B32B98, 0X11332211, 0X69BBD269, 0XD970A9D9, 0X8E89078E, 0X94A73394, + 0X9BB62D9B, 0X1E223C1E, 0X87921587, 0XE920C9E9, 0XCE4987CE, 0X55FFAA55, 0X28785028, 0XDF7AA5DF, + 0X8C8F038C, 0XA1F859A1, 0X89800989, 0X0D171A0D, 0XBFDA65BF, 0XE631D7E6, 0X42C68442, 0X68B8D068, + 0X41C38241, 0X99B02999, 0X2D775A2D, 0X0F111E0F, 0XB0CB7BB0, 0X54FCA854, 0XBBD66DBB, 0X163A2C16, +] + +TE3 = [ + 0X6363A5C6, 0X7C7C84F8, 0X777799EE, 0X7B7B8DF6, 0XF2F20DFF, 0X6B6BBDD6, 0X6F6FB1DE, 0XC5C55491, + 0X30305060, 0X01010302, 0X6767A9CE, 0X2B2B7D56, 0XFEFE19E7, 0XD7D762B5, 0XABABE64D, 0X76769AEC, + 0XCACA458F, 0X82829D1F, 0XC9C94089, 0X7D7D87FA, 0XFAFA15EF, 0X5959EBB2, 0X4747C98E, 0XF0F00BFB, + 0XADADEC41, 0XD4D467B3, 0XA2A2FD5F, 0XAFAFEA45, 0X9C9CBF23, 0XA4A4F753, 0X727296E4, 0XC0C05B9B, + 0XB7B7C275, 0XFDFD1CE1, 0X9393AE3D, 0X26266A4C, 0X36365A6C, 0X3F3F417E, 0XF7F702F5, 0XCCCC4F83, + 0X34345C68, 0XA5A5F451, 0XE5E534D1, 0XF1F108F9, 0X717193E2, 0XD8D873AB, 0X31315362, 0X15153F2A, + 0X04040C08, 0XC7C75295, 0X23236546, 0XC3C35E9D, 0X18182830, 0X9696A137, 0X05050F0A, 0X9A9AB52F, + 0X0707090E, 0X12123624, 0X80809B1B, 0XE2E23DDF, 0XEBEB26CD, 0X2727694E, 0XB2B2CD7F, 0X75759FEA, + 0X09091B12, 0X83839E1D, 0X2C2C7458, 0X1A1A2E34, 0X1B1B2D36, 0X6E6EB2DC, 0X5A5AEEB4, 0XA0A0FB5B, + 0X5252F6A4, 0X3B3B4D76, 0XD6D661B7, 0XB3B3CE7D, 0X29297B52, 0XE3E33EDD, 0X2F2F715E, 0X84849713, + 0X5353F5A6, 0XD1D168B9, 0X00000000, 0XEDED2CC1, 0X20206040, 0XFCFC1FE3, 0XB1B1C879, 0X5B5BEDB6, + 0X6A6ABED4, 0XCBCB468D, 0XBEBED967, 0X39394B72, 0X4A4ADE94, 0X4C4CD498, 0X5858E8B0, 0XCFCF4A85, + 0XD0D06BBB, 0XEFEF2AC5, 0XAAAAE54F, 0XFBFB16ED, 0X4343C586, 0X4D4DD79A, 0X33335566, 0X85859411, + 0X4545CF8A, 0XF9F910E9, 0X02020604, 0X7F7F81FE, 0X5050F0A0, 0X3C3C4478, 0X9F9FBA25, 0XA8A8E34B, + 0X5151F3A2, 0XA3A3FE5D, 0X4040C080, 0X8F8F8A05, 0X9292AD3F, 0X9D9DBC21, 0X38384870, 0XF5F504F1, + 0XBCBCDF63, 0XB6B6C177, 0XDADA75AF, 0X21216342, 0X10103020, 0XFFFF1AE5, 0XF3F30EFD, 0XD2D26DBF, + 0XCDCD4C81, 0X0C0C1418, 0X13133526, 0XECEC2FC3, 0X5F5FE1BE, 0X9797A235, 0X4444CC88, 0X1717392E, + 0XC4C45793, 0XA7A7F255, 0X7E7E82FC, 0X3D3D477A, 0X6464ACC8, 0X5D5DE7BA, 0X19192B32, 0X737395E6, + 0X6060A0C0, 0X81819819, 0X4F4FD19E, 0XDCDC7FA3, 0X22226644, 0X2A2A7E54, 0X9090AB3B, 0X8888830B, + 0X4646CA8C, 0XEEEE29C7, 0XB8B8D36B, 0X14143C28, 0XDEDE79A7, 0X5E5EE2BC, 0X0B0B1D16, 0XDBDB76AD, + 0XE0E03BDB, 0X32325664, 0X3A3A4E74, 0X0A0A1E14, 0X4949DB92, 0X06060A0C, 0X24246C48, 0X5C5CE4B8, + 0XC2C25D9F, 0XD3D36EBD, 0XACACEF43, 0X6262A6C4, 0X9191A839, 0X9595A431, 0XE4E437D3, 0X79798BF2, + 0XE7E732D5, 0XC8C8438B, 0X3737596E, 0X6D6DB7DA, 0X8D8D8C01, 0XD5D564B1, 0X4E4ED29C, 0XA9A9E049, + 0X6C6CB4D8, 0X5656FAAC, 0XF4F407F3, 0XEAEA25CF, 0X6565AFCA, 0X7A7A8EF4, 0XAEAEE947, 0X08081810, + 0XBABAD56F, 0X787888F0, 0X25256F4A, 0X2E2E725C, 0X1C1C2438, 0XA6A6F157, 0XB4B4C773, 0XC6C65197, + 0XE8E823CB, 0XDDDD7CA1, 0X74749CE8, 0X1F1F213E, 0X4B4BDD96, 0XBDBDDC61, 0X8B8B860D, 0X8A8A850F, + 0X707090E0, 0X3E3E427C, 0XB5B5C471, 0X6666AACC, 0X4848D890, 0X03030506, 0XF6F601F7, 0X0E0E121C, + 0X6161A3C2, 0X35355F6A, 0X5757F9AE, 0XB9B9D069, 0X86869117, 0XC1C15899, 0X1D1D273A, 0X9E9EB927, + 0XE1E138D9, 0XF8F813EB, 0X9898B32B, 0X11113322, 0X6969BBD2, 0XD9D970A9, 0X8E8E8907, 0X9494A733, + 0X9B9BB62D, 0X1E1E223C, 0X87879215, 0XE9E920C9, 0XCECE4987, 0X5555FFAA, 0X28287850, 0XDFDF7AA5, + 0X8C8C8F03, 0XA1A1F859, 0X89898009, 0X0D0D171A, 0XBFBFDA65, 0XE6E631D7, 0X4242C684, 0X6868B8D0, + 0X4141C382, 0X9999B029, 0X2D2D775A, 0X0F0F111E, 0XB0B0CB7B, 0X5454FCA8, 0XBBBBD66D, 0X16163A2C, +] + + +def _read_uint32_be(block: Sequence[int], offset: int) -> int: + return int.from_bytes(bytes(block[offset:offset + 4]), "big") + + +def _write_uint32_be(block: bytearray, offset: int, value: int) -> None: + block[offset:offset + 4] = value.to_bytes(4, "big") + + +class _AESRound: + def __init__(self, key: bytes) -> None: + words = [_read_uint32_be(key, 4 * i) for i in range(12)] + self.aes10_key = [0] * (4 * 10) + self.aes4_key = [0] * (4 * 4) + self.aes10_key[0:12] = words + self.aes10_key[12:24] = words + self.aes10_key[24:36] = words + self.aes10_key[36:40] = words[0:4] + self.aes4_key[0:4] = words[4:8] + self.aes4_key[4:8] = words[0:4] + self.aes4_key[8:12] = words[8:12] + + def reset(self) -> None: + for i in range(len(self.aes10_key)): + self.aes10_key[i] = 0 + for i in range(len(self.aes4_key)): + self.aes4_key[i] = 0 + + def AES4( + self, + j: Sequence[int], + i_vec: Sequence[int], + l_vec: Sequence[int], + src: Sequence[int], + dst: bytearray, + ) -> None: + _xor_bytes4x16(j, i_vec, l_vec, src, dst) + self.rounds(dst, 4) + + def AES10(self, l_vec: Sequence[int], src: Sequence[int], dst: bytearray) -> None: + _xor_bytes1x16(src, l_vec, dst) + self.rounds(dst, 10) + + def rounds(self, block: bytearray, rounds: int) -> None: + keys = self.aes4_key if rounds == 4 else self.aes10_key + s0 = _read_uint32_be(block, 0) + s1 = _read_uint32_be(block, 4) + s2 = _read_uint32_be(block, 8) + s3 = _read_uint32_be(block, 12) + + for r in range(rounds): + rk_off = r * 4 + t0 = ( + TE0[_uint8(s0 >> 24)] + ^ TE1[_uint8(s1 >> 16)] + ^ TE2[_uint8(s2 >> 8)] + ^ TE3[_uint8(s3)] + ^ keys[rk_off] + ) + t1 = ( + TE0[_uint8(s1 >> 24)] + ^ TE1[_uint8(s2 >> 16)] + ^ TE2[_uint8(s3 >> 8)] + ^ TE3[_uint8(s0)] + ^ keys[rk_off + 1] + ) + t2 = ( + TE0[_uint8(s2 >> 24)] + ^ TE1[_uint8(s3 >> 16)] + ^ TE2[_uint8(s0 >> 8)] + ^ TE3[_uint8(s1)] + ^ keys[rk_off + 2] + ) + t3 = ( + TE0[_uint8(s3 >> 24)] + ^ TE1[_uint8(s0 >> 16)] + ^ TE2[_uint8(s1 >> 8)] + ^ TE3[_uint8(s2)] + ^ keys[rk_off + 3] + ) + s0 = _uint32(t0) + s1 = _uint32(t1) + s2 = _uint32(t2) + s3 = _uint32(t3) + + _write_uint32_be(block, 0, s0) + _write_uint32_be(block, 4, s1) + _write_uint32_be(block, 8, s2) + _write_uint32_be(block, 12, s3) + + + + +class _AEZState: + def __init__(self) -> None: + self.I = [_mk_block(), _mk_block()] + self.J = [_mk_block(), _mk_block(), _mk_block()] + self.L = [_mk_block() for _ in range(8)] + self.aes: _AESRound | None = None + + def reset(self) -> None: + for buf in self.I + self.J + self.L: + for i in range(len(buf)): + buf[i] = 0 + if self.aes: + self.aes.reset() + + def init(self, key: bytes) -> None: + extracted = _extract_key(key) + self.I[0][:] = extracted[0:16] + _mult_block(2, self.I[0], self.I[1]) + self.J[0][:] = extracted[16:32] + _mult_block(2, self.J[0], self.J[1]) + _mult_block(2, self.J[1], self.J[2]) + self.L[1][:] = extracted[32:48] + _mult_block(2, self.L[1], self.L[2]) + _xor_bytes1x16(self.L[2], self.L[1], self.L[3]) + _mult_block(2, self.L[2], self.L[4]) + _xor_bytes1x16(self.L[4], self.L[1], self.L[5]) + _mult_block(2, self.L[3], self.L[6]) + _xor_bytes1x16(self.L[6], self.L[1], self.L[7]) + self.aes = _AESRound(extracted) + + def aez_hash(self, nonce: bytes | None, ad: Iterable[bytes], tau: int) -> bytearray: + assert self.aes is not None + buf = _mk_block() + sum_block = _mk_block() + I_tmp = _mk_block() + J_tmp = _mk_block() + buf[12:16] = _uint32(tau).to_bytes(4, "big") + _xor_bytes1x16(self.J[0], self.J[1], J_tmp) + self.aes.AES4(J_tmp, self.I[1], self.L[1], buf, sum_block) + + empty_nonce = not nonce + n_bytes = len(nonce) if nonce else 0 + I_tmp[:] = self.I[1] + offset = 0 + i = 1 + while n_bytes >= BLOCK_SIZE: + block = nonce[offset:offset + BLOCK_SIZE] + tmp = _mk_block() + tmp[:BLOCK_SIZE] = block + self.aes.AES4(self.J[2], I_tmp, self.L[i % 8], tmp, buf) + _xor_bytes1x16(sum_block, buf, sum_block) + offset += BLOCK_SIZE + n_bytes -= BLOCK_SIZE + if i % 8 == 0: + _double_block(I_tmp) + i += 1 + if n_bytes > 0 or empty_nonce: + buf[:] = b"\x00" * BLOCK_SIZE + if not empty_nonce and nonce is not None: + buf[:n_bytes] = nonce[offset:offset + n_bytes] + buf[n_bytes] = 0x80 + self.aes.AES4(self.J[2], self.I[0], self.L[0], buf, buf) + _xor_bytes1x16(sum_block, buf, sum_block) + + for k, piece in enumerate(ad): + empty_piece = not piece + bytes_left = len(piece) if piece else 0 + I_tmp[:] = self.I[1] + J_tmp2 = _mk_block() + _mult_block(5 + k, self.J[0], J_tmp2) + offset = 0 + i = 1 + while bytes_left >= BLOCK_SIZE: + tmp_in = piece[offset:offset + BLOCK_SIZE] + tmp = _mk_block() + tmp[:BLOCK_SIZE] = tmp_in + self.aes.AES4(J_tmp2, I_tmp, self.L[i % 8], tmp, buf) + _xor_bytes1x16(sum_block, buf, sum_block) + offset += BLOCK_SIZE + bytes_left -= BLOCK_SIZE + if i % 8 == 0: + _double_block(I_tmp) + i += 1 + if bytes_left > 0 or empty_piece: + buf[:] = b"\x00" * BLOCK_SIZE + if not empty_piece and piece: + buf[:bytes_left] = piece[offset:offset + bytes_left] + buf[bytes_left] = 0x80 + self.aes.AES4(J_tmp2, self.I[0], self.L[0], buf, buf) + _xor_bytes1x16(sum_block, buf, sum_block) + + return sum_block + + def aez_prf(self, delta: Sequence[int], tau: int, dst: bytearray) -> None: + assert self.aes is not None + buf = _mk_block() + ctr = _mk_block() + off = 0 + remaining = tau + while remaining >= BLOCK_SIZE: + _xor_bytes1x16(delta, ctr, buf) + self.aes.AES10(self.L[3], buf, buf) + dst[off:off + BLOCK_SIZE] = buf + for idx in range(15, -1, -1): + ctr[idx] = (ctr[idx] + 1) & 0xFF + if ctr[idx] != 0: + break + off += BLOCK_SIZE + remaining -= BLOCK_SIZE + if remaining > 0: + _xor_bytes1x16(delta, ctr, buf) + self.aes.AES10(self.L[3], buf, buf) + dst[off:off + remaining] = buf[:remaining] + + def encipher(self, delta: Sequence[int], data: bytes, dst: bytearray) -> None: + if not data: + return + if len(data) < 32: + self.aez_tiny(delta, data, 0, dst) + else: + self.aez_core(delta, data, 0, dst) + + def decipher(self, delta: Sequence[int], data: bytes, dst: bytearray) -> None: + if not data: + return + if len(data) < 32: + self.aez_tiny(delta, data, 1, dst) + else: + self.aez_core(delta, data, 1, dst) + + def aez_tiny(self, delta: Sequence[int], data: bytes, direction: int, dst: bytearray) -> None: + assert self.aes is not None + in_bytes = len(data) + buf = bytearray(2 * BLOCK_SIZE) + buf_view = memoryview(buf) + L = _mk_block() + R = _mk_block() + tmp = _mk_block() + mask = 0x00 + pad = 0x80 + idx_param = 7 + if in_bytes == 1: + rounds = 24 + elif in_bytes == 2: + rounds = 16 + elif in_bytes < BLOCK_SIZE: + rounds = 10 + else: + idx_param = 6 + rounds = 8 + + left_len = (in_bytes + 1) // 2 + right_start = in_bytes // 2 + L[:left_len] = data[:left_len] + R[:left_len] = data[right_start:right_start + left_len] + if in_bytes & 1: + half = in_bytes // 2 + for k in range(half): + R[k] = ((R[k] << 4) | (R[k + 1] >> 4)) & 0xFF + R[half] = (R[half] << 4) & 0xFF + pad = 0x08 + mask = 0xF0 + + if direction != 0: + if in_bytes < BLOCK_SIZE: + buf[:BLOCK_SIZE] = b"\x00" * BLOCK_SIZE + buf[:in_bytes] = data + buf[0] |= 0x80 + _xor_bytes1x16(delta, buf_view[:BLOCK_SIZE], buf_view[:BLOCK_SIZE]) + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[3], buf_view[:BLOCK_SIZE], tmp) + L[0] ^= tmp[0] & 0x80 + j = rounds - 1 + step = -1 + else: + j = 0 + step = 1 + + for _ in range(rounds // 2): + buf[:BLOCK_SIZE] = b"\x00" * BLOCK_SIZE + buf[:left_len] = R[:left_len] + mid_index = in_bytes // 2 + buf[mid_index] = (buf[mid_index] & mask) | pad + _xor_bytes1x16(buf_view[:BLOCK_SIZE], delta, buf_view[:BLOCK_SIZE]) + buf[15] ^= j & 0xFF + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[idx_param], buf_view[:BLOCK_SIZE], tmp) + _xor_bytes1x16(L, tmp, L) + + buf[:BLOCK_SIZE] = b"\x00" * BLOCK_SIZE + buf[:left_len] = L[:left_len] + buf[mid_index] = (buf[mid_index] & mask) | pad + _xor_bytes1x16(buf_view[:BLOCK_SIZE], delta, buf_view[:BLOCK_SIZE]) + buf[15] ^= (j + step) & 0xFF + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[idx_param], buf_view[:BLOCK_SIZE], tmp) + _xor_bytes1x16(R, tmp, R) + j += step * 2 + + half = in_bytes // 2 + buf[:half] = R[:half] + buf[half:half + left_len] = L[:left_len] + if in_bytes & 1: + for k in range(in_bytes - 1, half, -1): + buf[k] = ((buf[k] >> 4) | (buf[k - 1] << 4)) & 0xFF + buf[half] = ((L[0] >> 4) & 0x0F) | (R[half] & 0xF0) + + dst[:in_bytes] = buf[:in_bytes] + if in_bytes < BLOCK_SIZE and direction == 0: + buf[in_bytes:BLOCK_SIZE] = b"\x00" * (BLOCK_SIZE - in_bytes) + buf[0] |= 0x80 + _xor_bytes1x16(delta, buf_view[:BLOCK_SIZE], buf_view[:BLOCK_SIZE]) + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[3], buf_view[:BLOCK_SIZE], tmp) + dst[0] ^= tmp[0] & 0x80 + + def aez_core(self, delta: Sequence[int], data: bytes, direction: int, dst: bytearray) -> None: + assert self.aes is not None + in_bytes = len(data) + frag_bytes = in_bytes % 32 + initial_bytes = in_bytes - frag_bytes - 32 + tmp = _mk_block() + X = _mk_block() + Y = _mk_block() + S = _mk_block() + input_bytes = bytearray(data) + + if in_bytes >= 64: + self.aez_core_pass1(input_bytes, dst, X) + + tail = input_bytes[initial_bytes:] + if frag_bytes >= BLOCK_SIZE: + buf = _mk_block() + buf[:BLOCK_SIZE] = tail[BLOCK_SIZE:BLOCK_SIZE * 2] + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[4], buf, tmp) + _xor_bytes1x16(X, tmp, X) + padded = _mk_block() + _one_zero_pad(tail[BLOCK_SIZE:], frag_bytes - BLOCK_SIZE, padded) + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[5], padded, tmp) + _xor_bytes1x16(X, tmp, X) + elif frag_bytes > 0: + padded = _mk_block() + _one_zero_pad(tail, frag_bytes, padded) + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[4], padded, tmp) + _xor_bytes1x16(X, tmp, X) + + dst_tail = dst[in_bytes - 32:in_bytes] + input_tail = input_bytes[in_bytes - 32:in_bytes] + block1 = _mk_block() + block1[:BLOCK_SIZE] = input_tail[:BLOCK_SIZE] + block2 = _mk_block() + block2[:BLOCK_SIZE] = input_tail[BLOCK_SIZE:BLOCK_SIZE * 2] + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[(1 + direction) % 8], block2, tmp) + first_dst = _mk_block() + _xor_bytes4x16(X, block1, delta, tmp, first_dst) + dst_tail[:BLOCK_SIZE] = first_dst + tmp2 = _mk_block() + self.aes.AES10(self.L[(1 + direction) % 8], first_dst, tmp2) + second_dst = bytearray(block2) + _xor_bytes1x16(second_dst, tmp2, second_dst) + dst_tail[BLOCK_SIZE:BLOCK_SIZE * 2] = second_dst + _xor_bytes1x16(first_dst, second_dst, S) + + if in_bytes >= 64: + self.aez_core_pass2(input_bytes, dst, Y, S) + + dst_fragment = dst[initial_bytes:] + input_fragment = input_bytes[initial_bytes:] + if frag_bytes >= BLOCK_SIZE: + tmp_block = _mk_block() + self.aes.AES10(self.L[4], S, tmp_block) + block = bytearray(input_fragment[:BLOCK_SIZE]) + _xor_bytes1x16(block, tmp_block, block) + dst_fragment[:BLOCK_SIZE] = block + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[4], block, tmp) + _xor_bytes1x16(Y, tmp, Y) + + remaining = frag_bytes - BLOCK_SIZE + tmp_block = _mk_block() + self.aes.AES10(self.L[5], S, tmp_block) + buf = bytearray(tmp_block) + fragment = bytearray(input_fragment[BLOCK_SIZE:BLOCK_SIZE + remaining]) + for idx in range(remaining): + buf[idx] ^= fragment[idx] + dst_fragment[BLOCK_SIZE:BLOCK_SIZE + remaining] = buf[:remaining] + buf[:] = b"\x00" * BLOCK_SIZE + buf[:remaining] = dst_fragment[BLOCK_SIZE:BLOCK_SIZE + remaining] + buf[remaining] = 0x80 + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[5], buf, tmp) + _xor_bytes1x16(Y, tmp, Y) + dst_fragment = dst_fragment[BLOCK_SIZE + remaining:] + input_fragment = input_fragment[BLOCK_SIZE + remaining:] + elif frag_bytes > 0: + tmp_block = _mk_block() + self.aes.AES10(self.L[4], S, tmp_block) + buf = bytearray(tmp_block) + fragment = bytearray(input_fragment[:frag_bytes]) + for idx in range(frag_bytes): + buf[idx] ^= fragment[idx] + dst_fragment[:frag_bytes] = buf[:frag_bytes] + buf[:] = b"\x00" * BLOCK_SIZE + buf[:frag_bytes] = dst_fragment[:frag_bytes] + buf[frag_bytes] = 0x80 + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[4], buf, tmp) + _xor_bytes1x16(Y, tmp, Y) + dst_fragment = dst_fragment[frag_bytes:] + input_fragment = input_fragment[frag_bytes:] + + dst_tail = dst[in_bytes - 32:in_bytes] + second_half = bytearray(dst_tail[BLOCK_SIZE:BLOCK_SIZE * 2]) + tmp_block = _mk_block() + self.aes.AES10(self.L[(2 - direction) % 8], second_half, tmp_block) + first_half = bytearray(dst_tail[:BLOCK_SIZE]) + _xor_bytes1x16(first_half, tmp_block, first_half) + dst_tail[:BLOCK_SIZE] = first_half + self.aes.AES4(ZERO_BLOCK, self.I[1], self.L[(2 - direction) % 8], first_half, tmp) + combined = _mk_block() + _xor_bytes4x16(tmp, dst_tail[BLOCK_SIZE:BLOCK_SIZE * 2], delta, Y, combined) + dst_tail[BLOCK_SIZE:BLOCK_SIZE * 2] = combined + tmp_copy = bytearray(dst_tail[:BLOCK_SIZE]) + dst_tail[:BLOCK_SIZE] = combined + dst_tail[BLOCK_SIZE:BLOCK_SIZE * 2] = tmp_copy + + def aez_core_pass1(self, input_bytes: bytearray, output: bytearray, X: bytearray) -> None: + assert self.aes is not None + tmp = _mk_block() + I_tmp = _mk_block() + I_tmp[:] = self.I[1] + offset = 0 + remaining = len(input_bytes) + i = 1 + while remaining >= 64: + block1 = bytearray(input_bytes[offset + BLOCK_SIZE:offset + BLOCK_SIZE * 2]) + self.aes.AES4(self.J[0], I_tmp, self.L[i % 8], block1, tmp) + first_out = bytearray(input_bytes[offset:offset + BLOCK_SIZE]) + _xor_bytes1x16(first_out, tmp, first_out) + output[offset:offset + BLOCK_SIZE] = first_out + + self.aes.AES4(ZERO_BLOCK, self.I[0], self.L[0], first_out, tmp) + second_out = bytearray(input_bytes[offset + BLOCK_SIZE:offset + BLOCK_SIZE * 2]) + _xor_bytes1x16(second_out, tmp, second_out) + output[offset + BLOCK_SIZE:offset + BLOCK_SIZE * 2] = second_out + _xor_bytes1x16(second_out, X, X) + + offset += 32 + remaining -= 32 + i += 1 + if i % 8 == 0: + _double_block(I_tmp) + + def aez_core_pass2(self, input_bytes: bytearray, output: bytearray, Y: bytearray, S: bytearray) -> None: + assert self.aes is not None + tmp = _mk_block() + I_tmp = _mk_block() + I_tmp[:] = self.I[1] + offset = 0 + remaining = len(input_bytes) + i = 1 + while remaining >= 64: + first_out = bytearray(output[offset:offset + BLOCK_SIZE]) + second_out = bytearray(output[offset + BLOCK_SIZE:offset + BLOCK_SIZE * 2]) + self.aes.AES4(self.J[1], I_tmp, self.L[i % 8], S, tmp) + _xor_bytes1x16(first_out, tmp, first_out) + _xor_bytes1x16(second_out, tmp, second_out) + _xor_bytes1x16(first_out, Y, Y) + + self.aes.AES4(ZERO_BLOCK, self.I[0], self.L[0], second_out, tmp) + _xor_bytes1x16(first_out, tmp, first_out) + + self.aes.AES4(self.J[0], I_tmp, self.L[i % 8], first_out, tmp) + _xor_bytes1x16(second_out, tmp, second_out) + + temp = bytearray(first_out) + first_out[:] = second_out + second_out[:] = temp + + output[offset:offset + BLOCK_SIZE] = first_out + output[offset + BLOCK_SIZE:offset + BLOCK_SIZE * 2] = second_out + + offset += 32 + remaining -= 32 + i += 1 + if i % 8 == 0: + _double_block(I_tmp) + + +def _aez_decrypt(key: bytes, ad_list: Iterable[bytes], tau: int, ciphertext: bytes) -> bytes | None: + state = _AEZState() + state.reset() + state.init(key) + delta = state.aez_hash(None, list(ad_list), tau * 8) + x = bytearray(len(ciphertext)) + if len(ciphertext) == tau: + state.aez_prf(delta, tau, x) + mismatch = 0 + for i in range(tau): + mismatch |= x[i] ^ ciphertext[i] + if mismatch != 0: + return None + return bytes() + state.decipher(delta, ciphertext, x) + mismatch = 0 + for i in range(tau): + mismatch |= x[len(ciphertext) - tau + i] + if mismatch != 0: + return None + return bytes(x[: len(ciphertext) - tau]) + + +def mnemonic_to_bytes(words: Sequence[str], word_to_index: dict[str, int]) -> bytes: + if len(words) != 24: + raise InvalidMnemonicError("aezeed mnemonics must have 24 words") + bits = 0 + bit_len = 0 + out = bytearray(EncipheredCipherSeedSize) + pos = 0 + for word in words: + try: + idx = word_to_index[word] + except KeyError as exc: + raise InvalidMnemonicError(f"unknown word: {word}") from exc + bits = (bits << BitsPerWord) | idx + bit_len += BitsPerWord + while bit_len >= 8: + bit_len -= 8 + out[pos] = (bits >> bit_len) & 0xFF + bits &= (1 << bit_len) - 1 if bit_len else 0 + pos += 1 + return bytes(out) + + +def validate_mnemonic(words: Sequence[str], word_to_index: dict[str, int]) -> bool: + try: + cipher_bytes = mnemonic_to_bytes(words, word_to_index) + except InvalidMnemonicError: + return False + if cipher_bytes[0] != CipherSeedVersion: + return False + expected = int.from_bytes(cipher_bytes[-4:], "big") + computed = _crc32c(cipher_bytes[: EncipheredCipherSeedSize - 4]) + return expected == computed + + +def _encode_ad(version: int, salt: bytes) -> bytes: + return bytes([version]) + salt + + +def decode_mnemonic( + words: Sequence[str], + passphrase: str, + word_to_index: dict[str, int], +) -> DecipheredCipherSeed: + cipher_bytes = mnemonic_to_bytes(words, word_to_index) + version = cipher_bytes[0] + if version != CipherSeedVersion: + raise InvalidMnemonicError("unsupported aezeed version") + checksum = int.from_bytes(cipher_bytes[-4:], "big") + computed = _crc32c(cipher_bytes[: EncipheredCipherSeedSize - 4]) + if checksum != computed: + raise InvalidMnemonicError("checksum mismatch") + salt = cipher_bytes[EncipheredCipherSeedSize - 4 - SaltSize : EncipheredCipherSeedSize - 4] + ciphertext = cipher_bytes[1 : EncipheredCipherSeedSize - 4 - SaltSize] + # LND always prefixes the default passphrase to user-supplied strings before + # running scrypt. Append the provided passphrase to the base constant so + # recovery works for mnemonics created with custom passphrases. + pass_bytes = (DEFAULT_PASSPHRASE + (passphrase or "")).encode("utf-8") + key = hashlib.scrypt(pass_bytes, salt=salt, n=32768, r=8, p=1, dklen=32, maxmem=2_000_000_000) + ad = _encode_ad(version, salt) + plaintext = _aez_decrypt(key, [ad], CipherTextExpansion, ciphertext) + if plaintext is None or len(plaintext) != DecipheredCipherSeedSize: + raise InvalidPassphraseError("invalid passphrase") + internal_version = plaintext[0] + birthday = int.from_bytes(plaintext[1:3], "big") + entropy = plaintext[3:] + return DecipheredCipherSeed(entropy=bytes(entropy), salt=bytes(salt), internal_version=internal_version, birthday=birthday) + diff --git a/btcrecover/wallet_pb2.py b/btcrecover/bitcoinj_pb2.py similarity index 65% rename from btcrecover/wallet_pb2.py rename to btcrecover/bitcoinj_pb2.py index cf018630b..9e2a2f389 100644 --- a/btcrecover/wallet_pb2.py +++ b/btcrecover/bitcoinj_pb2.py @@ -1,13 +1,11 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: wallet.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -18,9 +16,11 @@ DESCRIPTOR = _descriptor.FileDescriptor( name='wallet.proto', package='wallet', - serialized_pb=_b('\n\x0cwallet.proto\x12\x06wallet\"A\n\x0bPeerAddress\x12\x12\n\nip_address\x18\x01 \x02(\x0c\x12\x0c\n\x04port\x18\x02 \x02(\r\x12\x10\n\x08services\x18\x03 \x02(\x04\"M\n\rEncryptedData\x12\x1d\n\x15initialisation_vector\x18\x01 \x02(\x0c\x12\x1d\n\x15\x65ncrypted_private_key\x18\x02 \x02(\x0c\"y\n\x10\x44\x65terministicKey\x12\x12\n\nchain_code\x18\x01 \x02(\x0c\x12\x0c\n\x04path\x18\x02 \x03(\r\x12\x16\n\x0eissued_subkeys\x18\x03 \x01(\r\x12\x16\n\x0elookahead_size\x18\x04 \x01(\r\x12\x13\n\x0bisFollowing\x18\x05 \x01(\x08\"\x9a\x03\n\x03Key\x12\x1e\n\x04type\x18\x01 \x02(\x0e\x32\x10.wallet.Key.Type\x12\x14\n\x0csecret_bytes\x18\x02 \x01(\x0c\x12-\n\x0e\x65ncrypted_data\x18\x06 \x01(\x0b\x32\x15.wallet.EncryptedData\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\r\n\x05label\x18\x04 \x01(\t\x12\x1a\n\x12\x63reation_timestamp\x18\x05 \x01(\x03\x12\x33\n\x11\x64\x65terministic_key\x18\x07 \x01(\x0b\x32\x18.wallet.DeterministicKey\x12\x1a\n\x12\x64\x65terministic_seed\x18\x08 \x01(\x0c\x12;\n\x1c\x65ncrypted_deterministic_seed\x18\t \x01(\x0b\x32\x15.wallet.EncryptedData\"a\n\x04Type\x12\x0c\n\x08ORIGINAL\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\x12\x1a\n\x16\x44\x45TERMINISTIC_MNEMONIC\x10\x03\x12\x15\n\x11\x44\x45TERMINISTIC_KEY\x10\x04\"5\n\x06Script\x12\x0f\n\x07program\x18\x01 \x02(\x0c\x12\x1a\n\x12\x63reation_timestamp\x18\x02 \x02(\x03\"\x92\x01\n\x10TransactionInput\x12\"\n\x1atransaction_out_point_hash\x18\x01 \x02(\x0c\x12#\n\x1btransaction_out_point_index\x18\x02 \x02(\r\x12\x14\n\x0cscript_bytes\x18\x03 \x02(\x0c\x12\x10\n\x08sequence\x18\x04 \x01(\r\x12\r\n\x05value\x18\x05 \x01(\x03\"\x7f\n\x11TransactionOutput\x12\r\n\x05value\x18\x01 \x02(\x03\x12\x14\n\x0cscript_bytes\x18\x02 \x02(\x0c\x12!\n\x19spent_by_transaction_hash\x18\x03 \x01(\x0c\x12\"\n\x1aspent_by_transaction_index\x18\x04 \x01(\x05\"\x89\x03\n\x15TransactionConfidence\x12\x30\n\x04type\x18\x01 \x01(\x0e\x32\".wallet.TransactionConfidence.Type\x12\x1a\n\x12\x61ppeared_at_height\x18\x02 \x01(\x05\x12\x1e\n\x16overriding_transaction\x18\x03 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x04 \x01(\x05\x12)\n\x0c\x62roadcast_by\x18\x06 \x03(\x0b\x32\x13.wallet.PeerAddress\x12\x34\n\x06source\x18\x07 \x01(\x0e\x32$.wallet.TransactionConfidence.Source\"O\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0c\n\x08\x42UILDING\x10\x01\x12\x0b\n\x07PENDING\x10\x02\x12\x15\n\x11NOT_IN_BEST_CHAIN\x10\x03\x12\x08\n\x04\x44\x45\x41\x44\x10\x04\"A\n\x06Source\x12\x12\n\x0eSOURCE_UNKNOWN\x10\x00\x12\x12\n\x0eSOURCE_NETWORK\x10\x01\x12\x0f\n\x0bSOURCE_SELF\x10\x02\"\xb4\x05\n\x0bTransaction\x12\x0f\n\x07version\x18\x01 \x02(\x05\x12\x0c\n\x04hash\x18\x02 \x02(\x0c\x12&\n\x04pool\x18\x03 \x01(\x0e\x32\x18.wallet.Transaction.Pool\x12\x11\n\tlock_time\x18\x04 \x01(\r\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x33\n\x11transaction_input\x18\x06 \x03(\x0b\x32\x18.wallet.TransactionInput\x12\x35\n\x12transaction_output\x18\x07 \x03(\x0b\x32\x19.wallet.TransactionOutput\x12\x12\n\nblock_hash\x18\x08 \x03(\x0c\x12 \n\x18\x62lock_relativity_offsets\x18\x0b \x03(\x05\x12\x31\n\nconfidence\x18\t \x01(\x0b\x32\x1d.wallet.TransactionConfidence\x12\x35\n\x07purpose\x18\n \x01(\x0e\x32\x1b.wallet.Transaction.Purpose:\x07UNKNOWN\x12+\n\rexchange_rate\x18\x0c \x01(\x0b\x32\x14.wallet.ExchangeRate\x12\x0c\n\x04memo\x18\r \x01(\t\"Y\n\x04Pool\x12\x0b\n\x07UNSPENT\x10\x04\x12\t\n\x05SPENT\x10\x05\x12\x0c\n\x08INACTIVE\x10\x02\x12\x08\n\x04\x44\x45\x41\x44\x10\n\x12\x0b\n\x07PENDING\x10\x10\x12\x14\n\x10PENDING_INACTIVE\x10\x12\"\x94\x01\n\x07Purpose\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x10\n\x0cUSER_PAYMENT\x10\x01\x12\x10\n\x0cKEY_ROTATION\x10\x02\x12\x1c\n\x18\x41SSURANCE_CONTRACT_CLAIM\x10\x03\x12\x1d\n\x19\x41SSURANCE_CONTRACT_PLEDGE\x10\x04\x12\x1b\n\x17\x41SSURANCE_CONTRACT_STUB\x10\x05\"N\n\x10ScryptParameters\x12\x0c\n\x04salt\x18\x01 \x02(\x0c\x12\x10\n\x01n\x18\x02 \x01(\x03:\x05\x31\x36\x33\x38\x34\x12\x0c\n\x01r\x18\x03 \x01(\x05:\x01\x38\x12\x0c\n\x01p\x18\x04 \x01(\x05:\x01\x31\"8\n\tExtension\x12\n\n\x02id\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\x12\x11\n\tmandatory\x18\x03 \x02(\x08\" \n\x03Tag\x12\x0b\n\x03tag\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"5\n\x11TransactionSigner\x12\x12\n\nclass_name\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x89\x05\n\x06Wallet\x12\x1a\n\x12network_identifier\x18\x01 \x02(\t\x12\x1c\n\x14last_seen_block_hash\x18\x02 \x01(\x0c\x12\x1e\n\x16last_seen_block_height\x18\x0c \x01(\r\x12!\n\x19last_seen_block_time_secs\x18\x0e \x01(\x03\x12\x18\n\x03key\x18\x03 \x03(\x0b\x32\x0b.wallet.Key\x12(\n\x0btransaction\x18\x04 \x03(\x0b\x32\x13.wallet.Transaction\x12&\n\x0ewatched_script\x18\x0f \x03(\x0b\x32\x0e.wallet.Script\x12\x43\n\x0f\x65ncryption_type\x18\x05 \x01(\x0e\x32\x1d.wallet.Wallet.EncryptionType:\x0bUNENCRYPTED\x12\x37\n\x15\x65ncryption_parameters\x18\x06 \x01(\x0b\x32\x18.wallet.ScryptParameters\x12\x12\n\x07version\x18\x07 \x01(\x05:\x01\x31\x12$\n\textension\x18\n \x03(\x0b\x32\x11.wallet.Extension\x12\x13\n\x0b\x64\x65scription\x18\x0b \x01(\t\x12\x19\n\x11key_rotation_time\x18\r \x01(\x04\x12\x19\n\x04tags\x18\x10 \x03(\x0b\x32\x0b.wallet.Tag\x12\x36\n\x13transaction_signers\x18\x11 \x03(\x0b\x32\x19.wallet.TransactionSigner\x12\x1e\n\x13sigsRequiredToSpend\x18\x12 \x01(\r:\x01\x31\";\n\x0e\x45ncryptionType\x12\x0f\n\x0bUNENCRYPTED\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\"R\n\x0c\x45xchangeRate\x12\x12\n\ncoin_value\x18\x01 \x02(\x03\x12\x12\n\nfiat_value\x18\x02 \x02(\x03\x12\x1a\n\x12\x66iat_currency_code\x18\x03 \x02(\tB\x1d\n\x13org.bitcoinj.walletB\x06Protos') + syntax='proto2', + serialized_options=b'\n\023org.bitcoinj.walletB\006Protos', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x0cwallet.proto\x12\x06wallet\"A\n\x0bPeerAddress\x12\x12\n\nip_address\x18\x01 \x02(\x0c\x12\x0c\n\x04port\x18\x02 \x02(\r\x12\x10\n\x08services\x18\x03 \x02(\x04\"M\n\rEncryptedData\x12\x1d\n\x15initialisation_vector\x18\x01 \x02(\x0c\x12\x1d\n\x15\x65ncrypted_private_key\x18\x02 \x02(\x0c\"y\n\x10\x44\x65terministicKey\x12\x12\n\nchain_code\x18\x01 \x02(\x0c\x12\x0c\n\x04path\x18\x02 \x03(\r\x12\x16\n\x0eissued_subkeys\x18\x03 \x01(\r\x12\x16\n\x0elookahead_size\x18\x04 \x01(\r\x12\x13\n\x0bisFollowing\x18\x05 \x01(\x08\"\x9a\x03\n\x03Key\x12\x1e\n\x04type\x18\x01 \x02(\x0e\x32\x10.wallet.Key.Type\x12\x14\n\x0csecret_bytes\x18\x02 \x01(\x0c\x12-\n\x0e\x65ncrypted_data\x18\x06 \x01(\x0b\x32\x15.wallet.EncryptedData\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\r\n\x05label\x18\x04 \x01(\t\x12\x1a\n\x12\x63reation_timestamp\x18\x05 \x01(\x03\x12\x33\n\x11\x64\x65terministic_key\x18\x07 \x01(\x0b\x32\x18.wallet.DeterministicKey\x12\x1a\n\x12\x64\x65terministic_seed\x18\x08 \x01(\x0c\x12;\n\x1c\x65ncrypted_deterministic_seed\x18\t \x01(\x0b\x32\x15.wallet.EncryptedData\"a\n\x04Type\x12\x0c\n\x08ORIGINAL\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\x12\x1a\n\x16\x44\x45TERMINISTIC_MNEMONIC\x10\x03\x12\x15\n\x11\x44\x45TERMINISTIC_KEY\x10\x04\"5\n\x06Script\x12\x0f\n\x07program\x18\x01 \x02(\x0c\x12\x1a\n\x12\x63reation_timestamp\x18\x02 \x02(\x03\"\x92\x01\n\x10TransactionInput\x12\"\n\x1atransaction_out_point_hash\x18\x01 \x02(\x0c\x12#\n\x1btransaction_out_point_index\x18\x02 \x02(\r\x12\x14\n\x0cscript_bytes\x18\x03 \x02(\x0c\x12\x10\n\x08sequence\x18\x04 \x01(\r\x12\r\n\x05value\x18\x05 \x01(\x03\"\x7f\n\x11TransactionOutput\x12\r\n\x05value\x18\x01 \x02(\x03\x12\x14\n\x0cscript_bytes\x18\x02 \x02(\x0c\x12!\n\x19spent_by_transaction_hash\x18\x03 \x01(\x0c\x12\"\n\x1aspent_by_transaction_index\x18\x04 \x01(\x05\"\x89\x03\n\x15TransactionConfidence\x12\x30\n\x04type\x18\x01 \x01(\x0e\x32\".wallet.TransactionConfidence.Type\x12\x1a\n\x12\x61ppeared_at_height\x18\x02 \x01(\x05\x12\x1e\n\x16overriding_transaction\x18\x03 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x04 \x01(\x05\x12)\n\x0c\x62roadcast_by\x18\x06 \x03(\x0b\x32\x13.wallet.PeerAddress\x12\x34\n\x06source\x18\x07 \x01(\x0e\x32$.wallet.TransactionConfidence.Source\"O\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0c\n\x08\x42UILDING\x10\x01\x12\x0b\n\x07PENDING\x10\x02\x12\x15\n\x11NOT_IN_BEST_CHAIN\x10\x03\x12\x08\n\x04\x44\x45\x41\x44\x10\x04\"A\n\x06Source\x12\x12\n\x0eSOURCE_UNKNOWN\x10\x00\x12\x12\n\x0eSOURCE_NETWORK\x10\x01\x12\x0f\n\x0bSOURCE_SELF\x10\x02\"\xb4\x05\n\x0bTransaction\x12\x0f\n\x07version\x18\x01 \x02(\x05\x12\x0c\n\x04hash\x18\x02 \x02(\x0c\x12&\n\x04pool\x18\x03 \x01(\x0e\x32\x18.wallet.Transaction.Pool\x12\x11\n\tlock_time\x18\x04 \x01(\r\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x33\n\x11transaction_input\x18\x06 \x03(\x0b\x32\x18.wallet.TransactionInput\x12\x35\n\x12transaction_output\x18\x07 \x03(\x0b\x32\x19.wallet.TransactionOutput\x12\x12\n\nblock_hash\x18\x08 \x03(\x0c\x12 \n\x18\x62lock_relativity_offsets\x18\x0b \x03(\x05\x12\x31\n\nconfidence\x18\t \x01(\x0b\x32\x1d.wallet.TransactionConfidence\x12\x35\n\x07purpose\x18\n \x01(\x0e\x32\x1b.wallet.Transaction.Purpose:\x07UNKNOWN\x12+\n\rexchange_rate\x18\x0c \x01(\x0b\x32\x14.wallet.ExchangeRate\x12\x0c\n\x04memo\x18\r \x01(\t\"Y\n\x04Pool\x12\x0b\n\x07UNSPENT\x10\x04\x12\t\n\x05SPENT\x10\x05\x12\x0c\n\x08INACTIVE\x10\x02\x12\x08\n\x04\x44\x45\x41\x44\x10\n\x12\x0b\n\x07PENDING\x10\x10\x12\x14\n\x10PENDING_INACTIVE\x10\x12\"\x94\x01\n\x07Purpose\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x10\n\x0cUSER_PAYMENT\x10\x01\x12\x10\n\x0cKEY_ROTATION\x10\x02\x12\x1c\n\x18\x41SSURANCE_CONTRACT_CLAIM\x10\x03\x12\x1d\n\x19\x41SSURANCE_CONTRACT_PLEDGE\x10\x04\x12\x1b\n\x17\x41SSURANCE_CONTRACT_STUB\x10\x05\"N\n\x10ScryptParameters\x12\x0c\n\x04salt\x18\x01 \x02(\x0c\x12\x10\n\x01n\x18\x02 \x01(\x03:\x05\x31\x36\x33\x38\x34\x12\x0c\n\x01r\x18\x03 \x01(\x05:\x01\x38\x12\x0c\n\x01p\x18\x04 \x01(\x05:\x01\x31\"8\n\tExtension\x12\n\n\x02id\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\x12\x11\n\tmandatory\x18\x03 \x02(\x08\" \n\x03Tag\x12\x0b\n\x03tag\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"5\n\x11TransactionSigner\x12\x12\n\nclass_name\x18\x01 \x02(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x89\x05\n\x06Wallet\x12\x1a\n\x12network_identifier\x18\x01 \x02(\t\x12\x1c\n\x14last_seen_block_hash\x18\x02 \x01(\x0c\x12\x1e\n\x16last_seen_block_height\x18\x0c \x01(\r\x12!\n\x19last_seen_block_time_secs\x18\x0e \x01(\x03\x12\x18\n\x03key\x18\x03 \x03(\x0b\x32\x0b.wallet.Key\x12(\n\x0btransaction\x18\x04 \x03(\x0b\x32\x13.wallet.Transaction\x12&\n\x0ewatched_script\x18\x0f \x03(\x0b\x32\x0e.wallet.Script\x12\x43\n\x0f\x65ncryption_type\x18\x05 \x01(\x0e\x32\x1d.wallet.Wallet.EncryptionType:\x0bUNENCRYPTED\x12\x37\n\x15\x65ncryption_parameters\x18\x06 \x01(\x0b\x32\x18.wallet.ScryptParameters\x12\x12\n\x07version\x18\x07 \x01(\x05:\x01\x31\x12$\n\textension\x18\n \x03(\x0b\x32\x11.wallet.Extension\x12\x13\n\x0b\x64\x65scription\x18\x0b \x01(\t\x12\x19\n\x11key_rotation_time\x18\r \x01(\x04\x12\x19\n\x04tags\x18\x10 \x03(\x0b\x32\x0b.wallet.Tag\x12\x36\n\x13transaction_signers\x18\x11 \x03(\x0b\x32\x19.wallet.TransactionSigner\x12\x1e\n\x13sigsRequiredToSpend\x18\x12 \x01(\r:\x01\x31\";\n\x0e\x45ncryptionType\x12\x0f\n\x0bUNENCRYPTED\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\"R\n\x0c\x45xchangeRate\x12\x12\n\ncoin_value\x18\x01 \x02(\x03\x12\x12\n\nfiat_value\x18\x02 \x02(\x03\x12\x1a\n\x12\x66iat_currency_code\x18\x03 \x02(\tB\x1d\n\x13org.bitcoinj.walletB\x06Protos' ) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -29,26 +29,31 @@ full_name='wallet.Key.Type', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='ORIGINAL', index=0, number=1, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='ENCRYPTED_SCRYPT_AES', index=1, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='DETERMINISTIC_MNEMONIC', index=2, number=3, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='DETERMINISTIC_KEY', index=3, number=4, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=607, serialized_end=704, ) @@ -59,30 +64,36 @@ full_name='wallet.TransactionConfidence.Type', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='UNKNOWN', index=0, number=0, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='BUILDING', index=1, number=1, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='PENDING', index=2, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='NOT_IN_BEST_CHAIN', index=3, number=3, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='DEAD', index=4, number=4, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=1287, serialized_end=1366, ) @@ -93,22 +104,26 @@ full_name='wallet.TransactionConfidence.Source', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='SOURCE_UNKNOWN', index=0, number=0, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='SOURCE_NETWORK', index=1, number=1, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='SOURCE_SELF', index=2, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=1368, serialized_end=1433, ) @@ -119,34 +134,41 @@ full_name='wallet.Transaction.Pool', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='UNSPENT', index=0, number=4, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='SPENT', index=1, number=5, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='INACTIVE', index=2, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='DEAD', index=3, number=10, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='PENDING', index=4, number=16, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='PENDING_INACTIVE', index=5, number=18, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=1888, serialized_end=1977, ) @@ -157,34 +179,41 @@ full_name='wallet.Transaction.Purpose', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='UNKNOWN', index=0, number=0, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='USER_PAYMENT', index=1, number=1, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='KEY_ROTATION', index=2, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='ASSURANCE_CONTRACT_CLAIM', index=3, number=3, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='ASSURANCE_CONTRACT_PLEDGE', index=4, number=4, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='ASSURANCE_CONTRACT_STUB', index=5, number=5, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=1980, serialized_end=2128, ) @@ -195,18 +224,21 @@ full_name='wallet.Wallet.EncryptionType', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='UNENCRYPTED', index=0, number=1, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='ENCRYPTED_SCRYPT_AES', index=1, number=2, - options=None, - type=None), + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, - options=None, + serialized_options=None, serialized_start=2948, serialized_end=3007, ) @@ -219,36 +251,38 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='ip_address', full_name='wallet.PeerAddress.ip_address', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='port', full_name='wallet.PeerAddress.port', index=1, number=2, type=13, cpp_type=3, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='services', full_name='wallet.PeerAddress.services', index=2, number=3, type=4, cpp_type=4, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -263,29 +297,31 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='initialisation_vector', full_name='wallet.EncryptedData.initialisation_vector', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='encrypted_private_key', full_name='wallet.EncryptedData.encrypted_private_key', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -300,50 +336,52 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='chain_code', full_name='wallet.DeterministicKey.chain_code', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='path', full_name='wallet.DeterministicKey.path', index=1, number=2, type=13, cpp_type=3, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='issued_subkeys', full_name='wallet.DeterministicKey.issued_subkeys', index=2, number=3, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='lookahead_size', full_name='wallet.DeterministicKey.lookahead_size', index=3, number=4, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='isFollowing', full_name='wallet.DeterministicKey.isFollowing', index=4, number=5, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -358,6 +396,7 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='type', full_name='wallet.Key.type', index=0, @@ -365,63 +404,63 @@ has_default_value=False, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='secret_bytes', full_name='wallet.Key.secret_bytes', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='encrypted_data', full_name='wallet.Key.encrypted_data', index=2, number=6, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='public_key', full_name='wallet.Key.public_key', index=3, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='label', full_name='wallet.Key.label', index=4, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='creation_timestamp', full_name='wallet.Key.creation_timestamp', index=5, number=5, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='deterministic_key', full_name='wallet.Key.deterministic_key', index=6, number=7, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='deterministic_seed', full_name='wallet.Key.deterministic_seed', index=7, number=8, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='encrypted_deterministic_seed', full_name='wallet.Key.encrypted_deterministic_seed', index=8, number=9, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -429,8 +468,9 @@ enum_types=[ _KEY_TYPE, ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -445,29 +485,31 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='program', full_name='wallet.Script.program', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='creation_timestamp', full_name='wallet.Script.creation_timestamp', index=1, number=2, type=3, cpp_type=2, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -482,50 +524,52 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='transaction_out_point_hash', full_name='wallet.TransactionInput.transaction_out_point_hash', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='transaction_out_point_index', full_name='wallet.TransactionInput.transaction_out_point_index', index=1, number=2, type=13, cpp_type=3, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='script_bytes', full_name='wallet.TransactionInput.script_bytes', index=2, number=3, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='sequence', full_name='wallet.TransactionInput.sequence', index=3, number=4, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='value', full_name='wallet.TransactionInput.value', index=4, number=5, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -540,6 +584,7 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='value', full_name='wallet.TransactionOutput.value', index=0, @@ -547,36 +592,37 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='script_bytes', full_name='wallet.TransactionOutput.script_bytes', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='spent_by_transaction_hash', full_name='wallet.TransactionOutput.spent_by_transaction_hash', index=2, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='spent_by_transaction_index', full_name='wallet.TransactionOutput.spent_by_transaction_index', index=3, number=4, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -591,6 +637,7 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='type', full_name='wallet.TransactionConfidence.type', index=0, @@ -598,42 +645,42 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='appeared_at_height', full_name='wallet.TransactionConfidence.appeared_at_height', index=1, number=2, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='overriding_transaction', full_name='wallet.TransactionConfidence.overriding_transaction', index=2, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='depth', full_name='wallet.TransactionConfidence.depth', index=3, number=4, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='broadcast_by', full_name='wallet.TransactionConfidence.broadcast_by', index=4, number=6, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='source', full_name='wallet.TransactionConfidence.source', index=5, number=7, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -642,8 +689,9 @@ _TRANSACTIONCONFIDENCE_TYPE, _TRANSACTIONCONFIDENCE_SOURCE, ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -658,6 +706,7 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='version', full_name='wallet.Transaction.version', index=0, @@ -665,91 +714,91 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='hash', full_name='wallet.Transaction.hash', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='pool', full_name='wallet.Transaction.pool', index=2, number=3, type=14, cpp_type=8, label=1, has_default_value=False, default_value=4, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='lock_time', full_name='wallet.Transaction.lock_time', index=3, number=4, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='updated_at', full_name='wallet.Transaction.updated_at', index=4, number=5, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='transaction_input', full_name='wallet.Transaction.transaction_input', index=5, number=6, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='transaction_output', full_name='wallet.Transaction.transaction_output', index=6, number=7, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='block_hash', full_name='wallet.Transaction.block_hash', index=7, number=8, type=12, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='block_relativity_offsets', full_name='wallet.Transaction.block_relativity_offsets', index=8, number=11, type=5, cpp_type=1, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='confidence', full_name='wallet.Transaction.confidence', index=9, number=9, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='purpose', full_name='wallet.Transaction.purpose', index=10, number=10, type=14, cpp_type=8, label=1, has_default_value=True, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='exchange_rate', full_name='wallet.Transaction.exchange_rate', index=11, number=12, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='memo', full_name='wallet.Transaction.memo', index=12, number=13, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -758,8 +807,9 @@ _TRANSACTION_POOL, _TRANSACTION_PURPOSE, ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -774,43 +824,45 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='salt', full_name='wallet.ScryptParameters.salt', index=0, number=1, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='n', full_name='wallet.ScryptParameters.n', index=1, number=2, type=3, cpp_type=2, label=1, has_default_value=True, default_value=16384, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='r', full_name='wallet.ScryptParameters.r', index=2, number=3, type=5, cpp_type=1, label=1, has_default_value=True, default_value=8, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='p', full_name='wallet.ScryptParameters.p', index=3, number=4, type=5, cpp_type=1, label=1, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -825,36 +877,38 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='id', full_name='wallet.Extension.id', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='data', full_name='wallet.Extension.data', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='mandatory', full_name='wallet.Extension.mandatory', index=2, number=3, type=8, cpp_type=7, label=2, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -869,29 +923,31 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='tag', full_name='wallet.Tag.tag', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='data', full_name='wallet.Tag.data', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -906,29 +962,31 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='class_name', full_name='wallet.TransactionSigner.class_name', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='data', full_name='wallet.TransactionSigner.data', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -943,119 +1001,120 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='network_identifier', full_name='wallet.Wallet.network_identifier', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='last_seen_block_hash', full_name='wallet.Wallet.last_seen_block_hash', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='last_seen_block_height', full_name='wallet.Wallet.last_seen_block_height', index=2, number=12, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='last_seen_block_time_secs', full_name='wallet.Wallet.last_seen_block_time_secs', index=3, number=14, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='key', full_name='wallet.Wallet.key', index=4, number=3, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='transaction', full_name='wallet.Wallet.transaction', index=5, number=4, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='watched_script', full_name='wallet.Wallet.watched_script', index=6, number=15, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='encryption_type', full_name='wallet.Wallet.encryption_type', index=7, number=5, type=14, cpp_type=8, label=1, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='encryption_parameters', full_name='wallet.Wallet.encryption_parameters', index=8, number=6, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='version', full_name='wallet.Wallet.version', index=9, number=7, type=5, cpp_type=1, label=1, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='extension', full_name='wallet.Wallet.extension', index=10, number=10, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='description', full_name='wallet.Wallet.description', index=11, number=11, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='key_rotation_time', full_name='wallet.Wallet.key_rotation_time', index=12, number=13, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='tags', full_name='wallet.Wallet.tags', index=13, number=16, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='transaction_signers', full_name='wallet.Wallet.transaction_signers', index=14, number=17, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='sigsRequiredToSpend', full_name='wallet.Wallet.sigsRequiredToSpend', index=15, number=18, type=13, cpp_type=3, label=1, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -1063,8 +1122,9 @@ enum_types=[ _WALLET_ENCRYPTIONTYPE, ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -1079,6 +1139,7 @@ filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='coin_value', full_name='wallet.ExchangeRate.coin_value', index=0, @@ -1086,29 +1147,30 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='fiat_value', full_name='wallet.ExchangeRate.fiat_value', index=1, number=2, type=3, cpp_type=2, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='fiat_currency_code', full_name='wallet.ExchangeRate.fiat_currency_code', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, + syntax='proto2', extension_ranges=[], oneofs=[ ], @@ -1158,113 +1220,113 @@ DESCRIPTOR.message_types_by_name['TransactionSigner'] = _TRANSACTIONSIGNER DESCRIPTOR.message_types_by_name['Wallet'] = _WALLET DESCRIPTOR.message_types_by_name['ExchangeRate'] = _EXCHANGERATE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) -PeerAddress = _reflection.GeneratedProtocolMessageType('PeerAddress', (_message.Message,), dict( - DESCRIPTOR = _PEERADDRESS, - __module__ = 'wallet_pb2' +PeerAddress = _reflection.GeneratedProtocolMessageType('PeerAddress', (_message.Message,), { + 'DESCRIPTOR' : _PEERADDRESS, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.PeerAddress) - )) + }) _sym_db.RegisterMessage(PeerAddress) -EncryptedData = _reflection.GeneratedProtocolMessageType('EncryptedData', (_message.Message,), dict( - DESCRIPTOR = _ENCRYPTEDDATA, - __module__ = 'wallet_pb2' +EncryptedData = _reflection.GeneratedProtocolMessageType('EncryptedData', (_message.Message,), { + 'DESCRIPTOR' : _ENCRYPTEDDATA, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.EncryptedData) - )) + }) _sym_db.RegisterMessage(EncryptedData) -DeterministicKey = _reflection.GeneratedProtocolMessageType('DeterministicKey', (_message.Message,), dict( - DESCRIPTOR = _DETERMINISTICKEY, - __module__ = 'wallet_pb2' +DeterministicKey = _reflection.GeneratedProtocolMessageType('DeterministicKey', (_message.Message,), { + 'DESCRIPTOR' : _DETERMINISTICKEY, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.DeterministicKey) - )) + }) _sym_db.RegisterMessage(DeterministicKey) -Key = _reflection.GeneratedProtocolMessageType('Key', (_message.Message,), dict( - DESCRIPTOR = _KEY, - __module__ = 'wallet_pb2' +Key = _reflection.GeneratedProtocolMessageType('Key', (_message.Message,), { + 'DESCRIPTOR' : _KEY, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Key) - )) + }) _sym_db.RegisterMessage(Key) -Script = _reflection.GeneratedProtocolMessageType('Script', (_message.Message,), dict( - DESCRIPTOR = _SCRIPT, - __module__ = 'wallet_pb2' +Script = _reflection.GeneratedProtocolMessageType('Script', (_message.Message,), { + 'DESCRIPTOR' : _SCRIPT, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Script) - )) + }) _sym_db.RegisterMessage(Script) -TransactionInput = _reflection.GeneratedProtocolMessageType('TransactionInput', (_message.Message,), dict( - DESCRIPTOR = _TRANSACTIONINPUT, - __module__ = 'wallet_pb2' +TransactionInput = _reflection.GeneratedProtocolMessageType('TransactionInput', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONINPUT, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.TransactionInput) - )) + }) _sym_db.RegisterMessage(TransactionInput) -TransactionOutput = _reflection.GeneratedProtocolMessageType('TransactionOutput', (_message.Message,), dict( - DESCRIPTOR = _TRANSACTIONOUTPUT, - __module__ = 'wallet_pb2' +TransactionOutput = _reflection.GeneratedProtocolMessageType('TransactionOutput', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONOUTPUT, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.TransactionOutput) - )) + }) _sym_db.RegisterMessage(TransactionOutput) -TransactionConfidence = _reflection.GeneratedProtocolMessageType('TransactionConfidence', (_message.Message,), dict( - DESCRIPTOR = _TRANSACTIONCONFIDENCE, - __module__ = 'wallet_pb2' +TransactionConfidence = _reflection.GeneratedProtocolMessageType('TransactionConfidence', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONCONFIDENCE, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.TransactionConfidence) - )) + }) _sym_db.RegisterMessage(TransactionConfidence) -Transaction = _reflection.GeneratedProtocolMessageType('Transaction', (_message.Message,), dict( - DESCRIPTOR = _TRANSACTION, - __module__ = 'wallet_pb2' +Transaction = _reflection.GeneratedProtocolMessageType('Transaction', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTION, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Transaction) - )) + }) _sym_db.RegisterMessage(Transaction) -ScryptParameters = _reflection.GeneratedProtocolMessageType('ScryptParameters', (_message.Message,), dict( - DESCRIPTOR = _SCRYPTPARAMETERS, - __module__ = 'wallet_pb2' +ScryptParameters = _reflection.GeneratedProtocolMessageType('ScryptParameters', (_message.Message,), { + 'DESCRIPTOR' : _SCRYPTPARAMETERS, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.ScryptParameters) - )) + }) _sym_db.RegisterMessage(ScryptParameters) -Extension = _reflection.GeneratedProtocolMessageType('Extension', (_message.Message,), dict( - DESCRIPTOR = _EXTENSION, - __module__ = 'wallet_pb2' +Extension = _reflection.GeneratedProtocolMessageType('Extension', (_message.Message,), { + 'DESCRIPTOR' : _EXTENSION, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Extension) - )) + }) _sym_db.RegisterMessage(Extension) -Tag = _reflection.GeneratedProtocolMessageType('Tag', (_message.Message,), dict( - DESCRIPTOR = _TAG, - __module__ = 'wallet_pb2' +Tag = _reflection.GeneratedProtocolMessageType('Tag', (_message.Message,), { + 'DESCRIPTOR' : _TAG, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Tag) - )) + }) _sym_db.RegisterMessage(Tag) -TransactionSigner = _reflection.GeneratedProtocolMessageType('TransactionSigner', (_message.Message,), dict( - DESCRIPTOR = _TRANSACTIONSIGNER, - __module__ = 'wallet_pb2' +TransactionSigner = _reflection.GeneratedProtocolMessageType('TransactionSigner', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONSIGNER, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.TransactionSigner) - )) + }) _sym_db.RegisterMessage(TransactionSigner) -Wallet = _reflection.GeneratedProtocolMessageType('Wallet', (_message.Message,), dict( - DESCRIPTOR = _WALLET, - __module__ = 'wallet_pb2' +Wallet = _reflection.GeneratedProtocolMessageType('Wallet', (_message.Message,), { + 'DESCRIPTOR' : _WALLET, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.Wallet) - )) + }) _sym_db.RegisterMessage(Wallet) -ExchangeRate = _reflection.GeneratedProtocolMessageType('ExchangeRate', (_message.Message,), dict( - DESCRIPTOR = _EXCHANGERATE, - __module__ = 'wallet_pb2' +ExchangeRate = _reflection.GeneratedProtocolMessageType('ExchangeRate', (_message.Message,), { + 'DESCRIPTOR' : _EXCHANGERATE, + '__module__' : 'wallet_pb2' # @@protoc_insertion_point(class_scope:wallet.ExchangeRate) - )) + }) _sym_db.RegisterMessage(ExchangeRate) -DESCRIPTOR.has_options = True -DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\023org.bitcoinj.walletB\006Protos')) +DESCRIPTOR._options = None # @@protoc_insertion_point(module_scope) diff --git a/btcrecover/btcrpass.py b/btcrecover/btcrpass.py index 51d7cb504..354748764 100644 --- a/btcrecover/btcrpass.py +++ b/btcrecover/btcrpass.py @@ -1,6 +1,8 @@ # btcrpass.py -- btcrecover main library # Copyright (C) 2014-2017 Christopher Gurnee -# +# 2020 Jefferson Nunn and Gaith +# 2019-2021 Stephen Rothery +# # This file is part of btcrecover. # # btcrecover is free software: you can redistribute it and/or @@ -16,29 +18,159 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! # TODO: put everything in a class? # TODO: pythonize comments/documentation -# (all optional futures for 2.7) -from __future__ import print_function, absolute_import, division, unicode_literals - -__version__ = "0.17.10" +__version__ = "1.13.0-Cryptoguide" __ordering_version__ = b"0.6.4" # must be updated whenever password ordering changes +disable_security_warnings = True + +BIP39_OPENCL_MEMORY_PER_THREAD_BYTES = 2 * 1024 ** 3 # ~2 GiB per worker + +# Import modules included in standard libraries +import sys, argparse, itertools, string, re, multiprocessing, signal, os, pickle, gc, \ + time, timeit, hashlib, collections, base64, struct, atexit, zlib, math, json, numbers, datetime, binascii, gzip + +# Import modules bundled with BTCRecover +import btcrecover.opencl_helpers +from . import success_alert +from .trezor_common_mistakes import TREZOR_COMMON_MISTAKES +import lib.cardano.cardano_utils as cardano +from lib.eth_hash.auto import keccak +from lib.mnemonic_btc_com_tweaked import Mnemonic + +module_leveldb_available = False +try: + from lib.ccl_chrome_indexeddb import ccl_leveldb + + module_leveldb_available = True +except: + pass + +hashlib_ripemd160_available = False +# Enable functions that may not work for some standard libraries in some environments +try: + # this will work with micropython and python < 3.10 + # but will raise and exception if ripemd is not supported (python3.10, openssl 3) + hashlib.new('ripemd160') + hashlib_ripemd160_available = True + def ripemd160(msg): + return hashlib.new('ripemd160', msg).digest() +except: + # otherwise use pure python implementation + from lib.embit.py_ripemd160 import ripemd160 + +# Import modules from requirements.txt +from Crypto.Cipher import AES + +# Import optional modules +module_opencl_available = False +try: + from lib.opencl_brute import opencl + from lib.opencl_brute.opencl_information import opencl_information + import pyopencl + + module_opencl_available = True +except: + pass + +# Eth Keystore Libraries +module_eth_keyfile_available = False +try: + import eth_keyfile + + module_eth_keyfile_available = True +except: + pass + +# PY Crypto HD wallet module +py_crypto_hd_wallet_available = False +try: + import py_crypto_hd_wallet + + py_crypto_hd_wallet_available = True +except: + pass + +# Shamir-Mnemonic module +shamir_mnemonic_available = False +try: + import shamir_mnemonic + + from shamir_mnemonic.recovery import RecoveryState + from shamir_mnemonic.shamir import generate_mnemonics + from shamir_mnemonic.share import Share + from shamir_mnemonic.utils import MnemonicError + + import click + from click import style + + FINISHED = style("\u2713", fg="green", bold=True) + EMPTY = style("\u2717", fg="red", bold=True) + INPROGRESS = style("\u25cf", fg="yellow", bold=True) + + + def error(s: str) -> None: + click.echo(style("ERROR: ", fg="red") + s) + + shamir_mnemonic_available = True +except: + pass + +# Modules dependant on bitcoinutils +bitcoinutils_available = False +try: + import lib.block_io + bitcoinutils_available = True +except: + pass + +# Modules dependant on SJCL +sjcl_available = False +try: + from sjcl import SJCL + sjcl_available = True +except: + pass + +# Nacl +nacl_available = False +try: + import nacl.pwhash + import nacl.secret + + nacl_available = True +except: + pass + +# Argument namespace populated by parse_arguments(); +# initialized here to allow direct wallet class use in tests +args = argparse.Namespace() + +passwordlist_file = None +initial_passwordlist = () +passwordlist_allcached = False +passwordlist_first_line_num = 1 +passwordlist_embedded_arguments = False + +searchfailedtext = "\nAll possible passwords (as specified in your tokenlist or passwordlist) have been checked and none are correct for this wallet. You could consider trying again with a different password list or expanded tokenlist..." + +def load_customTokenWildcard(customTokenWildcardFile): + customTokenWildcards = [''] + if customTokenWildcardFile: + try: + customTokenWildcards_File = open(customTokenWildcardFile, "r", encoding="utf-8", errors='ignore') + customTokenWildcards_Lines = customTokenWildcards_File.readlines() -import sys, argparse, itertools, string, re, multiprocessing, signal, os, cPickle, gc, \ - time, timeit, hashlib, collections, base64, struct, atexit, zlib, math, json, numbers - -# The progressbar module is recommended but optional; it is typically -# distributed with btcrecover (it is loaded later on demand) - + for customTokenWildcard in customTokenWildcards_Lines: + customTokenWildcards.append(customTokenWildcard.strip()) + customTokenWildcards_File.close() + except Exception as e: + print(e) + return customTokenWildcards +# Assemble and output some information about the current system and python environment. def full_version(): from struct import calcsize return "btcrecover {} on Python {} {}-bit, {}-bit unicodes, {}-bit ints".format( @@ -46,7 +178,7 @@ def full_version(): ".".join(str(i) for i in sys.version_info[:3]), calcsize(b"P") * 8, sys.maxunicode.bit_length(), - sys.maxint.bit_length() + 1 + sys.maxsize.bit_length() + 1 ) @@ -54,18 +186,11 @@ def full_version(): def enable_unicode_mode(): global io, tstr, tstr_from_stdin, tchr import locale, io - tstr = unicode + tstr = str preferredencoding = locale.getpreferredencoding() - tstr_from_stdin = lambda s: s if isinstance(s, unicode) else unicode(s, preferredencoding) - tchr = unichr + tstr_from_stdin = lambda s: s if isinstance(s, str) else str(s, preferredencoding) + tchr = chr # -def enable_ascii_mode(): - global io, tstr, tstr_from_stdin, tchr - io = None - tstr = str - tstr_from_stdin = str - tchr = chr - ################################### Configurables/Plugins ################################### # wildcard sets, simple typo generators, and wallet support functions @@ -74,17 +199,22 @@ def enable_ascii_mode(): # Recognized wildcard (e.g. %d, %a) types mapped to their associated sets # of characters; used in expand_wildcards_generator() # warning: these can't be the key for a wildcard set: digits 'i' 'b' '[' ',' ';' '-' '<' '>' -def init_wildcards(): +def init_wildcards(wildcard_custom_list_e = None, + wildcard_custom_list_f = None, + wildcard_custom_list_j = None, + wildcard_custom_list_k = None): global wildcard_sets, wildcard_keys, wildcard_nocase_sets, wildcard_re, \ custom_wildcard_cache, backreference_maps, backreference_maps_sha1 # N.B. that tstr() will not convert string.*case to Unicode correctly if the locale has # been set to one with a single-byte code page e.g. ISO-8859-1 (Latin1) or Windows-1252 wildcard_sets = { + tstr("H") : tstr(string.hexdigits), + tstr("B") : tstr("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), tstr("d") : tstr(string.digits), - tstr("a") : tstr(string.lowercase), - tstr("A") : tstr(string.uppercase), - tstr("n") : tstr(string.lowercase + string.digits), - tstr("N") : tstr(string.uppercase + string.digits), + tstr("a") : tstr(string.ascii_lowercase), + tstr("A") : tstr(string.ascii_uppercase), + tstr("n") : tstr(string.ascii_lowercase + string.digits), + tstr("N") : tstr(string.ascii_uppercase + string.digits), tstr("s") : tstr(" "), # space tstr("l") : tstr("\n"), # line feed tstr("r") : tstr("\r"), # carriage return @@ -95,21 +225,27 @@ def init_wildcards(): tstr("W") : tstr(" \r\n\t"), # space, newline, and tab tstr("y") : tstr(string.punctuation), tstr("Y") : tstr(string.digits + string.punctuation), - tstr("p") : tstr().join(map(tchr, xrange(33, 127))), # all ASCII printable characters except whitespace - tstr("P") : tstr().join(map(tchr, xrange(33, 127))) + tstr(" \r\n\t"), # as above, plus space, newline, and tab + tstr("p") : tstr().join(map(tchr, range(33, 127))), # all ASCII printable characters except whitespace + tstr("P") : tstr().join(map(tchr, range(33, 127))) + tstr(" \r\n\t"), # as above, plus space, newline, and tab + tstr("q") : tstr().join(map(tchr, range(33, 127))) + tstr(" "), # all ASCII printable characters plus whitespace (All characters that are easily available for a Trezor Passphrase via keyboard or touchscreen entry) + tstr("U"): ''.join(chr(i) for i in range(65536)), # All possible 16 bit unicode characters + tstr("e"): load_customTokenWildcard(wildcard_custom_list_e), # %e and %f are special types of wildcards which can both be customised AND can occur multiple times, but always have the same value. (And can also include other types of wildcards) + tstr("f"): load_customTokenWildcard(wildcard_custom_list_f), + tstr("j"): load_customTokenWildcard(wildcard_custom_list_j), # %j and %k behave mostly like standard wildcards but can be entire words/strings and are loaded from a custom file + tstr("k"): load_customTokenWildcard(wildcard_custom_list_k), # wildcards can be used to escape these special symbols tstr("%") : tstr("%"), tstr("^") : tstr("^"), - tstr("S") : tstr("$") # the key is intentionally a capital "S", the value is a dollar sign + tstr("S") : tstr("$"), # the key is intentionally a capital "S", the value is a dollar sign } wildcard_keys = tstr().join(wildcard_sets) # # case-insensitive versions (e.g. %ia) of wildcard_sets for those which have them wildcard_nocase_sets = { - tstr("a") : tstr(string.lowercase + string.uppercase), - tstr("A") : tstr(string.uppercase + string.lowercase), - tstr("n") : tstr(string.lowercase + string.uppercase + string.digits), - tstr("N") : tstr(string.uppercase + string.lowercase + string.digits) + tstr("a") : tstr(string.ascii_lowercase + string.ascii_uppercase), + tstr("A") : tstr(string.ascii_uppercase + string.ascii_lowercase), + tstr("n") : tstr(string.ascii_lowercase + string.ascii_uppercase + string.digits), + tstr("N") : tstr(string.ascii_uppercase + string.ascii_lowercase + string.digits) } # wildcard_re = None @@ -142,7 +278,11 @@ def typo_closecase(p, i): # Returns a swapped case only when case transitions return () # def typo_replace_wildcard(p, i): return [e for e in typos_replace_expanded if e != p[i]] -def typo_map(p, i): return typos_map.get(p[i], ()) + +def typo_map(p, i): + returnVal = "".join(list(typos_map.get(p[i], ()))) + return returnVal + # (typos_replace_expanded and typos_map are initialized from args.typos_replace # and args.typos_map respectively in parse_arguments() ) # @@ -176,10 +316,12 @@ def register_wallet_class(cls): global wallet_types, wallet_types_by_id wallet_types.append(cls) try: - assert cls.data_extract_id not in wallet_types_by_id,\ + assert cls.data_extract_id() not in wallet_types_by_id,\ "register_wallet_class: registered wallet types must have unique data_extract_id's" - wallet_types_by_id[cls.data_extract_id] = cls - except AttributeError: pass + wallet_types_by_id[cls.data_extract_id()] = cls + except AttributeError: + pass + return cls # Clears the current set of registered wallets (including those registered by default below) @@ -197,14 +339,19 @@ def load_wallet(wallet_filename): # Ask each registered wallet type if the file might be of their type, # and if so load the wallet uncertain_wallet_types = [] - with open(wallet_filename, "rb") as wallet_file: - for wallet_type in wallet_types: - found = wallet_type.is_wallet_file(wallet_file) - if found: - wallet_file.close() - return wallet_type.load_from_filename(wallet_filename) - elif found is None: # None means it might still be this type of wallet... - uncertain_wallet_types.append(wallet_type) + try: + with open(wallet_filename, "rb") as wallet_file: + for wallet_type in wallet_types: + found = wallet_type.is_wallet_file(wallet_file) + if found: + wallet_file.close() + return wallet_type.load_from_filename(wallet_filename) + elif found is None: # None means it might still be this type of wallet... + uncertain_wallet_types.append(wallet_type) + except PermissionError: #Metamask wallets can be a folder which may throw a PermissionError or IsADirectoryError + return WalletMetamask.load_from_filename(wallet_filename) + except IsADirectoryError: + return WalletMetamask.load_from_filename(wallet_filename) # If the wallet type couldn't be definitively determined, try each # questionable type (which must raise ValueError on a load failure) @@ -212,8 +359,8 @@ def load_wallet(wallet_filename): for wallet_type in uncertain_wallet_types: try: return wallet_type.load_from_filename(wallet_filename) - except ValueError as e: - uncertain_errors.append(wallet_type.__name__ + ": " + unicode(e)) + except Exception as e: + uncertain_errors.append(wallet_type.__name__ + ": " + str(e)) error_exit("unrecognized wallet format" + ("; heuristic parser(s) reported:\n " + "\n ".join(uncertain_errors) if uncertain_errors else "") ) @@ -239,9 +386,11 @@ def load_from_base64_key(key_crc_base64): if zlib.crc32(key_data) & 0xffffffff != key_crc: error_exit("encrypted key data is corrupted (failed CRC check)") - wallet_type = wallet_types_by_id.get(key_data[:2]) + wallet_type = wallet_types_by_id.get(key_data[:2].decode()) + if not wallet_type: - error_exit("unrecognized encrypted key type '"+key_data[:3]+"'") + print("Wallet Types:", wallet_types_by_id) + error_exit("unrecognized encrypted key type '" + key_data[:2].decode() + "'") loaded_wallet = wallet_type.load_from_data_extract(key_data[3:]) return key_crc @@ -250,17 +399,24 @@ def load_from_base64_key(key_crc_base64): # Load the OpenCL libraries and return a list of available devices cl_devices_avail = None def get_opencl_devices(): + """Return a cached list of available OpenCL devices.""" global pyopencl, numpy, cl_devices_avail if cl_devices_avail is None: try: import pyopencl, numpy - cl_devices_avail = filter(lambda d: d.available==1 and d.profile=="FULL_PROFILE" and d.endian_little==1, - itertools.chain(*[p.get_devices() for p in pyopencl.get_platforms()])) + platforms = pyopencl.get_platforms() + devices = [] + for p in platforms: + for d in p.get_devices(): + if d.available == 1 and d.profile == "FULL_PROFILE" and d.endian_little == 1: + devices.append(d) + cl_devices_avail = devices except ImportError as e: - print(prog+": warning:", e, file=sys.stderr) + print("Warning:", e, file=sys.stderr) cl_devices_avail = [] except pyopencl.LogicError as e: - if "platform not found" not in unicode(e): raise # unexpected error + if "platform not found" not in str(e): + raise # unexpected error cl_devices_avail = [] # PyOpenCL loaded OK but didn't find any supported hardware return cl_devices_avail @@ -269,7 +425,7 @@ def get_opencl_devices(): def est_entropy_bits(data): hist_bins = [0] * 256 for byte in data: - hist_bins[ord(byte)] += 1 + hist_bins[byte] += 1 entropy_bits = 0.0 for frequency in hist_bins: if frequency: @@ -283,398 +439,22 @@ def prompt_unicode_password(prompt, error_msg): from getpass import getpass encoding = sys.stdin.encoding or 'ASCII' if 'utf' not in encoding.lower(): - print(prog+": warning: terminal does not support UTF; passwords with non-ASCII chars might not work", file=sys.stderr) - prompt = b"(note your password will not be displayed as you type)\n" + prompt + print("Warning: terminal does not support UTF; passwords with non-ASCII chars might not work", file=sys.stderr) + prompt = "(note your password will not be displayed as you type)\n" + prompt password = getpass(prompt) if not password: error_exit(error_msg) - if isinstance(password, str): - password = password.decode(encoding) # convert from terminal's encoding to unicode return password - -############### Armory ############### - -# Try to add the Armory libraries to the path for various platforms -is_armory_path_added = False -def add_armory_library_path(): - global is_armory_path_added - if is_armory_path_added: return - if sys.platform == "win32": - progfiles_path = os.environ.get("ProgramFiles", r"C:\Program Files") # default is for XP - armory_path = progfiles_path + r"\Armory" - sys.path.extend((armory_path, armory_path + r"\library.zip")) - # 64-bit Armory might install into the 32-bit directory; if this is 64-bit Python look in both - if struct.calcsize(b"P") * 8 == 64: # calcsize('P') is a pointer's size in bytes - assert not progfiles_path.endswith("(x86)"), "ProgramFiles doesn't end with '(x86)' on x64 Python" - progfiles_path += " (x86)" - armory_path = progfiles_path + r"\Armory" - sys.path.extend((armory_path, armory_path + r"\library.zip")) - elif sys.platform.startswith("linux"): - sys.path.extend(("/usr/local/lib/armory", "/usr/lib/armory")) - elif sys.platform == "darwin": - import glob - sys.path.extend(( - "/Applications/Armory.app/Contents/MacOS/py/usr/local/lib/armory", - "/Applications/Armory.app/Contents/MacOS/py/usr/lib/armory", - "/Applications/Armory.app/Contents/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages")) - sys.path.extend(glob.iglob( - "/Applications/Armory.app/Contents/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/*.egg")) - is_armory_path_added = True - -is_armory_loaded = False -def load_armory_library(): - if tstr == unicode: - error_exit("armory wallets do not support unicode; please remove the --utf8 option") - global is_armory_loaded - if is_armory_loaded: return - - # Temporarily blank out argv before importing Armory, otherwise it attempts to process argv, - # and then add this one option to avoid a confusing warning message from Armory - old_argv = sys.argv[1:] - sys.argv[1:] = ["--language", "es"] - - add_armory_library_path() - try: - # Try up to 10 times to load the first Armory library (there's a race - # condition on opening an Armory log file in Windows when multiprocessing) - import random - for i in xrange(10): - try: - from armoryengine.ArmoryUtils import getVersionInt, readVersionString, BTCARMORY_VERSION - except IOError as e: - if i<9 and e.filename.endswith(r"\armorylog.txt"): - time.sleep(random.uniform(0.05, 0.15)) - else: raise # unexpected failure - except SystemExit: - if len(sys.argv) == 3: - del sys.argv[1:] # older versions of Armory don't support the --language option; remove it - else: raise # unexpected failure - except ImportError as e: - if "not a valid Win32 application" in unicode(e): - print(prog+": error: can't load Armory, 32/64 bit mismatch between it and Python", file=sys.stderr) - raise - else: break # when it succeeds - - # Fixed https://github.com/etotheipi/BitcoinArmory/issues/196 - if getVersionInt(BTCARMORY_VERSION) < getVersionInt(readVersionString("0.92")): - error_exit("Armory version 0.92 or greater is required") - - # These are the modules we actually need - global PyBtcWallet, PyBtcAddress, SecureBinaryData, KdfRomix - from armoryengine.PyBtcWallet import PyBtcWallet - from armoryengine.PyBtcWallet import PyBtcAddress - from CppBlockUtils import SecureBinaryData, KdfRomix - is_armory_loaded = True - - finally: - sys.argv[1:] = old_argv # restore the command line - -@register_wallet_class -class WalletArmory(object): - - class __metaclass__(type): - @property - def data_extract_id(cls): return b"ar" - - @staticmethod - def passwords_per_seconds(seconds): - return max(int(round(4 * seconds)), 1) - - @staticmethod - def is_wallet_file(wallet_file): - wallet_file.seek(0) - return wallet_file.read(8) == b"\xbaWALLET\x00" # Armory magic - - def __init__(self, loading = False): - assert loading, 'use load_from_* to create a ' + self.__class__.__name__ - load_armory_library() - - def __getstate__(self): - # Extract data from unpicklable Armory library objects and delete them - state = self.__dict__.copy() - del state["_address"], state["_kdf"] - state["addrStr20"] = self._address.addrStr20 - state["binPrivKey32_Encr"] = self._address.binPrivKey32_Encr.toBinStr() - state["binInitVect16"] = self._address.binInitVect16.toBinStr() - state["binPublicKey65"] = self._address.binPublicKey65.toBinStr() - state["memoryReqtBytes"] = self._kdf.getMemoryReqtBytes() - state["numIterations"] = self._kdf.getNumIterations() - state["salt"] = self._kdf.getSalt().toBinStr() - return state - - def __setstate__(self, state): - # Restore unpicklable Armory library objects - global tstr - try: - assert tstr == str # load_armory_library() requires this; - except NameError: # but tstr doesn't exist when using multiprocessing on Windows - tstr = str # so apply this workaround - load_armory_library() - # - state["_address"] = PyBtcAddress().createFromEncryptedKeyData( - state["addrStr20"], - SecureBinaryData(state["binPrivKey32_Encr"]), - SecureBinaryData(state["binInitVect16"]), - pubKey=state["binPublicKey65"] # optional; makes checking slightly faster - ) - del state["addrStr20"], state["binPrivKey32_Encr"] - del state["binInitVect16"], state["binPublicKey65"] - # - state["_kdf"] = KdfRomix( - state["memoryReqtBytes"], - state["numIterations"], - SecureBinaryData(state["salt"]) - ) - del state["memoryReqtBytes"], state["numIterations"], state["salt"] - # - self.__dict__ = state - - # Load the Armory wallet file - @classmethod - def load_from_filename(cls, wallet_filename): - self = cls(loading=True) - wallet = PyBtcWallet().readWalletFile(wallet_filename) - self._address = wallet.addrMap['ROOT'] - self._kdf = wallet.kdf - if not self._address.hasPrivKey(): - error_exit("Armory wallet cannot be watching-only") - if not self._address.useEncryption : - error_exit("Armory wallet is not encrypted") - return self - - # Import an Armory private key that was extracted by extract-armory-privkey.py - @classmethod - def load_from_data_extract(cls, privkey_data): - self = cls(loading=True) - self._address = PyBtcAddress().createFromEncryptedKeyData( - privkey_data[:20], # address (160 bit hash) - SecureBinaryData(privkey_data[20:52]), # encrypted private key - SecureBinaryData(privkey_data[52:68]) # initialization vector - ) - bytes_reqd, iter_count = struct.unpack(b"< I I", privkey_data[68:76]) - self._kdf = KdfRomix(bytes_reqd, iter_count, SecureBinaryData(privkey_data[76:])) # kdf args and seed - return self - - def difficulty_info(self): - return "{:g} MiB, {} iterations + ECC".format(round(self._kdf.getMemoryReqtBytes() / 1024**2, 2), self._kdf.getNumIterations()) - - # Defer to either the cpu or OpenCL implementation - def return_verified_password_or_false(self, passwords): - return self._return_verified_password_or_false_opencl(passwords) if hasattr(self, "_cl_devices") \ - else self._return_verified_password_or_false_cpu(passwords) - - # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password - # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def _return_verified_password_or_false_cpu(self, passwords): - for count, password in enumerate(passwords, 1): - if self._address.verifyEncryptionKey(self._kdf.DeriveKey(SecureBinaryData(password))): - return password, count - else: - return False, count - - # Load and initialize the OpenCL kernel for Armory, given the global wallet and these params: - # devices - a list of one or more of the devices returned by get_opencl_devices() - # global_ws - a list of global work sizes, exactly one per device - # local_ws - a list of local work sizes (or Nones), exactly one per device - # int_rate - number of times to interrupt calculations to prevent hanging - # the GPU driver per call to return_verified_password_or_false() - # save_every- how frequently hashes are saved in the lookup table - # calc_memory-if true, just print the memory statistics and exit - def init_opencl_kernel(self, devices, global_ws, local_ws, int_rate, save_every = 1, calc_memory = False): - # Need to save these for return_verified_password_or_false_opencl() - assert devices, "WalletArmory.init_opencl_kernel: at least one device is selected" - assert len(devices) == len(global_ws) == len(local_ws), "WalletArmory.init_opencl_kernel: one global_ws and one local_ws specified for each device" - assert save_every > 0 - self._cl_devices = devices - self._cl_global_ws = global_ws - self._cl_local_ws = local_ws - - self._cl_V_buffer0s = self._cl_V_buffer1s = self._cl_V_buffer2s = self._cl_V_buffer3s = None # clear any - self._cl_kernel = self._cl_kernel_fill = self._cl_queues = self._cl_hashes_buffers = None # previously loaded - cl_context = pyopencl.Context(devices) - # - # Load and compile the OpenCL program, passing in defines for SAVE_EVERY, V_LEN, and SALT - assert self._kdf.getMemoryReqtBytes() % 64 == 0 - v_len = self._kdf.getMemoryReqtBytes() // 64 - salt = self._kdf.getSalt().toBinStr() - assert len(salt) == 32 - with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "romix-ar-kernel.cl")) as opencl_file: - cl_program = pyopencl.Program(cl_context, opencl_file.read()).build( - b"-w -D SAVE_EVERY={}U -D V_LEN={}U -D SALT0=0x{:016x}UL -D SALT1=0x{:016x}UL -D SALT2=0x{:016x}UL -D SALT3=0x{:016x}UL" \ - .format(save_every, v_len, *struct.unpack(b">4Q", salt))) - # - # Configure and store for later the OpenCL kernels (the entrance functions) - self._cl_kernel_fill = cl_program.kernel_fill_V # this kernel is executed first - self._cl_kernel = cl_program.kernel_lookup_V # this kernel is executed once per iter_count - self._cl_kernel_fill.set_scalar_arg_dtypes([None, None, None, None, numpy.uint32, numpy.uint32, None, numpy.uint8]) - self._cl_kernel .set_scalar_arg_dtypes([None, None, None, None, numpy.uint32, None]) - # - # Check the local_ws sizes - for i, device in enumerate(devices): - if local_ws[i] is None: continue - max_local_ws = min(self._cl_kernel_fill.get_work_group_info(pyopencl.kernel_work_group_info.WORK_GROUP_SIZE, device), - self._cl_kernel .get_work_group_info(pyopencl.kernel_work_group_info.WORK_GROUP_SIZE, device)) - if local_ws[i] > max_local_ws: - error_exit("--local-ws of", local_ws[i], "exceeds max of", max_local_ws, "for GPU '"+device.name.strip()+"' with Armory wallets") - - if calc_memory: - mem_per_worker = math.ceil(v_len / save_every) * 64 + 64 - print( "Details for this wallet") - print( " ROMix V-table length: {:,}".format(v_len)) - print( " outer iteration count: {:,}".format(self._kdf.getNumIterations())) - print( " with --mem-factor {},".format(save_every if save_every>1 else "1 (the default)")) - print( " memory per global worker: {:,} KiB\n".format(int(round(mem_per_worker / 1024)))) - # - for i, device in enumerate(devices): - print("Details for", device.name.strip()) - print(" global memory size: {:,} MiB".format(int(round(device.global_mem_size / float(1024**2))))) - print(" with --mem-factor {},".format(save_every if save_every>1 else "1 (the default)")) - print(" est. max --global-ws: {}".format((int(device.global_mem_size // mem_per_worker) // 32 * 32))) - print(" with --global-ws {},".format(global_ws[i] if global_ws[i]!=4096 else "4096 (the default)")) - print(" est. memory usage: {:,} MiB\n".format(int(round(global_ws[i] * mem_per_worker / float(1024**2))))) - sys.exit(0) - - # Create one command queue, one I/O buffer, and four "V" buffers per device - self._cl_queues = [] - self._cl_hashes_buffers = [] - self._cl_V_buffer0s = [] - self._cl_V_buffer1s = [] - self._cl_V_buffer2s = [] - self._cl_V_buffer3s = [] - for i, device in enumerate(devices): - self._cl_queues.append(pyopencl.CommandQueue(cl_context, device)) - # Each I/O buffer is of len --global-ws * (size-of-sha512-hash-in-bytes == 512 bits / 8 == 64) - self._cl_hashes_buffers.append(pyopencl.Buffer(cl_context, pyopencl.mem_flags.READ_WRITE, global_ws[i] * 64)) - # - # The "V" buffers total v_len * 64 * --global-ws bytes per device. There are four - # per device, so each is 1/4 of the total. They are reduced by a factor of save_every, - # rounded up to the nearest 64-byte boundry (the size-of-sha512-hash-in-bytes) - assert global_ws[i] % 4 == 0 # (kdf.getMemoryReqtBytes() is already checked to be divisible by 64) - V_buffer_len = int(math.ceil(v_len / save_every)) * 64 * global_ws[i] // 4 - self._cl_V_buffer0s.append(pyopencl.Buffer(cl_context, pyopencl.mem_flags.READ_WRITE, V_buffer_len)) - self._cl_V_buffer1s.append(pyopencl.Buffer(cl_context, pyopencl.mem_flags.READ_WRITE, V_buffer_len)) - self._cl_V_buffer2s.append(pyopencl.Buffer(cl_context, pyopencl.mem_flags.READ_WRITE, V_buffer_len)) - self._cl_V_buffer3s.append(pyopencl.Buffer(cl_context, pyopencl.mem_flags.READ_WRITE, V_buffer_len)) - - # Doing all the work at once will hang the GPU. One set of passwords requires iter_count - # calls to cl_kernel_fill and to cl_kernel. Divide 2xint_rate among these calls (2x is - # an arbitrary choice) and then calculate how much work (v_len_chunksize) to perform for - # each call rounding up to to maximize the work done in the last sets to optimize performance. - int_rate = int(round(int_rate / self._kdf.getNumIterations())) or 1 # there are two 2's which cancel out - self._v_len_chunksize = v_len // int_rate or 1 - if self._v_len_chunksize % int_rate != 0: # if not evenly divisible, - self._v_len_chunksize += 1 # then round up. - if self._v_len_chunksize % 2 != 0: # also if not divisible by two, - self._v_len_chunksize += 1 # make it divisible by two. - - def _return_verified_password_or_false_opencl(self, passwords): - assert len(passwords) <= sum(self._cl_global_ws), "WalletArmory.return_verified_password_or_false_opencl: at most --global-ws passwords" - - # The first password hash is done by the CPU - salt = self._kdf.getSalt().toBinStr() - hashes = numpy.empty([sum(self._cl_global_ws), 64], numpy.uint8) - for i, password in enumerate(passwords): - hashes[i] = numpy.fromstring(hashlib.sha512(password + salt).digest(), numpy.uint8) - - # Divide up and copy the starting hashes into the OpenCL buffer(s) (one per device) in parallel - done = [] # a list of OpenCL event objects - offset = 0 - for devnum, ws in enumerate(self._cl_global_ws): - done.append(pyopencl.enqueue_copy(self._cl_queues[devnum], self._cl_hashes_buffers[devnum], - hashes[offset : offset + ws], is_blocking=False)) - self._cl_queues[devnum].flush() # Starts the copy operation - offset += ws - pyopencl.wait_for_events(done) - - v_len = self._kdf.getMemoryReqtBytes() // 64 - for i in xrange(self._kdf.getNumIterations()): - - # Doing all the work at once will hang the GPU, so instead do v_len_chunksize chunks - # at a time, pausing briefly while waiting for them to complete, and then continuing. - # Because the work is probably not evenly divisible by v_len_chunksize, the loops below - # perform all but the last of these v_len_chunksize sets of work. - - # The first set of kernel executions runs cl_kernel_fill which fills the "V" lookup table. - - v_start = -self._v_len_chunksize # used if the loop below doesn't run (when --int-rate == 1) - for v_start in xrange(0, v_len - self._v_len_chunksize, self._v_len_chunksize): - done = [] # a list of OpenCL event objects - # Start up a kernel for each device to do one chunk of v_len_chunksize work - for devnum in xrange(len(self._cl_devices)): - done.append(self._cl_kernel_fill( - self._cl_queues[devnum], (self._cl_global_ws[devnum],), - None if self._cl_local_ws[devnum] is None else (self._cl_local_ws[devnum],), - self._cl_V_buffer0s[devnum], self._cl_V_buffer1s[devnum], self._cl_V_buffer2s[devnum], self._cl_V_buffer3s[devnum], - v_start, self._v_len_chunksize, self._cl_hashes_buffers[devnum], 0 == v_start == i)) - self._cl_queues[devnum].flush() # Starts the kernel - pyopencl.wait_for_events(done) - - # Perform the remaining work (usually less then v_len_chunksize) - done = [] # a list of OpenCL event objects - for devnum in xrange(len(self._cl_devices)): - done.append(self._cl_kernel_fill( - self._cl_queues[devnum], (self._cl_global_ws[devnum],), - None if self._cl_local_ws[devnum] is None else (self._cl_local_ws[devnum],), - self._cl_V_buffer0s[devnum], self._cl_V_buffer1s[devnum], self._cl_V_buffer2s[devnum], self._cl_V_buffer3s[devnum], - v_start + self._v_len_chunksize, v_len - self._v_len_chunksize - v_start, self._cl_hashes_buffers[devnum], v_start<0 and i==0)) - self._cl_queues[devnum].flush() # Starts the kernel - pyopencl.wait_for_events(done) - - # The second set of kernel executions runs cl_kernel which uses the "V" lookup table to complete - # the hashes. This kernel runs with half the count of internal iterations as cl_kernel_fill. - - assert self._v_len_chunksize % 2 == 0 - v_start = -self._v_len_chunksize//2 # used if the loop below doesn't run (when --int-rate == 1) - for v_start in xrange(0, v_len//2 - self._v_len_chunksize//2, self._v_len_chunksize//2): - done = [] # a list of OpenCL event objects - # Start up a kernel for each device to do one chunk of v_len_chunksize work - for devnum in xrange(len(self._cl_devices)): - done.append(self._cl_kernel( - self._cl_queues[devnum], (self._cl_global_ws[devnum],), - None if self._cl_local_ws[devnum] is None else (self._cl_local_ws[devnum],), - self._cl_V_buffer0s[devnum], self._cl_V_buffer1s[devnum], self._cl_V_buffer2s[devnum], self._cl_V_buffer3s[devnum], - self._v_len_chunksize//2, self._cl_hashes_buffers[devnum])) - self._cl_queues[devnum].flush() # Starts the kernel - pyopencl.wait_for_events(done) - - # Perform the remaining work (usually less then v_len_chunksize) - done = [] # a list of OpenCL event objects - for devnum in xrange(len(self._cl_devices)): - done.append(self._cl_kernel( - self._cl_queues[devnum], (self._cl_global_ws[devnum],), - None if self._cl_local_ws[devnum] is None else (self._cl_local_ws[devnum],), - self._cl_V_buffer0s[devnum], self._cl_V_buffer1s[devnum], self._cl_V_buffer2s[devnum], self._cl_V_buffer3s[devnum], - v_len//2 - self._v_len_chunksize//2 - v_start, self._cl_hashes_buffers[devnum])) - self._cl_queues[devnum].flush() # Starts the kernel - pyopencl.wait_for_events(done) - - # Copy the resulting fully computed hashes back to RAM in parallel - done = [] # a list of OpenCL event objects - offset = 0 - for devnum, ws in enumerate(self._cl_global_ws): - done.append(pyopencl.enqueue_copy(self._cl_queues[devnum], hashes[offset : offset + ws], - self._cl_hashes_buffers[devnum], is_blocking=False)) - offset += ws - self._cl_queues[devnum].flush() # Starts the copy operation - pyopencl.wait_for_events(done) - - # The first 32 bytes of each computed hash is the derived key. Use each to try to decrypt the private key. - for i, password in enumerate(passwords): - if self._address.verifyEncryptionKey(hashes[i,:32].tostring()): - return password, i + 1 - - return False, i + 1 - - ############### Bitcoin Core ############### @register_wallet_class class WalletBitcoinCore(object): + opencl_algo = -1 + opencl_context_hash_iterations_sha512 = -1 - class __metaclass__(type): - @property - def data_extract_id(cls): return b"bc" + def data_extract_id(): + return "bc" @staticmethod def passwords_per_seconds(seconds): @@ -682,8 +462,14 @@ def passwords_per_seconds(seconds): @staticmethod def is_wallet_file(wallet_file): + # Check if it's a legacy (Berkeley DB) wallet_file.seek(12) - return wallet_file.read(8) == b"\x62\x31\x05\x00\x09\x00\x00\x00" # BDB magic, Btree v9 + if wallet_file.read(8) == b"\x62\x31\x05\x00\x09\x00\x00\x00": # BDB magic, Btree v9 + return True + + wallet_file.seek(0) + # returns "maybe yes" or "definitely no" (Bither and Msigna wallets are also SQLite 3) + return None if wallet_file.read(16) == b"SQLite format 3\0" else False def __init__(self, loading = False): assert loading, 'use load_from_* to create a ' + self.__class__.__name__ @@ -697,71 +483,94 @@ def __setstate__(self, state): # Load a Bitcoin Core BDB wallet file given the filename and extract part of the first encrypted master key @classmethod def load_from_filename(cls, wallet_filename, force_purepython = False): - if not force_purepython: - try: - import bsddb.db - except ImportError: - force_purepython = True + mkey = None - if not force_purepython: - db_env = bsddb.db.DBEnv() - wallet_filename = os.path.abspath(wallet_filename) - try: - db_env.open(os.path.dirname(wallet_filename), bsddb.db.DB_CREATE | bsddb.db.DB_INIT_MPOOL) - db = bsddb.db.DB(db_env) - db.open(wallet_filename, b"main", bsddb.db.DB_BTREE, bsddb.db.DB_RDONLY) - except UnicodeEncodeError: - error_exit("the entire path and filename of Bitcoin Core wallets must be entirely ASCII") - mkey = db.get(b"\x04mkey\x01\x00\x00\x00") - db.close() - db_env.close() + try: + if not force_purepython: + try: + import bsddb3.db + except ImportError: + force_purepython = True - else: - def align_32bits(i): # if not already at one, return the next 32-bit boundry - m = i % 4 - return i if m == 0 else i + 4 - m + if not force_purepython: + db_env = bsddb3.db.DBEnv() + wallet_filename = os.path.abspath(wallet_filename) + try: + db_env.open(os.path.dirname(wallet_filename), bsddb3.db.DB_CREATE | bsddb3.db.DB_INIT_MPOOL) + db = bsddb3.db.DB(db_env) + db.open(wallet_filename, "main", bsddb3.db.DB_BTREE, bsddb3.db.DB_RDONLY) + except UnicodeEncodeError: + error_exit("the entire path and filename of Bitcoin Core wallets must be entirely ASCII") - with open(wallet_filename, "rb") as wallet_file: - wallet_file.seek(12) - assert wallet_file.read(8) == b"\x62\x31\x05\x00\x09\x00\x00\x00", "is a Btree v9 file" - mkey = None - - # Don't actually try walking the btree, just look through every btree leaf page - # for the value/key pair (yes they are in that order...) we're searching for - wallet_file.seek(20) - page_size = struct.unpack(b"126 or ord(c)==0) > 512: # about 3% raise ValueError("Unrecognized pywallet format (does not look like ASCII text)") while cur_block: - found_at = cur_block.find(b'"nDerivation') + found_at = cur_block.find('"nDerivation') if found_at >= 0: break last_block = cur_block cur_block = wallet_file.read(16384) @@ -962,7 +804,7 @@ def load_from_filename(cls, wallet_filename): raise ValueError("Unrecognized pywallet format (can't find mkey)") cur_block = last_block + cur_block + wallet_file.read(4096) - found_at = cur_block.rfind(b"{", 0, found_at + len(last_block)) + found_at = cur_block.rfind("{", 0, found_at + len(last_block)) if found_at < 0: raise ValueError("Unrecognized pywallet format (can't find mkey opening brace)") wallet = json.JSONDecoder().raw_decode(cur_block[found_at:])[0] @@ -1007,17 +849,20 @@ def load_from_filename(cls, wallet_filename): @register_wallet_class class WalletMultiBit(object): + opencl_algo = -1 + _dump_privkeys_file = None - class __metaclass__(type): - @property - def data_extract_id(cls): return b"mb" + def data_extract_id(): + return "mb" # MultiBit private key backup file (not the wallet file) @staticmethod def is_wallet_file(wallet_file): wallet_file.seek(0) - try: data = base64.b64decode(wallet_file.read(20).lstrip()[:12]) - except TypeError: return False + try: + base64EncodedData = wallet_file.read(20).lstrip()[:12] + data = base64.b64decode(base64EncodedData) + except binascii.Error: return False return data.startswith(b"Salted__") def __init__(self, loading = False): @@ -1030,6 +875,34 @@ def __setstate__(self, state): load_aes256_library(warnings=False) self.__dict__ = state + # This just dumps the wallet private keys for Android Wallets + def dump_privkeys(self, wallet_data): + with open(self._dump_privkeys_file, 'a') as logfile: + from . import bitcoinj_pb2 + global pylibscrypt + import lib.pylibscrypt as pylibscrypt + pad_len = wallet_data[-1] + if isinstance(pad_len, str): + pad_len = ord(pad_len) + + # Attempt to dump the menemonic from the wallet (standard BitcoinJ file) + pbdata = wallet_data[:-pad_len] + pb_wallet = bitcoinj_pb2.Wallet() + pb_wallet.ParseFromString(pbdata) + mnemonic = WalletBitcoinj.extract_mnemonic(pb_wallet) + logfile.write("Android Wallet Mnemonic: '" + mnemonic.decode() + "' derivation path: m/0'") + + # This just dumps the wallet private keys for Multibit Classic, Multidoge Wallets + def dump_privkeys_keybackup(self, key1, key2, iv): + with open(self._dump_privkeys_file, 'a') as logfile: + decrypted_wallet = aes256_cbc_decrypt(key1 + key2, iv, self._encrypted_wallet).decode().splitlines() + for line in decrypted_wallet: + try: + key, date = line.split(" ") + logfile.write(key + "\n") + except: + pass + def passwords_per_seconds(self, seconds): return max(int(round(self._passwords_per_second * seconds)), 1) @@ -1039,13 +912,15 @@ def load_from_filename(cls, privkey_filename): with open(privkey_filename) as privkey_file: # Multibit privkey files contain base64 text split into multiple lines; # we need the first 48 bytes after decoding, which translates to 64 before. - data = b"".join(privkey_file.read(70).split()) # join multiple lines into one + data = "".join(privkey_file.read().split()) # join multiple lines into one + if len(data) < 64: raise EOFError("Expected at least 64 bytes of text in the MultiBit private key file") - data = base64.b64decode(data[:64]) + data = base64.b64decode(data) assert data.startswith(b"Salted__"), "WalletBitcoinCore.load_from_filename: file starts with base64 'Salted__'" if len(data) < 48: raise EOFError("Expected at least 48 bytes of decoded data in the MultiBit private key file") self = cls(loading=True) self._encrypted_block = data[16:48] # the first two 16-byte AES blocks + self._encrypted_wallet = data[16:] self._salt = data[8:16] return self @@ -1053,7 +928,7 @@ def load_from_filename(cls, privkey_filename): @classmethod def load_from_data_extract(cls, privkey_data): assert len(privkey_data) == 24 - print(prog + ": WARNING: read the Usage for MultiBit Classic section of Extract_Scripts.md before proceeding", file=sys.stderr) + print("WARNING: read the Usage for MultiBit Classic section of Extract_Scripts.md before proceeding", file=sys.stderr) self = cls(loading=True) self._encrypted_block = privkey_data[8:] # a single 16-byte AES block self._salt = privkey_data[:8] @@ -1065,7 +940,14 @@ def difficulty_info(self): # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 assert b"1" < b"9" < b"A" < b"Z" < b"a" < b"z" # the b58 check below assumes ASCII ordering in the interest of speed - def return_verified_password_or_false(self, orig_passwords): + def return_verified_password_or_false(self, orig_passwords): # Multibit + # Add OpenCL dispatch like other wallet types + if not isinstance(self.opencl_algo, int): + return self._return_verified_password_or_false_opencl(orig_passwords) + else: + return self._return_verified_password_or_false_cpu(orig_passwords) + + def _return_verified_password_or_false_cpu(self, orig_passwords): # Multibit # Copy a few globals into local for a small speed boost l_md5 = hashlib.md5 l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1073,10 +955,7 @@ def return_verified_password_or_false(self, orig_passwords): salt = self._salt # Convert Unicode strings (lazily) to UTF-16 bytestrings, truncating each code unit to 8 bits - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_16_le", "ignore")[::2], orig_passwords) - else: - passwords = orig_passwords + passwords = map(lambda p: p.encode("utf_16_le", "ignore")[::2], orig_passwords) for count, password in enumerate(passwords, 1): salted = password + salt @@ -1086,13 +965,15 @@ def return_verified_password_or_false(self, orig_passwords): b58_privkey = l_aes256_cbc_decrypt(key1 + key2, iv, encrypted_block[:16]) # (all this may be fragile, e.g. what if comments or whitespace precede what's expected in future versions?) - if b58_privkey[0] in b"LK5Q\x0a#": + if type(b58_privkey) == str: + b58_privkey = b58_privkey.encode() + if chr(b58_privkey[0]) in "LK5Q\x0a#": # # Does it look like a base58 private key (MultiBit, MultiDoge, or oldest-format Android key backup)? - if b58_privkey[0] in b"LK5Q": # private keys always start with L, K, or 5, or for MultiDoge Q + if b58_privkey[0] in "LK5Q".encode(): # private keys always start with L, K, or 5, or for MultiDoge Q for c in b58_privkey[1:]: # If it's outside of the base58 set [1-9A-HJ-NP-Za-km-z], break - if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl": + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": break # If the loop above doesn't break, it's base58-looking so far else: @@ -1100,44 +981,143 @@ def return_verified_password_or_false(self, orig_passwords): if len(encrypted_block) >= 32: b58_privkey = l_aes256_cbc_decrypt(key1 + key2, encrypted_block[:16], encrypted_block[16:32]) for c in b58_privkey: - if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl": + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": break # not base58 # If the loop above doesn't break, it's base58; we've found it else: + if self._dump_privkeys_file: + self.dump_privkeys_keybackup(key1, key2, iv) return orig_passwords[count-1], count else: # (when no second block is available, there's a 1 in 300 billion false positive rate here) + if self._dump_privkeys_file: + self.dump_privkeys_keybackup(key1, key2, iv) return orig_passwords[count - 1], count # # Does it look like a bitcoinj protobuf (newest Bitcoin for Android backup) - elif b58_privkey[2:6] == b"org." and b58_privkey[0] == b"\x0a" and ord(b58_privkey[1]) < 128: + elif b58_privkey[2:6] == b"org." and b58_privkey[0] == 10 and b58_privkey[1] < 128: for c in b58_privkey[6:14]: # If it doesn't look like a lower alpha domain name of len >= 8 (e.g. 'bitcoin.'), break - if c > b"z" or (c < b"a" and c != b"."): + if c > ord("z") or (c < ord("a") and c != ord(".")): break # If the loop above doesn't break, it looks like a domain name; we've found it else: + print("Notice: Found Bitcoin for Android Wallet Password") + if self._dump_privkeys_file: + #try: + if True: + wallet_data = l_aes256_cbc_decrypt(key1 + key2, iv, self._encrypted_wallet) + self.dump_privkeys(wallet_data) + #except: + # print("Unable to decode wallet mnemonic (common for very old wallets)") + return orig_passwords[count - 1], count # # Does it look like a KnC for Android key backup? elif b58_privkey == b"# KEEP YOUR PRIV": - return orig_passwords[count-1], count + if isinstance(orig_passwords[count-1],str): + return orig_passwords[count-1], count + if isinstance(orig_passwords[count - 1], bytes): + return orig_passwords[count-1].decode(), count return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): + # Copy a few globals into local for a small speed boost + l_aes256_cbc_decrypt = aes256_cbc_decrypt + encrypted_block = self._encrypted_block + + # Convert Unicode strings to UTF-16 bytestrings, truncating each code unit to 8 bits + passwords = map(lambda p: p.encode("utf_16_le", "ignore")[::2], arg_passwords) + + # Use OpenCL for MD5 computation and AES decryption of first block + # Returns: match flag (4B) + decrypted block (16B) + key1 (16B) + key2 (16B) + iv (16B) = 68 bytes + clResult = self.opencl_algo.cl_multibit_md5(self.opencl_context_multibit_md5, + passwords, self._salt, + self._encrypted_block[:16]) + # Process results on CPU (further validation of decrypted data) + for count, derived in enumerate(clResult, 1): + match_flag = derived[0] + if not match_flag: + continue # skip non-matching results immediately + # Extract results from OpenCL output + b58_privkey = derived[4:20] + key1 = derived[20:36] + key2 = derived[36:52] + iv = derived[52:68] + + # (all this may be fragile, e.g. what if comments or whitespace precede what's expected in future versions?) + if isinstance(b58_privkey, str): + b58_privkey = b58_privkey.encode() + + if chr(b58_privkey[0]) in "LK5Q\x0a#": + # + # Does it look like a base58 private key (MultiBit, MultiDoge, or oldest-format Android key backup)? + if b58_privkey[0] in "LK5Q".encode(): # private keys always start with L, K, or 5, or for MultiDoge Q + for c in b58_privkey[1:]: + # If it's outside of the base58 set [1-9A-HJ-NP-Za-km-z], break + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": + break + # If the loop above doesn't break, it's base58-looking so far + else: + # If another AES block is available, decrypt and check it as well to avoid false positives + if len(encrypted_block) >= 32: + b58_privkey = l_aes256_cbc_decrypt(key1 + key2, encrypted_block[:16], encrypted_block[16:32]) + for c in b58_privkey: + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": + break # not base58 + # If the loop above doesn't break, it's base58; we've found it + else: + if self._dump_privkeys_file: + self.dump_privkeys_keybackup(key1, key2, iv) + return arg_passwords[count - 1], count + else: + # (when no second block is available, there's a 1 in 300 billion false positive rate here) + if self._dump_privkeys_file: + self.dump_privkeys_keybackup(key1, key2, iv) + return arg_passwords[count - 1], count + # + # Does it look like a bitcoinj protobuf (newest Bitcoin for Android backup) + elif b58_privkey[2:6] == b"org." and b58_privkey[0] == 10 and b58_privkey[1] < 128: + for c in b58_privkey[6:14]: + # If it doesn't look like a lower alpha domain name of len >= 8 (e.g. 'bitcoin.'), break + if c > ord("z") or (c < ord("a") and c != ord(".")): + break + # If the loop above doesn't break, it looks like a domain name; we've found it + else: + print("Notice: Found Bitcoin for Android Wallet Password") + if self._dump_privkeys_file: + #try: + if True: + wallet_data = l_aes256_cbc_decrypt(key1 + key2, iv, self._encrypted_wallet) + self.dump_privkeys(wallet_data) + #except: + # print("Unable to decode wallet mnemonic (common for very old wallets)") + + return arg_passwords[count - 1], count + # + # Does it look like a KnC for Android key backup? + elif b58_privkey == b"# KEEP YOUR PRIV": + if isinstance(arg_passwords[count - 1], str): + return arg_passwords[count - 1], count + if isinstance(arg_passwords[count - 1], bytes): + return arg_passwords[count - 1].decode(), count + return False, count ############### bitcoinj ############### -# A namedtuple with the same attributes as the protobuf message object from wallet_pb2 +# A namedtuple with the same attributes as the protobuf message object from bitcoinj_pb2 # (it's a global so that it's pickleable) EncryptionParams = collections.namedtuple("EncryptionParams", "salt n r p") @register_wallet_class class WalletBitcoinj(object): + opencl_algo = -1 + _dump_privkeys_file = None - class __metaclass__(type): - @property - def data_extract_id(cls): return b"bj" + def data_extract_id(): + return "bj" def passwords_per_seconds(self, seconds): passwords_per_second = self._passwords_per_second @@ -1159,13 +1139,50 @@ def is_wallet_file(wallet_file): return True return False + # From https://github.com/gurnec/decrypt_bitcoinj_seed + @staticmethod + def extract_mnemonic(pb_wallet, password = None): + from . import bitcoinj_pb2 + """extract and if necessary decrypt (w/scrypt) a BIP39 mnemonic from a bitcoinj wallet protobuf + + :param pb_wallet: a Wallet protobuf message + :type pb_wallet: wallet_pb2.Wallet + :param get_password_fn: a callback returning a password that's called iff one is required + :type get_password_fn: function + :return: the first BIP39 mnemonic found in the wallet or None if no password was entered when required + :rtype: str + """ + for key in pb_wallet.key: + if key.type == bitcoinj_pb2.Key.DETERMINISTIC_MNEMONIC: + + if key.HasField('secret_bytes'): # if not encrypted + return key.secret_bytes + + elif key.HasField('encrypted_data'): # if encrypted (w/scrypt) + # Derive the encryption key + aes_key = pylibscrypt.scrypt( + password, + pb_wallet.encryption_parameters.salt, + pb_wallet.encryption_parameters.n, + pb_wallet.encryption_parameters.r, + pb_wallet.encryption_parameters.p, + 32) + + # Decrypt the mnemonic + ciphertext = key.encrypted_data.encrypted_private_key + iv = key.encrypted_data.initialisation_vector + return aes256_cbc_decrypt(aes_key, iv, ciphertext).decode().replace("\\t", "") + + else: # if the loop exists normally, no mnemonic was found + raise ValueError('no BIP39 mnemonic found') + def __init__(self, loading = False): assert loading, 'use load_from_* to create a ' + self.__class__.__name__ global pylibscrypt - import pylibscrypt + import lib.pylibscrypt as pylibscrypt # This is the base estimate for the scrypt N,r,p defaults of 16384,8,1 if not pylibscrypt._done: - print(prog+": warning: can't find an scrypt library, performance will be severely degraded", file=sys.stderr) + print("Warning: can't find an scrypt library, performance will be severely degraded", file=sys.stderr) self._passwords_per_second = 0.03 else: self._passwords_per_second = 14 @@ -1174,7 +1191,7 @@ def __init__(self, loading = False): def __setstate__(self, state): # (re-)load the required libraries after being unpickled global pylibscrypt - import pylibscrypt + import lib.pylibscrypt as pylibscrypt load_aes256_library(warnings=False) self.__dict__ = state @@ -1187,18 +1204,30 @@ def load_from_filename(cls, wallet_filename): @classmethod def _load_from_filedata(cls, filedata): - from . import wallet_pb2 - pb_wallet = wallet_pb2.Wallet() + try: + from . import bitcoinj_pb2 + except ModuleNotFoundError: + print("Warning: Cannot load protobuf module, unable to check if this is a Coinomi wallet" + "... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + pb_wallet = bitcoinj_pb2.Wallet() pb_wallet.ParseFromString(filedata) - if pb_wallet.encryption_type == wallet_pb2.Wallet.UNENCRYPTED: + + if pb_wallet.encryption_type == bitcoinj_pb2.Wallet.UNENCRYPTED: + print("\nWallet Not Encrypted, Contains the following Private Keys") + for key in pb_wallet.key: + from lib.cashaddress import base58 + privkey_wif = base58.b58encode_check(bytes([0x80]) + key.secret_bytes + bytes([0x1])) + print(privkey_wif) + print() raise ValueError("bitcoinj wallet is not encrypted") - if pb_wallet.encryption_type != wallet_pb2.Wallet.ENCRYPTED_SCRYPT_AES: - raise NotImplementedError("Unsupported bitcoinj encryption type "+unicode(pb_wallet.encryption_type)) + if pb_wallet.encryption_type != bitcoinj_pb2.Wallet.ENCRYPTED_SCRYPT_AES: + raise NotImplementedError("Unsupported bitcoinj encryption type "+str(pb_wallet.encryption_type)) if not pb_wallet.HasField("encryption_parameters"): raise ValueError("bitcoinj wallet is missing its scrypt encryption parameters") for key in pb_wallet.key: - if key.type in (wallet_pb2.Key.ENCRYPTED_SCRYPT_AES, wallet_pb2.Key.DETERMINISTIC_KEY) and key.HasField("encrypted_data"): + if key.type in (bitcoinj_pb2.Key.ENCRYPTED_SCRYPT_AES, bitcoinj_pb2.Key.DETERMINISTIC_KEY) and key.HasField("encrypted_data"): encrypted_len = len(key.encrypted_data.encrypted_private_key) if encrypted_len == 48: # only need the final 2 encrypted blocks (half of it padding) plus the scrypt parameters @@ -1208,8 +1237,9 @@ def _load_from_filedata(cls, filedata): self._scrypt_n = pb_wallet.encryption_parameters.n self._scrypt_r = pb_wallet.encryption_parameters.r self._scrypt_p = pb_wallet.encryption_parameters.p + self.pb_wallet_filedata = filedata return self - print(prog+": warning: ignoring encrypted key of unexpected length ("+unicode(encrypted_len)+")", file=sys.stderr) + print("Warning: ignoring encrypted key of unexpected length ("+str(encrypted_len)+")", file=sys.stderr) raise ValueError("No encrypted keys found in bitcoinj wallet") @@ -1227,9 +1257,22 @@ def load_from_data_extract(cls, privkey_data): def difficulty_info(self): return "scrypt N, r, p = {}, {}, {}".format(self._scrypt_n, self._scrypt_r, self._scrypt_p) + def dump_privkeys(self, derived_key): + from . import bitcoinj_pb2 + pb_wallet = bitcoinj_pb2.Wallet() + pb_wallet.ParseFromString(self.pb_wallet_filedata) + + from lib.cashaddress import base58 + with open(self._dump_privkeys_file, 'a') as logfile: + for key in pb_wallet.key: + privkey = aes256_cbc_decrypt(derived_key, key.encrypted_data.initialisation_vector, + key.encrypted_data.encrypted_private_key)[:32] + privkey_wif = base58.b58encode_check(bytes([0x80]) + privkey + bytes([0x1])) + logfile.write(privkey_wif + "\n") + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): # Bitcoinj # Copy a few globals into local for a small speed boost l_scrypt = pylibscrypt.scrypt l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1239,37 +1282,185 @@ def return_verified_password_or_false(self, passwords): scrypt_r = self._scrypt_r scrypt_p = self._scrypt_p + # Convert strings (lazily) to UTF-16BE bytestrings - passwords = itertools.imap(lambda p: p.encode("utf_16_be", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_16_be", "ignore"), passwords) for count, password in enumerate(passwords, 1): derived_key = l_scrypt(password, scrypt_salt, scrypt_n, scrypt_r, scrypt_p, 32) part_key = l_aes256_cbc_decrypt(derived_key, part_encrypted_key[:16], part_encrypted_key[16:]) - # # If the last block (bytes 16-31) of part_encrypted_key is all padding, we've found it if part_key == b"\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10": password = password.decode("utf_16_be", "replace") - return password.encode("ascii", "replace") if tstr == str else password, count + if self._dump_privkeys_file: + self.dump_privkeys(derived_key) + + return password, count return False, count -############### MultiBit HD ############### -@register_wallet_class -class WalletMultiBitHD(WalletBitcoinj): +############### Coinomi ############### + +# A namedtuple with the same attributes as the protobuf message object from coinomi_pb2 +# (it's a global so that it's pickleable) +EncryptionParams = collections.namedtuple("EncryptionParams", "salt n r p") - class __metaclass__(WalletBitcoinj.__metaclass__): - @property - def data_extract_id(cls): return b"m5" - # id "m2", which *only* supported MultiBit HD prior to v0.5.0 ("m5" supports - # both before and after), is no longer supported as of btcrecover version 0.15.7 +@register_wallet_class +class WalletCoinomi(WalletBitcoinj): + opencl_algo = -1 + _using_extract = False + _dump_privkeys_file = None + + def data_extract_id(): + return "cn" + + # This just dumps the wallet private keys + def dump_privkeys(self, derived_key): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Private Keys (BIP39 seed and BIP32 Root Key) are below...\n") + mnemonic = aes256_cbc_decrypt(derived_key, self._mnemonic_iv, self._mnemonic) + mnemonic = mnemonic.decode()[:-1] + if mnemonic[-2:-1] != b'\x0c': + mnemonic = mnemonic.replace('\x0c', "") + " (BIP39 Passphrase In Use, if you don't have it use BIP32 root key to recover wallet)" + + logfile.write("BIP39 Mnemonic: " + mnemonic + "\n") + master_key = aes256_cbc_decrypt(derived_key, self._masterkey_encrypted_iv, self._masterkey_encrypted) + from lib.cashaddress import convert, base58 + xprv = base58.b58encode_check( + b'\x04\x88\xad\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00' + self._masterkey_chaincode + b'\x00' + master_key[ + :-16]) + logfile.write("\nBIP32 Root Key: " + xprv) @staticmethod - def is_wallet_file(wallet_file): return None # there's no easy way to check this + def is_wallet_file(wallet_file): + wallet_file.seek(0) + if wallet_file.read(1) == b'\x08': # protobuf field number 1 of type length-delimited + wallet_file.read(1) + if wallet_file.read(1) == b'\x12': + try: + from . import coinomi_pb2 + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load protobuf module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") - # Load a MultiBit HD wallet file (the part of it we need) - @classmethod + try: + wallet_file.seek(0) + pb_wallet = coinomi_pb2.Wallet() + pb_wallet.ParseFromString(wallet_file.read()) + pockets = pb_wallet.pockets # Pockets is a fairly unique coinomi key... #This will certainly fail on non-coinomi protobuf wallets in Python 3.9+ + return True + except: + pass + + return False + + @classmethod + def _load_from_filedata(cls, filedata): + try: + from . import coinomi_pb2 + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load protobuf module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + pb_wallet = coinomi_pb2.Wallet() + pb_wallet.ParseFromString(filedata) + if pb_wallet.encryption_type == coinomi_pb2.Wallet.UNENCRYPTED: + raise ValueError("Coinomi wallet is not encrypted") + if pb_wallet.encryption_type != coinomi_pb2.Wallet.ENCRYPTED_SCRYPT_AES: + raise NotImplementedError("Unsupported Coinomi wallet encryption type "+str(pb_wallet.encryption_type)) + if not pb_wallet.HasField("encryption_parameters"): + raise ValueError("Coinomi wallet is missing its scrypt encryption parameters") + + # only need the final 2 encrypted blocks (half of it padding) plus the scrypt parameters + self = cls(loading=True) + self._encrypted_masterkey_part = pb_wallet.master_key.encrypted_data.encrypted_private_key[-32:] + self._scrypt_salt = pb_wallet.encryption_parameters.salt + self._scrypt_n = pb_wallet.encryption_parameters.n + self._scrypt_r = pb_wallet.encryption_parameters.r + self._scrypt_p = pb_wallet.encryption_parameters.p + self._mnemonic = pb_wallet.seed.encrypted_data.encrypted_private_key + self._mnemonic_iv = pb_wallet.seed.encrypted_data.initialisation_vector + self._masterkey_encrypted = pb_wallet.master_key.encrypted_data.encrypted_private_key + self._masterkey_encrypted_iv = pb_wallet.master_key.encrypted_data.initialisation_vector + self._masterkey_chaincode = pb_wallet.master_key.deterministic_key.chain_code + self._masterkey_pubkey = pb_wallet.master_key.public_key + return self + + # Import a bitcoinj private key that was extracted by extract-bitcoinj-privkey.py + @classmethod + def load_from_data_extract(cls, privkey_data): + self = cls(loading=True) + # The final 2 encrypted blocks + self._encrypted_masterkey_part = privkey_data[:32] + # The scrypt parameters + self._scrypt_salt = privkey_data[32:40] + (self._scrypt_n, self._scrypt_r, self._scrypt_p) = struct.unpack(b"< I H H", privkey_data[40:]) + self._using_extract = True + return self + + def difficulty_info(self): + return "scrypt N, r, p = {}, {}, {}".format(self._scrypt_n, self._scrypt_r, self._scrypt_p) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, passwords): # Bitcoinj + # Copy a few globals into local for a small speed boost + l_scrypt = pylibscrypt.scrypt + l_aes256_cbc_decrypt = aes256_cbc_decrypt + _encrypted_masterkey_part = self._encrypted_masterkey_part + scrypt_salt = self._scrypt_salt + scrypt_n = self._scrypt_n + scrypt_r = self._scrypt_r + scrypt_p = self._scrypt_p + + # Convert strings (lazily) to UTF-16BE bytestrings + passwords = map(lambda p: p.encode("utf_16_be", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + derived_key = l_scrypt(password, scrypt_salt, scrypt_n, scrypt_r, scrypt_p, 32) + part_key = l_aes256_cbc_decrypt(derived_key, _encrypted_masterkey_part[:16], _encrypted_masterkey_part[16:]) + + # If the last block (bytes 16-31) of part_encrypted_key is all padding, we've found it + if part_key == b"\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10": + if not self._using_extract and self._dump_privkeys_file: + self.dump_privkeys(derived_key) + + password = password.decode("utf_16_be", "replace") + return password, count + + return False, count + + +############### MultiBit HD ############### + +@register_wallet_class +class WalletMultiBitHD(WalletBitcoinj): + _dump_privkeys_file = None + + def data_extract_id(): + return "m5" + # id "m2", which *only* supported MultiBit HD prior to v0.5.0 ("m5" supports + # both before and after), is no longer supported as of btcrecover version 0.15.7 + + # This just dumps the wallet private keys + def dump_privkeys(self, derived_key, password): + with open(self._dump_privkeys_file, 'a') as logfile: + decrypted_data = aes256_cbc_decrypt(derived_key, self._iv, self._encrypted_data) + padding_len = decrypted_data[-1] + + from . import bitcoinj_pb2 + pb_wallet = bitcoinj_pb2.Wallet() + pb_wallet.ParseFromString(decrypted_data[:-padding_len]) + mnemonic = WalletBitcoinj.extract_mnemonic(pb_wallet, password) + logfile.write("BIP39 Seed: " + mnemonic) + + @staticmethod + def is_wallet_file(wallet_file): return None # there's no easy way to check this + + # Load a MultiBit HD wallet file (the part of it we need) + @classmethod def load_from_filename(cls, wallet_filename): # MultiBit HD wallet files look like completely random bytes, so we # require that its name remain unchanged in order to "detect" it @@ -1277,7 +1468,7 @@ def load_from_filename(cls, wallet_filename): raise ValueError("MultiBit HD wallet files must be named mbhd.wallet.aes") with open(wallet_filename, "rb") as wallet_file: - encrypted_data = wallet_file.read(16384) # typical size is >= 23k + encrypted_data = wallet_file.read() if len(encrypted_data) < 32: raise ValueError("MultiBit HD wallet files must be at least 32 bytes long") @@ -1291,6 +1482,8 @@ def load_from_filename(cls, wallet_filename): self._iv = encrypted_data[:16] # the AES initialization vector (v0.5.0+) self._encrypted_block_iv = encrypted_data[16:32] # the first 16-byte encrypted block (v0.5.0+) self._encrypted_block_noiv = encrypted_data[:16] # the first 16-byte encrypted block w/hardcoded IV (< v0.5.0) + self._encrypted_data = encrypted_data[16:] # Encrypted Data + self._encrypted_data_noiv = encrypted_data # Encrypted Data w/hardcoded IV (< v0.5.0) return self # Import a MultiBit HD encrypted block that was extracted by extract-multibit-hd-data.py @@ -1308,7 +1501,7 @@ def difficulty_info(self): # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): # MultibitHD # Copy a few globals into local for a small speed boost l_scrypt = pylibscrypt.scrypt l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1317,7 +1510,7 @@ def return_verified_password_or_false(self, passwords): encrypted_block_noiv = self._encrypted_block_noiv # Convert strings (lazily) to UTF-16BE bytestrings - passwords = itertools.imap(lambda p: p.encode("utf_16_be", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_16_be", "ignore"), passwords) for count, password in enumerate(passwords, 1): derived_key = l_scrypt(password, b'\x35\x51\x03\x80\x75\xa3\xb0\xc5', olen=32) # w/a hardcoded salt @@ -1330,9 +1523,12 @@ def return_verified_password_or_false(self, passwords): # Does it look like a bitcoinj protobuf file? # (there's a 1 in 2 trillion chance this hits but the password is wrong) for block in (block_iv, block_noiv): - if block[2:6] == b"org." and block[0] == b"\x0a" and ord(block[1]) < 128: + if block[2:6] == b"org." and block[0] == 10 and block[1] < 128: + if self._dump_privkeys_file: + self.dump_privkeys(derived_key, password) + password = password.decode("utf_16_be", "replace") - return password.encode("ascii", "replace") if tstr == str else password, count + return password, count return False, count @@ -1367,7 +1563,7 @@ def load_from_filename(cls, wallet_filename, password = None, force_purepython = if not password: password = prompt_unicode_password( - b"Please enter the password for the Bitcoin Wallet for Android/BlackBerry backup: ", + "Please enter the password for the Bitcoin Wallet for Android/BlackBerry backup: ", "encrypted Bitcoin Wallet for Android/BlackBerry backups must be decrypted before searching for the PIN") # Convert Unicode string to a UTF-16 bytestring, truncating each code unit to 8 bits password = password.encode("utf_16_le", "ignore")[::2] @@ -1379,12 +1575,12 @@ def load_from_filename(cls, wallet_filename, password = None, force_purepython = key2 = hashlib.md5(key1 + salted).digest() iv = hashlib.md5(key2 + salted).digest() data = aes256_cbc_decrypt(key1 + key2, iv, data) - from cStringIO import StringIO - if not WalletBitcoinj.is_wallet_file(StringIO(data[:100])): + #from cStringIO import StringIO + if not WalletBitcoinj.is_wallet_file(io.BytesIO(data[:100])): error_exit("can't decrypt wallet (wrong password?)") # Validate and remove the PKCS7 padding - padding_len = ord(data[-1]) - if not (1 <= padding_len <= 16 and data.endswith(chr(padding_len) * padding_len)): + padding_len = data[-1] + if not (1 <= padding_len <= 16 and data.endswith((chr(padding_len) * padding_len).encode())): error_exit("can't decrypt wallet, invalid padding (wrong password?)") return cls._load_from_filedata(data[:-padding_len]) # WalletBitcoinj._load_from_filedata() parses the bitcoinj wallet @@ -1394,10 +1590,10 @@ def load_from_filename(cls, wallet_filename, password = None, force_purepython = @register_wallet_class class WalletMsigna(object): + opencl_algo = -1 - class __metaclass__(type): - @property - def data_extract_id(cls): return b"ms" + def data_extract_id(): + return "ms" @staticmethod def is_wallet_file(wallet_file): @@ -1425,40 +1621,40 @@ def load_from_filename(cls, wallet_filename): import sqlite3 wallet_conn = sqlite3.connect(wallet_filename) wallet_conn.row_factory = sqlite3.Row - select = b"SELECT * FROM Keychain" + select = "SELECT * FROM Keychain" try: if "args" in globals() and args.msigna_keychain: # args is not defined during unit tests - wallet_cur = wallet_conn.execute(select + b" WHERE name LIKE '%' || ? || '%'", (args.msigna_keychain,)) + wallet_cur = wallet_conn.execute(select + " WHERE name LIKE '%' || ? || '%'", (args.msigna_keychain,)) else: wallet_cur = wallet_conn.execute(select) except sqlite3.OperationalError as e: - if str(e).startswith(b"no such table"): - raise ValueError("Not an mSIGNA wallet: " + unicode(e)) # it might be a Bither wallet + if str(e).startswith("no such table"): + raise ValueError("Not an mSIGNA wallet: " + str(e)) # it might be a Bither or Bitcoin Core wallet else: - raise # unexpected error + raise# unexpected error keychain = wallet_cur.fetchone() if not keychain: error_exit("no such keychain found in the mSIGNA vault") keychain_extra = wallet_cur.fetchone() if keychain_extra: print("Multiple matching keychains found in the mSIGNA vault:", file=sys.stderr) - print(" ", keychain[b"name"]) - print(" ", keychain_extra[b"name"]) + print(" ", keychain["name"]) + print(" ", keychain_extra["name"]) for keychain_extra in wallet_cur: - print(" ", keychain_extra[b"name"]) + print(" ", keychain_extra["name"]) error_exit("use --msigna-keychain NAME to specify a specific keychain") wallet_conn.close() - privkey_ciphertext = str(keychain[b"privkey_ciphertext"]) + privkey_ciphertext = keychain["privkey_ciphertext"] if len(privkey_ciphertext) == 32: - error_exit("mSIGNA keychain '"+keychain[b"name"]+"' is not encrypted") + error_exit("mSIGNA keychain '"+keychain["name"]+"' is not encrypted") if len(privkey_ciphertext) != 48: - error_exit("mSIGNA keychain '"+keychain[b"name"]+"' has an unexpected privkey length") + error_exit("mSIGNA keychain '"+keychain["name"]+"' has an unexpected privkey length") # only need the final 2 encrypted blocks (half of which is padding) plus the salt self = cls(loading=True) self._part_encrypted_privkey = privkey_ciphertext[-32:] - self._salt = struct.pack(b"< q", keychain[b"privkey_salt"]) + self._salt = struct.pack("< q", keychain["privkey_salt"]) return self # Import an encrypted privkey and salt that was extracted by extract-msigna-privkey.py @@ -1474,7 +1670,7 @@ def difficulty_info(self): # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): #mSIGNA # Copy some vars into local for a small speed boost l_sha1 = hashlib.sha1 l_sha256 = hashlib.sha256 @@ -1482,8 +1678,7 @@ def return_verified_password_or_false(self, passwords): salt = self._salt # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) for count, password in enumerate(passwords, 1): password_hashed = l_sha256(l_sha256(password).digest()).digest() # mSIGNA does this first @@ -1492,17 +1687,17 @@ def return_verified_password_or_false(self, passwords): # 5. The EVP_BytesToKey outer loop is unrolled with two iterations below which produces # 320 bits (2x SHA1's output) which is > 32 bytes (what's needed for the AES-256 key) derived_part1 = password_hashed + salt - for i in xrange(5): # 5 is mSIGNA's hard coded iteration count + for i in range(5): # 5 is mSIGNA's hard coded iteration count derived_part1 = l_sha1(derived_part1).digest() derived_part2 = derived_part1 + password_hashed + salt - for i in xrange(5): + for i in range(5): derived_part2 = l_sha1(derived_part2).digest() # part_privkey = aes256_cbc_decrypt(derived_part1 + derived_part2[:12], part_encrypted_privkey[:16], part_encrypted_privkey[16:]) # # If the last block (bytes 16-31) of part_encrypted_privkey is all padding, we've found it if part_privkey == b"\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10": - return password if tstr == str else password.decode("utf_8", "replace"), count + return password.decode("utf_8", "replace"), count return False, count @@ -1511,6 +1706,7 @@ def return_verified_password_or_false(self, passwords): # Comman base class for all Electrum wallets class WalletElectrum(object): + opencl_algo = -1 def __init__(self, loading = False): assert loading, 'use load_from_* to create a ' + self.__class__.__name__ @@ -1540,9 +1736,8 @@ def difficulty_info(self): @register_wallet_class class WalletElectrum1(WalletElectrum): - class __metaclass__(type): - @property - def data_extract_id(cls): return b"el" + def data_extract_id(): + return "el" @staticmethod def is_wallet_file(wallet_file): @@ -1565,7 +1760,7 @@ def load_from_filename(cls, wallet_filename): def _load_from_dict(cls, wallet): seed_version = wallet.get("seed_version") if seed_version is None: raise ValueError("Unrecognized wallet format (Electrum1 seed_version not found)") - if seed_version != 4: raise NotImplementedError("Unsupported Electrum1 seed version " + unicode(seed_version)) + if seed_version != 4: raise NotImplementedError("Unsupported Electrum1 seed version " + str(seed_version)) if not wallet.get("use_encryption"): raise RuntimeError("Electrum1 wallet is not encrypted") seed_data = base64.b64decode(wallet["seed"]) if len(seed_data) != 64: raise RuntimeError("Electrum1 encrypted seed plus iv is not 64 bytes long") @@ -1577,7 +1772,7 @@ def _load_from_dict(cls, wallet): # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 assert b"0" < b"9" < b"a" < b"f" # the hex check below assumes ASCII ordering in the interest of speed - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): #Electrum1 # Copy some vars into local for a small speed boost l_sha256 = hashlib.sha256 l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1585,32 +1780,34 @@ def return_verified_password_or_false(self, passwords): iv = self._iv # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) for count, password in enumerate(passwords, 1): key = l_sha256( l_sha256( password ).digest() ).digest() seed = l_aes256_cbc_decrypt(key, iv, part_encrypted_seed) # If the first 16 bytes of the encrypted seed is all lower-case hex, we've found it for c in seed: - if c > b"f" or c < b"0" or b"9" < c < b"a": break # not hex + if type(c) == str: + c = ord(c.encode()) + + if c > ord("f") or c < ord("0") or ord("9") < c < ord("a"): break # not hex else: # if the loop above doesn't break, it's all hex - return password if tstr == str else password.decode("utf_8", "replace"), count + return password.decode("utf_8", "replace"), count return False, count @register_wallet_class class WalletElectrum2(WalletElectrum): - class __metaclass__(type): - @property - def data_extract_id(cls): return b"e2" + def data_extract_id(): + return "e2" @staticmethod def is_wallet_file(wallet_file): wallet_file.seek(0) # returns "maybe yes" or "definitely no" - return None if wallet_file.read(1) == b"{" else False + electrumWalletFileStart = wallet_file.read(1) + return None if electrumWalletFileStart == b"{" else False # Load an Electrum wallet file (the part of it we need) @classmethod @@ -1627,8 +1824,15 @@ def load_from_filename(cls, wallet_filename): if not wallet.get("use_encryption"): raise ValueError("Electrum2 wallet is not encrypted") seed_version = wallet.get("seed_version", "(not found)") - if wallet.get("seed_version") not in (11, 12, 13) and wallet_type != "imported": # all 2.x versions as of Oct 2016 - raise NotImplementedError("Unsupported Electrum2 seed version " + unicode(seed_version)) + try: + if wallet.get("seed_version") < 11: # all versions above 2.x + raise NotImplementedError("Unsupported Electrum2 seed version " + str(seed_version)) + + except TypeError: # Seed version is none... Likely imported loose key wallet... + if wallet_type != "imported": + raise NotImplementedError("Unsupported Electrum2 seed version " + str(seed_version)) + else: + pass xprv = None while True: # "loops" exactly once; only here so we've something to break out of @@ -1670,7 +1874,7 @@ def load_from_filename(cls, wallet_filename): return self else: - print(prog+": warning: found unsupported keystore type " + keystore_type, file=sys.stderr) + print("Warning: found unsupported keystore type " + keystore_type, file=sys.stderr) # Electrum 2.7+ multisig or 2fa wallet for i in itertools.count(1): @@ -1681,7 +1885,7 @@ def load_from_filename(cls, wallet_filename): xprv = x.get("xprv") if xprv: break else: - print(prog + ": warning: found unsupported key type " + x_type, file=sys.stderr) + print("Warning: found unsupported key type " + x_type, file=sys.stderr) if xprv: break # Electrum 2.0 - 2.6.4 wallet with imported loose private keys @@ -1702,7 +1906,7 @@ def load_from_filename(cls, wallet_filename): else: mpks = wallet.get("master_private_keys") if mpks: - xprv = mpks.values()[0] + xprv = list(mpks.values())[0] break raise RuntimeError("No master private keys or seeds found in Electrum2 wallet") @@ -1718,7 +1922,7 @@ def load_from_filename(cls, wallet_filename): # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 assert b"1" < b"9" < b"A" < b"Z" < b"a" < b"z" # the b58 check below assumes ASCII ordering in the interest of speed - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): #Electrum2 # Copy some vars into local for a small speed boost l_sha256 = hashlib.sha256 l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1726,28 +1930,26 @@ def return_verified_password_or_false(self, passwords): iv = self._iv # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) for count, password in enumerate(passwords, 1): key = l_sha256( l_sha256( password ).digest() ).digest() xprv = l_aes256_cbc_decrypt(key, iv, part_encrypted_xprv) - if xprv.startswith(b"xprv"): # BIP32 extended private key version bytes + if xprv.startswith(b"xprv") or xprv.startswith(b"zprv"): # BIP32 extended private key version bytes for c in xprv[4:]: # If it's outside of the base58 set [1-9A-HJ-NP-Za-km-z] - if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl": break # not base58 + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": break # not base58 else: # if the loop above doesn't break, it's base58 - return password if tstr == str else password.decode("utf_8", "replace"), count + return password.decode("utf_8", "replace"), count return False, count @register_wallet_class class WalletElectrumLooseKey(WalletElectrum): - class __metaclass__(type): - @property - def data_extract_id(cls): return b"ek" + def data_extract_id(): + return "ek" @staticmethod def is_wallet_file(wallet_file): return False # WalletElectrum2.load_from_filename() creates us @@ -1755,7 +1957,7 @@ def is_wallet_file(wallet_file): return False # WalletElectrum2.load_from_filen # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 assert b"1" < b"9" < b"A" < b"Z" < b"a" < b"z" # the b58 check below assumes ASCII ordering in the interest of speed - def return_verified_password_or_false(self, passwords): + def return_verified_password_or_false(self, passwords): #ElectrumLooseKey # Copy some vars into local for a small speed boost l_sha256 = hashlib.sha256 l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -1763,27 +1965,27 @@ def return_verified_password_or_false(self, passwords): iv = self._iv # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) for count, password in enumerate(passwords, 1): key = l_sha256( l_sha256( password ).digest() ).digest() privkey_end = l_aes256_cbc_decrypt(key, iv, encrypted_privkey_end) - padding_len = ord(privkey_end[-1]) + padding_len = privkey_end[-1] # Check for valid PKCS7 padding for a 52 or 51 byte "WIF" private key # (4*16-byte-blocks == 64, 64 - 52 or 51 == 12 or 13 - if (padding_len == 12 or padding_len == 13) and privkey_end.endswith(chr(padding_len) * padding_len): + if (padding_len == 12 or padding_len == 13) and privkey_end.endswith((chr(padding_len) * padding_len).encode()): for c in privkey_end[:-padding_len]: # If it's outside of the base58 set [1-9A-HJ-NP-Za-km-z] - if c > b"z" or c < b"1" or b"9" < c < b"A" or b"Z" < c < b"a" or c in b"IOl": break # not base58 + if c > ord("z") or c < ord("1") or ord("9") < c < ord("A") or ord("Z") < c < ord("a") or chr(c) in "IOl": break # not base58 else: # if the loop above doesn't break, it's base58 - return password if tstr == str else password.decode("utf_8", "replace"), count + return password.decode("utf_8", "replace"), count return False, count @register_wallet_class class WalletElectrum28(object): + opencl_algo = -1 def passwords_per_seconds(self, seconds): return max(int(round(self._passwords_per_second * seconds)), 1) @@ -1791,14 +1993,22 @@ def passwords_per_seconds(self, seconds): @staticmethod def is_wallet_file(wallet_file): wallet_file.seek(0) - try: data = base64.b64decode(wallet_file.read(8)) - except TypeError: return False + try: + base64walletData = wallet_file.read(8) + data = base64.b64decode(base64walletData) + except: return False return data[:4] == b"BIE1" # Electrum 2.8+ magic def __init__(self, loading = False): assert loading, 'use load_from_* to create a ' + self.__class__.__name__ global hmac, coincurve - import hmac, coincurve + import hmac + + try: + import coincurve + except ModuleNotFoundError: + exit("\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + pbkdf2_library_name = load_pbkdf2_library().__name__ self._aes_library_name = load_aes256_library().__name__ self._passwords_per_second = 800 if pbkdf2_library_name == "hashlib" else 140 @@ -1812,7 +2022,13 @@ def __getstate__(self): def __setstate__(self, state): # Restore coincurve.PublicKey object and (re-)load the required libraries global hmac, coincurve - import hmac, coincurve + import hmac + + try: + import coincurve + except ModuleNotFoundError: + exit("\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + load_pbkdf2_library(warnings=False) load_aes256_library(warnings=False) self.__dict__ = state @@ -1844,41 +2060,59 @@ def load_from_filename(cls, wallet_filename): def difficulty_info(self): return "1024 PBKDF2-SHA512 iterations + ECC" + def return_verified_password_or_false(self, passwords): # Electrum28 + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(passwords) + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def _return_verified_password_or_false_cpu(self, passwords): #Electrum28 cutils = coincurve.utils # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) for count, password in enumerate(passwords, 1): # Derive the ECIES shared public key, and from it, the AES and HMAC keys - static_privkey = pbkdf2_hmac(b"sha512", password, b"", 1024, 64) + static_privkey = pbkdf2_hmac("sha512", password, b"", 1024, 64) # Electrum uses a 512-bit private key (why?), but libsecp256k1 expects a 256-bit key < group's order: static_privkey = cutils.int_to_bytes( cutils.bytes_to_int(static_privkey) % cutils.GROUP_ORDER_INT ) shared_pubkey = self._ephemeral_pubkey.multiply(static_privkey).format() keys = hashlib.sha512(shared_pubkey).digest() - # Only run these initial checks if we have a fast AES library - if self._aes_library_name != 'aespython': - # Check for the expected zlib and deflate headers in the first 16-byte decrypted block - plaintext_block = aes256_cbc_decrypt(keys[16:32], keys[:16], self._ciphertext_beg) # key, iv, ciphertext - if not (plaintext_block.startswith(b"\x78\x9c") and ord(plaintext_block[2]) & 0x7 == 0x5): - continue + # Check the MAC + computed_mac = hmac.new(keys[32:], self._all_but_mac, hashlib.sha256).digest() + if computed_mac == self._mac: + return password.decode("utf_8", "replace"), count - # Check for valid PKCS7 padding in the last 16-byte decrypted block - plaintext_block = aes256_cbc_decrypt(keys[16:32], self._ciphertext_end[:16], self._ciphertext_end[16:]) # key, iv, ciphertext - padding_len = ord(plaintext_block[-1]) - if not (1 <= padding_len <= 16 and plaintext_block.endswith(chr(padding_len) * padding_len)): - continue + return False, count + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_opencl(self, arg_passwords): #Electrum28 + cutils = coincurve.utils + + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512, passwords, b"", 1024, 64) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, static_privkey) in enumerate(results, 1): + # Electrum uses a 512-bit private key (why?), but libsecp256k1 expects a 256-bit key < group's order: + static_privkey = cutils.int_to_bytes( cutils.bytes_to_int(static_privkey) % cutils.GROUP_ORDER_INT ) + shared_pubkey = self._ephemeral_pubkey.multiply(static_privkey).format() + keys = hashlib.sha512(shared_pubkey).digest() # Check the MAC computed_mac = hmac.new(keys[32:], self._all_but_mac, hashlib.sha256).digest() if computed_mac == self._mac: - return password if tstr == str else password.decode("utf_8", "replace"), count + return password.decode("utf_8", "replace"), count return False, count @@ -1888,9 +2122,122 @@ def return_verified_password_or_false(self, passwords): @register_wallet_class class WalletBlockchain(object): - class __metaclass__(type): - @property - def data_extract_id(cls): return b"bk" + #Some of these strings are concatenated to 10 chars, as a the full string may not fit in the single decrypted block + matchStrings = b"\"guid\"|\"sharedKey\"|\"double_enc|\"dpasswordh|\"metadataHD|\"options\"|\"address_bo|\"tx_notes\"|\"tx_names\"|\"keys\"|\"hd_wallets|\"paidTo\"|\"tag_names\"" + + opencl_algo = -1 + + _savepossiblematches = True + _possible_passwords_file = "possible_passwords.log" + + _dump_privkeys_file = None + _dump_wallet_file = None + _using_extract = False + + def data_extract_id(): + return "bk" + + # + # These are a bit fragile in the interest of simplicity because they assume that certain + # JSON data will be in the first block of the file + # + + # Encryption scheme used in newer wallets + def decrypt_current(self,password, salt_and_iv, iter_count, data): + key = pbkdf2_hmac("sha1", password, salt_and_iv, iter_count, 32) + decrypted = aes256_cbc_decrypt(key, salt_and_iv, data) # CBC mode + padding = ord(decrypted[-1:]) # ISO 10126 padding length + # A bit fragile because it assumes the guid is in the first encrypted block, + # although this has always been the case as of 6/2014 (since 12/2011) + # As of May 2020, guid no longer appears in the first block, but tx_notes appears there instead + return decrypted[:-padding] if 1 <= padding <= 16 and re.search(self.matchStrings, decrypted) else None + + # + # Encryption scheme only used in version 0.0 wallets (N.B. this is untested) + def decrypt_old(self, password, salt_and_iv, data): + key = pbkdf2_hmac("sha1", password, salt_and_iv, 1, 32) # only 1 iteration + decrypted = aes256_ofb_decrypt(key, salt_and_iv, data) # OFB mode + # The 16-byte last block, reversed, with all but the first byte of ISO 7816-4 padding removed: + last_block = tuple(itertools.dropwhile(lambda x: x == b"\0", decrypted[:15:-1])) + padding = 17 - len(last_block) # ISO 7816-4 padding length + return decrypted[:-padding] if 1 <= padding <= 16 and \ + decrypted[-padding] == b"\x80" and \ + re.match(self.matchStrings,decrypted.decode()) else None + + def decrypt_wallet(self,password): + from lib.cashaddress import base58 + + # Can't decrypt or dump an extract in any meaninful way... + if self._using_extract: + return + + # If we aren't dumping these files, then just return... + if not (self._dump_wallet_file or self._dump_privkeys_file): + return + + #print(self._encrypted_wallet) + + # Convert and split encrypted private key + #encrypted = base64.b64decode(self._encrypted_wallet) + iv, encrypted = self._encrypted_wallet[:16], self._encrypted_wallet[16:] + + if self._iter_count: # v2.0 wallets have a single possible encryption scheme + data = self.decrypt_current(password, iv, self._iter_count, encrypted) + else: # v0.0 wallets have three different possible encryption schemes + data = self.decrypt_current(password, iv, 10, encrypted) or \ + self.decrypt_current(password, iv, 1, encrypted) or \ + self.decrypt_old(password, iv, encrypted) + + # Load and parse the now-decrypted wallet + self._wallet_json = json.loads(data) + + # Add these items to the json for their associated address + for key in self._wallet_json['keys']: + try: + # Need to check that the private key is actually 64 characters (32 bytes) long, as some blockchain wallets + # have a bug where the base58 private keys in wallet files leave off any leading zeros... + privkey = binascii.hexlify(base58.b58decode(key["priv"])) + privkey = privkey.zfill(64) + privkey = binascii.unhexlify(privkey) + + # Some versions of blockchain wallets can be inconsistent in whether they used compressed or uncompressed addresses + # Rather than do something clever like check the addr key for to check which, just dump both for now... + key['privkey_compressed'] = base58.b58encode_check(bytes([0x80]) + privkey + bytes([0x1])) + key['privkey_uncompressed'] = base58.b58encode_check(bytes([0x80]) + privkey) + except ValueError: + print("Error: Private Key not correctly decrypted, likey due to second password being present...") + + if self._dump_wallet_file: + self.dump_wallet() + if self._dump_privkeys_file: + self.dump_privkeys() + + # This just dumps the wallet json as-is (regardless of whether the keys have been decrypted + def dump_wallet(self): + with open(self._dump_wallet_file, 'a') as logfile: + logfile.write(json.dumps(self._wallet_json, indent=4)) + + # This just dumps the wallet private keys + def dump_privkeys(self): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Private Keys (For copy/paste in to Electrum) are below...\n") + + for key in self._wallet_json['keys']: + # Blockchain.com wallets are fairly inconsistent in whether they used + # compressed or uncompressed keys, so produce both... + try: + logfile.write(key['privkey_compressed'] + "\n") + logfile.write(key['privkey_uncompressed'] + "\n") + except KeyError: + print("Error: Private Key not correctly decrypted, likey due to second password being present...") + + # Older wallets don't have any hd_wallets at all, so handle this gracefully + try: + for hd_wallets in self._wallet_json['hd_wallets']: + for accounts in hd_wallets['accounts']: + logfile.write(accounts['xpriv'] + "\n") + except: + pass @staticmethod def is_wallet_file(wallet_file): return None # there's no easy way to check this @@ -1924,6 +2271,7 @@ def load_from_filename(cls, wallet_filename): self = cls(iter_count, loading=True) self._salt_and_iv = data[:16] # only need the salt_and_iv plus self._encrypted_block = data[16:32] # the first 16-byte encrypted block + self._encrypted_wallet = data return self # Parse the contents of an encrypted blockchain wallet (v0 - v3) or config file returning two @@ -1948,12 +2296,12 @@ def _parse_encrypted_blockchain_wallet(data): data = json.loads(data) # try again to parse a v2.0/v3.0 JSON-encoded wallet file except ValueError: break - # Extract what's needed from a v2.0/3.0 wallet file - if data["version"] > 3: - raise NotImplementedError("Unsupported Blockchain wallet version " + unicode(data["version"])) + # Extract what's needed from a v2.0/3.0/4 wallet file + if data["version"] > 4: + raise NotImplementedError("Unsupported Blockchain wallet version " + str(data["version"])) iter_count = data["pbkdf2_iterations"] if not isinstance(iter_count, int) or iter_count < 1: - raise ValueError("Invalid Blockchain pbkdf2_iterations " + unicode(iter_count)) + raise ValueError("Invalid Blockchain pbkdf2_iterations " + str(iter_count)) data = data["payload"] break @@ -1963,11 +2311,12 @@ def _parse_encrypted_blockchain_wallet(data): try: data = base64.b64decode(data) except TypeError as e: - raise ValueError("Can't base64-decode Blockchain wallet: "+unicode(e)) + raise ValueError("Can't base64-decode Blockchain wallet: "+str(e)) if len(data) < 32: raise ValueError("Encrypted Blockchain data is too short") - if len(data) % 16 != 0: - raise ValueError("Encrypted Blockchain data length is not divisible by the encryption blocksize (16)") + #Used to check if the length of the decrypted data was divisible by 16, but this wasn't actually true for all v0 wallets + #if len(data) % 16 != 0: + # raise ValueError("Encrypted Blockchain data length is not divisible by the encryption blocksize (16)") # If this is (possibly) a v0.0 (a.k.a. v1) wallet file, check that the encrypted data # looks random, otherwise this could be some other type of base64-encoded file such @@ -1975,9 +2324,9 @@ def _parse_encrypted_blockchain_wallet(data): if not iter_count: # if this is a v0.0 wallet # The likelihood of of finding a valid encrypted blockchain wallet (even at its minimum length # of about 500 bytes) with less than 7.4 bits of entropy per byte is less than 1 in 10^6 - # (decreased test below to 7.2 after being shown a wallet with just under 7.4 entropy bits) + # (decreased test below to 7.0 after being shown a wallet with 7.0 entropy bits) entropy_bits = est_entropy_bits(data) - if entropy_bits < 7.2: + if entropy_bits < 7.0: raise ValueError("Doesn't look random enough to be an encrypted Blockchain wallet (only {:.1f} bits of entropy per byte)".format(entropy_bits)) return data, iter_count # iter_count == 0 for v0 wallets @@ -1990,14 +2339,93 @@ def load_from_data_extract(cls, file_data): self = cls(iter_count, loading=True) self._encrypted_block = encrypted_block self._salt_and_iv = salt_and_iv + self._using_extract = True return self def difficulty_info(self): return "{:,} PBKDF2-SHA1 iterations".format(self._iter_count or 10) + def init_logfile(self): + with open(self._possible_passwords_file, 'a') as logfile: + logfile.write( + "\n\n" + + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " New Recovery Started...\n" + + "This file contains passwords and blocks from passwords which `may` not exactly match those that " + "BTCRecover searches for by default. \n\n" + "Examples of successfully decrypted blocks will not just be random characters, " + "some examples of what correctly decryped blocks logs look like are:\n\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>{\n\"guid\" : \"9bb<==\n" + "Possible Password ==>testblockchain<== in Decrypted Block ==>{\"address_book\":<==\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>{\"tx_notes\":{},\"\n" + "Possible Password ==>Testing123!<== in Decrypted Block ==>{\"double_encrypt<==\n" + "\n" + "Note: The markers ==> and <== are not part of either your password or the decrypted block...\n\n" + "If the password works and was not correctly found, or your wallet detects a false positive, please report the decrypted block data at " + "https://github.com/3rdIteration/btcrecover/issues/\n\n") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print("* Note for Blockchain.com Wallets... *") + print("* *") + print("* Writing all `possibly matched` and fully matched Passwords & *") + print("* Decrypted blocks to ", self._possible_passwords_file) + print("* This can be disabled with the --disablesavepossiblematches argument *") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print() + + # A bit fragile because it assumes that some specific text is in the first encrypted block, + # This was "guid" as of 6/2014 (since 12/2011) + # As of May 2020, guid no longer appears in the first block, but 'tx_notes' appears there instead + # Also check to see if the first block starts with 'address_book' + # first as was apparently the case with some wallets created around Jan 2014 + # (see https://github.com/gurnec/btcrecover/issues/ that start with "double_encryption" + # as per this issue here: https://github.com/3rdIteration/btcrecover/issues/96 + def check_blockchain_decrypted_block(self, unencrypted_block, password): + # Return True if + if re.search(self.matchStrings, unencrypted_block): + if self._savepossiblematches: + try: + return True # Only return true if we can successfully decode the block in to ascii + + except UnicodeDecodeError: # Likely a false positive if we can't... + with open('possible_passwords.log', 'a', encoding="utf_8") as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Found Likely False Positive Password (with non-Ascii characters in decrypted block) ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("utf-8", "ignore") + + "<==\n") + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "**NOTICE** Possible (Unlikely) Match Found, recorded in possible_passwords.log") + + elif unencrypted_block[0] == ord("{"): + if b'"' in unencrypted_block[:4]: # If it really is a json wallet fragment, there will be a double quote in there within the first few characters... + try: + # Try to decode the decrypted block to ascii, this will pretty much always fail on anything other + # than the correct password + unencrypted_block.decode("ascii") + if self._savepossiblematches: + with open(self._possible_passwords_file, 'a', encoding="utf_8") as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Possible Password ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("ascii") + + "<==\n") + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "**NOTICE** Possible (Unlikely) Match Found, recorded in possible_passwords.log") + except UnicodeDecodeError: + pass + + + + return False + + def return_verified_password_or_false(self, passwords): # Blockchain.com Main Password + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(passwords) + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def _return_verified_password_or_false_cpu(self, arg_passwords): # Blockchain.com Main Password # Copy a few globals into local for a small speed boost l_pbkdf2_hmac = pbkdf2_hmac l_aes256_cbc_decrypt = aes256_cbc_decrypt @@ -2007,38 +2435,161 @@ def return_verified_password_or_false(self, passwords): iter_count = self._iter_count # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = map(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) v0 = not iter_count # version 0.0 wallets don't specify an iter_count if v0: iter_count = 10 # the default iter_count for version 0.0 wallets + for count, password in enumerate(passwords, 1): - key = l_pbkdf2_hmac(b"sha1", password, salt_and_iv, iter_count, 32) # iter_count iterations + key = l_pbkdf2_hmac("sha1", password, salt_and_iv, iter_count, 32) # iter_count iterations unencrypted_block = l_aes256_cbc_decrypt(key, salt_and_iv, encrypted_block) # CBC mode - # A bit fragile because it assumes the guid is in the first encrypted block, - # although this has always been the case as of 6/2014 (since 12/2011) - if unencrypted_block[0] == b"{" and b'"guid"' in unencrypted_block: - return password if tstr == str else password.decode("utf_8", "replace"), count + + if self.check_blockchain_decrypted_block(unencrypted_block, password): + # Decrypt and dump the wallet if required + self.decrypt_wallet(password) + return password.decode("utf_8", "replace"), count if v0: + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + # Try the older encryption schemes possibly used in v0.0 wallets for count, password in enumerate(passwords, 1): - key = l_pbkdf2_hmac(b"sha1", password, salt_and_iv, 1, 32) # only 1 iteration + key = l_pbkdf2_hmac("sha1", password, salt_and_iv, 1, 32) # only 1 iteration unencrypted_block = l_aes256_cbc_decrypt(key, salt_and_iv, encrypted_block) # CBC mode - if unencrypted_block[0] == b"{" and b'"guid"' in unencrypted_block: - return password if tstr == str else password.decode("utf_8", "replace"), count + # print("CBC:", unencrypted_block) + if self.check_blockchain_decrypted_block(unencrypted_block, password): + return password.decode("utf_8", "replace"), count + unencrypted_block = l_aes256_ofb_decrypt(key, salt_and_iv, encrypted_block) # OFB mode - if unencrypted_block[0] == b"{" and b'"guid"' in unencrypted_block: - return password if tstr == str else password.decode("utf_8", "replace"), count + # print("OBF:", unencrypted_block) + if self.check_blockchain_decrypted_block(unencrypted_block, password): + return password.decode("utf_8", "replace"), count + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): # Blockchain.com Main Password + # Copy a few globals into local for a small speed boost + l_aes256_cbc_decrypt = aes256_cbc_decrypt + l_aes256_ofb_decrypt = aes256_ofb_decrypt + encrypted_block = self._encrypted_block + salt_and_iv = self._salt_and_iv + iter_count = self._iter_count + + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha1, passwords, salt_and_iv, iter_count, 32) + + #This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password,key) in enumerate(results, 1): + unencrypted_block = l_aes256_cbc_decrypt(key, salt_and_iv, encrypted_block) # CBC mode + if self.check_blockchain_decrypted_block(unencrypted_block, password): + return password.decode("utf_8", "replace"), count return False, count + @register_wallet_class class WalletBlockchainSecondpass(WalletBlockchain): - class __metaclass__(WalletBlockchain.__metaclass__): - @property - def data_extract_id(cls): return b"bs" + _dump_privkeys_file = None + _dump_wallet_file = None + _using_extract = False + + def data_extract_id(): + return "bs" + + def decrypt_secondpass_privkey(self, encrypted, password, iterations, legacy_decrypt): + # Convert and split encrypted private key + encrypted = base64.b64decode(encrypted) + iv, encrypted = encrypted[:16], encrypted[16:] + + # Create the decryption key and decrypt the private key + aeshash = pbkdf2_hmac("sha1", password, iv, iterations, 32) + if not legacy_decrypt: + clear = aes256_cbc_decrypt(aeshash, iv, encrypted) + else: + clear = aes256_ofb_decrypt(aeshash, iv, encrypted) + + # Remove ISO 10126 Padding + pad_len = clear[-1] + decrypted = clear[:-pad_len] + return decrypted + + def decrypt_wallet(self, password, iter_count, legacy_decrypt = False): + from lib.cashaddress import base58 + + # Can't decrypt or dump an extract in any meaninful way... + if self._using_extract: + return + + # If we aren't dumping these files, then just return... + if not (self._dump_wallet_file or self._dump_privkeys_file): + return + + # Decrypt the keys and add these items to the json for their associated address + for key in self._wallet_json['keys']: + privkey = self.decrypt_secondpass_privkey(key["priv"], + self._wallet_json['sharedKey'].encode('ascii') + password, + iter_count, legacy_decrypt) + + key['priv_decrypted'] = base58.b58encode(base58.b58decode(privkey)) + + # Need to check that the private key is actually 64 characters (32 bytes) long, as some blockchain wallets + # have a bug where the base58 private keys in wallet files leave off any leading zeros... + privkey = binascii.hexlify(base58.b58decode(privkey)) + privkey = privkey.zfill(64) + privkey = binascii.unhexlify(privkey) + + # Some versions of blockchain wallets can be inconsistent in whether they used compressed or uncompressed addresses + # Rather than do something clever like check the addr key for to check which, just dump both for now... + key['privkey_compressed'] = base58.b58encode_check(bytes([0x80]) + privkey + bytes([0x1])) + key['privkey_uncompressed'] = base58.b58encode_check(bytes([0x80]) + privkey) + + # Older wallets don't have any hd_wallets at all, so handle this gracefully + try: + for hd_wallets in self._wallet_json['hd_wallets']: + for accounts in hd_wallets['accounts']: + accounts['xpriv_decrypted'] = self.decrypt_secondpass_privkey(accounts["xpriv"], + self._wallet_json['sharedKey'].encode('ascii') + password, + iter_count, legacy_decrypt).decode() + except: + pass + + if self._dump_wallet_file: + self.dump_wallet() + + if self._dump_privkeys_file: + self.dump_privkeys() + + # This just dumps the wallet json as-is (regardless of whether the keys have been decrypted + def dump_wallet(self): + with open(self._dump_wallet_file, 'a') as logfile: + logfile.write(json.dumps(self._wallet_json, indent=4)) + + # This just dumps the wallet private keys + def dump_privkeys(self): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Private Keys (For copy/paste in to Electrum) are below...\n") + + for key in self._wallet_json['keys']: + # Blockchain.com wallets are fairly inconsistent in whether they used + # compressed or uncompressed keys, so produce both... + logfile.write(key['privkey_compressed'] + "\n") + logfile.write(key['privkey_uncompressed'] + "\n") + + try: + for hd_wallets in self._wallet_json['hd_wallets']: + for accounts in hd_wallets['accounts']: + logfile.write(accounts['xpriv_decrypted']+ "\n") + except: + pass + @staticmethod def is_wallet_file(wallet_file): return False # never auto-detected as this wallet type @@ -2061,42 +2612,25 @@ def load_from_filename(cls, wallet_filename, password = None, force_purepython = pass else: raise - except StandardError as e: - error_exit(unicode(e)) + except Exception as e: + error_exit(str(e)) else: # If there were no problems getting the encrypted data, decrypt it if not password: password = prompt_unicode_password( - b"Please enter the Blockchain wallet's main password: ", + "Please enter the Blockchain wallet's main password: ", "encrypted Blockchain files must be decrypted before searching for the second password") password = password.encode("utf_8") data, salt_and_iv = data[16:], data[:16] load_pbkdf2_library(force_purepython) load_aes256_library(force_purepython) - # - # These are a bit fragile in the interest of simplicity because they assume the guid is the first - # name in the JSON object, although this has always been the case as of 6/2014 (since 12/2011) - # - # Encryption scheme used in newer wallets - def decrypt_current(iter_count): - key = pbkdf2_hmac(b"sha1", password, salt_and_iv, iter_count, 32) - decrypted = aes256_cbc_decrypt(key, salt_and_iv, data) # CBC mode - padding = ord(decrypted[-1:]) # ISO 10126 padding length - return decrypted[:-padding] if 1 <= padding <= 16 and re.match(b'{\s*"guid"', decrypted) else None - # - # Encryption scheme only used in version 0.0 wallets (N.B. this is untested) - def decrypt_old(): - key = pbkdf2_hmac(b"sha1", password, salt_and_iv, 1, 32) # only 1 iteration - decrypted = aes256_ofb_decrypt(key, salt_and_iv, data) # OFB mode - # The 16-byte last block, reversed, with all but the first byte of ISO 7816-4 padding removed: - last_block = tuple(itertools.dropwhile(lambda x: x==b"\0", decrypted[:15:-1])) - padding = 17 - len(last_block) # ISO 7816-4 padding length - return decrypted[:-padding] if 1 <= padding <= 16 and decrypted[-padding] == b"\x80" and re.match(b'{\s*"guid"', decrypted) else None - # + if iter_count: # v2.0 wallets have a single possible encryption scheme - data = decrypt_current(iter_count) + data = cls.decrypt_current(cls, password, salt_and_iv, iter_count, data) else: # v0.0 wallets have three different possible encryption schemes - data = decrypt_current(10) or decrypt_current(1) or decrypt_old() + data = cls.decrypt_current(cls, password, salt_and_iv, 10, data) or \ + cls.decrypt_current(cls, password, salt_and_iv, 1, data) or \ + cls.decrypt_old(cls, password, salt_and_iv, data) if not data: error_exit("can't decrypt wallet (wrong main password?)") @@ -2109,7 +2643,7 @@ def decrypt_old(): try: iter_count = data["options"]["pbkdf2_iterations"] if not isinstance(iter_count, int) or iter_count < 1: - raise ValueError("Invalid Blockchain second password pbkdf2_iterations " + unicode(iter_count)) + raise ValueError("Invalid Blockchain second password pbkdf2_iterations " + str(iter_count)) except KeyError: iter_count = 0 self = cls(iter_count, loading=True) @@ -2118,10 +2652,14 @@ def decrypt_old(): if len(self._password_hash) != 32: raise ValueError("Blockchain second password hash is not 32 bytes long") # + self._salt = data["sharedKey"].encode("ascii") - if str(UUID(self._salt)) != self._salt: + + if str(UUID(self._salt.decode().replace("-",""))).encode() != self._salt: raise ValueError("Unrecognized Blockchain salt format") + self._wallet_json = data + return self # Import extracted Blockchain file data necessary for second password checking @@ -2133,14 +2671,19 @@ def load_from_data_extract(cls, file_data): self = cls(iter_count, loading=True) self._salt = str(UUID(bytes=uuid_salt)) self._password_hash = password_hash + self._using_extract = True return self def difficulty_info(self): return ("{:,}".format(self._iter_count) if self._iter_count else "1-10") + " SHA-256 iterations" + def return_verified_password_or_false(self, passwords): # Blockchain.com second Password + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(passwords) + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): + def _return_verified_password_or_false_cpu(self, arg_passwords): # Blockchain.com Secondpassword # Copy vars into locals for a small speed boost l_sha256 = hashlib.sha256 password_hash = self._password_hash @@ -2148,277 +2691,2873 @@ def return_verified_password_or_false(self, passwords): iter_count = self._iter_count # Convert Unicode strings (lazily) to UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: p.encode("utf_8", "ignore"), passwords) + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) - # Newer wallets specify an iter_count and use something similar to PBKDF1 with SHA-256 - if iter_count: - for count, password in enumerate(passwords, 1): - running_hash = salt + password - for i in xrange(iter_count): - running_hash = l_sha256(running_hash).digest() - if running_hash == password_hash: - return password if tstr == str else password.decode("utf_8", "replace"), count + for count, password in enumerate(passwords, 1): - # Older wallets used one of three password hashing schemes - else: - for count, password in enumerate(passwords, 1): - running_hash = l_sha256(salt + password).digest() - # Just a single SHA-256 hash - if running_hash == password_hash: - return password if tstr == str else password.decode("utf_8", "replace"), count - # Exactly 10 hashes (the first of which was done above) - for i in xrange(9): + # Newer wallets specify an iter_count and use something similar to PBKDF1 with SHA-256 + if iter_count: + if isinstance(salt,str): running_hash = salt.encode() + password + if isinstance(salt,bytes): running_hash = salt + password + for i in range(iter_count): running_hash = l_sha256(running_hash).digest() if running_hash == password_hash: - return password if tstr == str else password.decode("utf_8", "replace"), count - # A single unsalted hash - if l_sha256(password).digest() == password_hash: - return password if tstr == str else password.decode("utf_8", "replace"), count + #print("Debug: Matched Second pass (Iter-Count present)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, iter_count) + return password.decode("utf_8", "replace"), count + + # Older wallets used one of three password hashing schemes + # 2022-03 Update - It also seems that some newer (v3) wallets use these older hashing schemes too... + if isinstance(salt,str): running_hash = l_sha256(salt.encode() + password).digest() + if isinstance(salt, bytes): running_hash = l_sha256(salt + password).digest() + # Just a single SHA-256 hash + if running_hash == password_hash: + #print("Debug: Matched Second pass (Single Hash)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 1) + return password.decode("utf_8", "replace"), count + # Exactly 10 hashes (the first of which was done above) + for i in range(9): + running_hash = l_sha256(running_hash).digest() + if running_hash == password_hash: + #print("Debug: Matched Second pass (Exactly 10 hashes)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 10) + return password.decode("utf_8", "replace"), count + # A single unsalted hash + if l_sha256(password).digest() == password_hash: + #print("Debug: Matched Second pass (Single Unsalted Hash)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 1, True) + return password.decode("utf_8", "replace"), count return False, count + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_opencl(self, arg_passwords): # Blockchain.com Secondpassword + # Copy vars into locals for a small speed boost + l_sha256 = hashlib.sha256 + password_hash = self._password_hash + salt = self._salt + iter_count = self._iter_count -############### Bither ############### + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + hashed_keys = [] + for password in passwords: + if isinstance(salt, str): derived_key = salt.encode() + password + if isinstance(salt, bytes): derived_key = salt + password + hashed_keys.append(l_sha256(derived_key).digest()) + + if iter_count: + clResult = self.opencl_algo.cl_hash_iterations(self.opencl_context_hash_iterations_sha256, hashed_keys, self._iter_count-1, 8) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + # Newer wallets specify an iter_count and use something similar to PBKDF1 with SHA-256 + for count, (password, derived_key) in enumerate(results, 1): + if derived_key == password_hash: + self.decrypt_wallet(password, iter_count) + return password.decode("utf_8", "replace"), count + + # Older wallets used one of three password hashing schemes + # 2022-03 Update - It also seems that some newer (v3) wallets use these older hashing schemes too... + # (These older encryption schemes aren't worth running on the GPU, too few iterations) + + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + for count, password in enumerate(passwords, 1): + if isinstance(salt, str): running_hash = l_sha256(salt.encode() + password).digest() + if isinstance(salt, bytes): running_hash = l_sha256(salt + password).digest() + # Just a single SHA-256 hash + if running_hash == password_hash: + # print("Debug: Matched Second pass (Single Hash)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 1) + return password.decode("utf_8", "replace"), count + # Exactly 10 hashes (the first of which was done above) + for i in range(9): + running_hash = l_sha256(running_hash).digest() + if running_hash == password_hash: + # print("Debug: Matched Second pass (Exactly 10 hashes)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 10) + return password.decode("utf_8", "replace"), count + # A single unsalted hash + if l_sha256(password).digest() == password_hash: + # print("Debug: Matched Second pass (Single Unsalted Hash)") + # Decrypt wallet and dump if required + self.decrypt_wallet(password, 1, True) + return password.decode("utf_8", "replace"), count + + + return False, count + +############### Block.io ############### @register_wallet_class -class WalletBither(object): +class WalletBlockIO(object): + opencl_algo = -1 + _savepossiblematches = False - class __metaclass__(type): - @property - def data_extract_id(cls): return b"bt" + _dump_privkeys_file = None + _dump_wallet_file = None + _using_extract = False - def passwords_per_seconds(self, seconds): - return max(int(round(self._passwords_per_second * seconds)), 1) + def __init__(self): + try: + import ecdsa + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load ecdsa module which is required for block.io wallets... You can install it with the command 'pip3 install ecdsa") + + try: + import bitcoinutils + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load bitcoin-utils module which is required for block.io wallets... You can install it with the command 'pip3 install bitcoin-utils'") @staticmethod def is_wallet_file(wallet_file): wallet_file.seek(0) - # returns "maybe yes" or "definitely no" (mSIGNA wallets are also SQLite 3) - return None if wallet_file.read(16) == b"SQLite format 3\0" else False - - def __init__(self, loading = False): - assert loading, 'use load_from_* to create a ' + self.__class__.__name__ - # loading crypto libraries is done in load_from_* + try: + walletdata = wallet_file.read() + except: return False + return (b"user_key" in walletdata and b"encrypted_passphrase" in walletdata) # Block.io wallets have a user_key field and emcrypted passphrase fields which are quite unique - def __setstate__(self, state): - # (re-)load the required libraries after being unpickled - global pylibscrypt, coincurve - import pylibscrypt, coincurve - load_aes256_library(warnings=False) - self.__dict__ = state + def passwords_per_seconds(self, seconds): + try: + if self.user_key['algorithm']['pbkdf2_iterations'] == 2048: + return 5000 + else: + return 150 # Newer wallets use over 100,000 PBKDF2 iterations + except KeyError: # Older Legacy wallets don't have a algorithm key at all... + return 5000 - # Load a Bither wallet file (the part of it we need) + # Load a Dogechain wallet file @classmethod def load_from_filename(cls, wallet_filename): - import sqlite3 - wallet_conn = sqlite3.connect(wallet_filename) + self = cls() + with open(wallet_filename, "rb") as wallet_file: + wallet_data = wallet_file.read() - is_bitcoinj_compatible = None - # Try to find an encrypted loose key first; they're faster to check + json_data = json.loads(wallet_data) + # There are two ways that the JSON from block.io can be formatted, depending on which backup the user retrieves try: - wallet_cur = wallet_conn.execute(b"SELECT encrypt_private_key FROM addresses LIMIT 1") - key_data = wallet_cur.fetchone() - if key_data: - key_data = key_data[0] + self.user_key = json_data['data']['current_user_keys'][0]['user_key'] + except KeyError: + self.user_key = json_data['data']['user_key'] + return self + + def difficulty_info(self): + try: + iter_count = self.user_key['algorithm']['pbkdf2_iterations'] + hash_function = self.user_key['algorithm']['pbkdf2_hash_function'] + except KeyError: + iter_count = 2048 + hash_function = "SHA256" + return str(iter_count) + " " + hash_function + " Iterations" + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, arg_passwords): # block.io Main Password + + for count, password in enumerate(arg_passwords, 1): + try: + key = lib.block_io.BlockIo.Helper.dynamicExtractKey(self.user_key, password) + if self.user_key['public_key'].encode() == key.pubkey_hex(): + return password, count + + except lib.block_io.IncorrectDecryptionPasswordError: + pass + except binascii.Error: + pass + + return False, count + +############### Bitgo User Key ############### + +@register_wallet_class +class WalletBitGo(object): + opencl_algo = -1 + _savepossiblematches = False + + _dump_privkeys_file = None + _dump_wallet_file = None + _using_extract = False + + def __init__(self): + if not sjcl_available: + exit( + "\nERROR: Cannot load SJCL module which is required for BitGo wallets... You can install it with the command 'pip3 install sjcl") + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + json.loads(walletdata) # Check if it's a valid JSON + except: return False + + return (b"adata" in walletdata and b"aes" in walletdata) + + def passwords_per_seconds(self, seconds): + return 5 + + # Load a Dogechain wallet file + @classmethod + def load_from_filename(cls, wallet_filename): + self = cls() + with open(wallet_filename, "rb") as wallet_file: + wallet_data = wallet_file.read() + + self.user_key = json.loads(wallet_data) + + return self + + def difficulty_info(self): + iter_count = self.user_key['iter'] + hash_function = str(self.user_key['ks']) + return str(iter_count) + " SHA" + hash_function + " Iterations" + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, arg_passwords): # block.io Main Password + + for count, password in enumerate(arg_passwords, 1): + try: + key = SJCL().decrypt(self.user_key, password) + return password, count + + except ValueError: + pass + + return False, count + +############### Dogechain.info ############### + +@register_wallet_class +class WalletDogechain(object): + opencl_algo = -1 + _savepossiblematches = True + _possible_passwords_file = "possible_passwords.log" + + matchStrings = b"\"guid\"|\"sharedKey\"|\"keys\"" + + _dump_privkeys_file = None + _dump_wallet_file = None + _using_extract = False + + def data_extract_id(): + return "dc" + + # + # These are a bit fragile in the interest of simplicity because they assume that certain + # JSON data will be in the first block of the file + # + def decrypt(self, password): + passwordSHA256 = hashlib.sha256(password).digest() + passwordbase64 = base64.b64encode(passwordSHA256) + key = hashlib.pbkdf2_hmac('sha256', passwordbase64, self.salt, self._iter_count, 32) + + decrypted = AES.new(key, AES.MODE_CBC).decrypt(self._encrypted_wallet) + padding = ord(decrypted[-1:]) # ISO 10126 padding length + + # A bit fragile because it assumes the guid is in the first encrypted block, + return decrypted[:-padding] if 1 <= padding <= 16 and re.search( + self.matchString, decrypted) else None + + def decrypt_wallet(self, password): + # Can't decrypt or dump an extract in any meaninful way... + if self._using_extract: + return + + # If we aren't dumping these files, then just return... + if not (self._dump_wallet_file or self._dump_privkeys_file): + return + + # print(self._encrypted_wallet) + data = self.decrypt(password)[16:] + + # Load and parse the now-decrypted wallet + self._wallet_json = json.loads(data) + + if self._dump_wallet_file: + self.dump_wallet() + if self._dump_privkeys_file: + self.dump_privkeys() + + # This just dumps the wallet json as-is (regardless of whether the keys have been decrypted + def dump_wallet(self): + with open(self._dump_wallet_file, 'a') as logfile: + logfile.write(json.dumps(self._wallet_json, indent=4)) + + # This just dumps the wallet private keys + def dump_privkeys(self): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Private Keys (For copy/paste) are below...\n") + for key in self._wallet_json['keys']: + try: + logfile.write(key['priv'] + "\n") + except KeyError: + print("Error: Private Key not correctly decrypted...") + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + except: return False + isWallet = False + if (b"email" in walletdata and b"two_fa_method" in walletdata): # Older Dogechain.info wallets have email and 2fa fields that are fairly unique + isWallet = True + elif (b"salt" in walletdata and b"cipher" in walletdata and b"payload" in walletdata): # Newer Dogechain.info wallets have cipher, salt and payload fields + isWallet = True + return isWallet + + def __init__(self, iter_count, loading=False): + assert loading, 'use load_from_* to create a ' + self.__class__.__name__ + pbkdf2_library_name = load_pbkdf2_library().__name__ + aes_library_name = load_aes256_library().__name__ + self._iter_count = iter_count + self._passwords_per_second = 400000 if pbkdf2_library_name == "hashlib" else 100000 + self._passwords_per_second /= iter_count + if aes_library_name != "Crypto" and self._passwords_per_second > 2000: + self._passwords_per_second = 2000 + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + load_pbkdf2_library(warnings=False) + load_aes256_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return max(int(round(self._passwords_per_second * seconds)), 1) + + # Load a Dogechain wallet file + @classmethod + def load_from_filename(cls, wallet_filename): + with open(wallet_filename, "rb") as wallet_file: + wallet_data = wallet_file.read() + wallet_json = json.loads(wallet_data) + self = cls(wallet_json["pbkdf2_iterations"], loading=True) + self.salt = base64.b64decode(wallet_json["salt"]) + try: + self.aes_cipher = wallet_json["cipher"] + except: + self.aes_cipher = "AES-CBC" + + if self.aes_cipher == "AES-CBC": + self.iv = base64.b64decode(wallet_json["payload"])[:16] + self._encrypted_wallet = base64.b64decode(wallet_json["payload"])[16:] + else: # AES GCM + self.iv = base64.b64decode(wallet_json["payload"])[:12] + self.aes_auth_tag = base64.b64decode(wallet_json["payload"])[12:12+16] + self._encrypted_wallet = base64.b64decode(wallet_json["payload"])[12+16:] + + self._encrypted_block = self._encrypted_wallet[:32] + + if ";" in wallet_json["payload"]: + exit("\n**ERROR**\nFound RSA-encrypted Dogechain wallet payload, this means it wasn't downloaded in a way that supports password recovery or decryption... You cannot decrypt this wallet and will need to download it correctly or request your encrypted wallet from dogechain)") + + return self + + @classmethod + def load_from_data_extract(cls, file_data): + # These are the same second password hash, salt, iteration count retrieved above + payload_data, salt, iter_count = struct.unpack(b"< 32s 16s I", file_data) + self = cls(iter_count, loading=True) + self.salt = salt + self._encrypted_wallet = "" + self.iv = payload_data[:16] + self._encrypted_block = payload_data[16:] + self._using_extract = True + self.aes_cipher = "AES-CBC" + return self + + def difficulty_info(self): + return "{:,} PBKDF2-SHA256 iterations".format(self._iter_count or 10) + + def init_logfile(self): + with open(self._possible_passwords_file, 'a') as logfile: + logfile.write( + "\n\n" + + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " New Recovery Started...\n" + + "This file contains passwords and blocks from passwords which `may` not exactly match those that " + "BTCRecover searches for by default. \n\n" + "Examples of successfully decrypted blocks will not just be random characters, " + "some examples of what correctly decryped blocks logs look like are:\n\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>{\n\"guid\" : \"9bb<==\n" + "Possible Password ==>testblockchain<== in Decrypted Block ==>{\"address_book\":<==\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>{\"tx_notes\":{},\"\n" + "Possible Password ==>Testing123!<== in Decrypted Block ==>{\"double_encrypt<==\n" + "\n" + "Note: The markers ==> and <== are not part of either your password or the decrypted block...\n\n" + "If the password works and was not correctly found, or your wallet detects a false positive, please report the decrypted block data at " + "https://github.com/3rdIteration/btcrecover/issues/\n\n") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print("* Note for dogechain.info Wallets... *") + print("* *") + print("* Writing all `possibly matched` and fully matched Passwords & *") + print("* Decrypted blocks to ", self._possible_passwords_file) + print("* This can be disabled with the --disablesavepossiblematches argument *") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print() + + # A bit fragile because it assumes that some specific text is in the first encrypted block, + def check_decrypted_block(self, unencrypted_block, password): + if unencrypted_block[0] == ord("{"): + if b'"' in unencrypted_block[ + :4]: # If it really is a json wallet fragment, there will be a double quote in there within the first few characters... + try: + # Try to decode the decrypted block to ascii, this will pretty much always fail on anything other + # than the correct password + unencrypted_block.decode("ascii") + if self._savepossiblematches: + with open(self._possible_passwords_file, 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Possible Password ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("ascii") + + "<==\n") + except UnicodeDecodeError: + pass + + # Return True if + if re.search(self.matchStrings, unencrypted_block): + if self._savepossiblematches: + try: + with open('possible_passwords.log', 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Found Password ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("ascii") + + "<==\n") + return True # Only return true if we can successfully decode the block in to ascii + + except UnicodeDecodeError: # Likely a false positive if we can't... + with open('possible_passwords.log', 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Found Likely False Positive Password (with non-Ascii characters in decrypted block) ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("utf-8", "ignore") + + "<==\n") + + return False + + def return_verified_password_or_false(self, passwords): # dogechain.info Main Password + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo, int)) \ + else self._return_verified_password_or_false_cpu(passwords) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, arg_passwords): # dogechain.info Main Password + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + for count, password in enumerate(passwords, 1): + #if self.decrypt(password): + # return password.decode("utf_8", "replace"), count + + passwordSHA256 = hashlib.sha256(password).digest() + passwordbase64 = base64.b64encode(passwordSHA256) + key = hashlib.pbkdf2_hmac('sha256', passwordbase64, self.salt, self._iter_count, 32) + + if self.aes_cipher == "AES-CBC": + decrypted_block = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self._encrypted_block) + + if self.check_decrypted_block(decrypted_block, password): + # Decrypt and dump the wallet if required + self.decrypt_wallet(password) + return password.decode("utf_8", "replace"), count + else: + try: + # For AES-GCM we need to decrypt the whole wallet, not just a block, + # also don't need to manually check the file contents as verification is part of the decryption + decrypted_block = AES.new(key, AES.MODE_GCM, self.iv).decrypt_and_verify(self._encrypted_wallet, self.aes_auth_tag) + return password.decode("utf_8", "replace"), count + except ValueError: + continue + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): # dogechain.info Main Password + + # Convert Unicode strings + passwords = map(lambda p: base64.b64encode(hashlib.sha256(p.encode("utf_8", "ignore")).digest()), arg_passwords) + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha256, passwords, self.salt, self._iter_count, 32) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, key) in enumerate(results, 1): + if self.aes_cipher == "AES-CBC": + decrypted_block = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self._encrypted_block) + if self.check_decrypted_block(decrypted_block, password): + # Decrypt and dump the wallet if required + self.decrypt_wallet(password) + return password.decode("utf_8", "replace"), count + else: + try: + decrypted_block = AES.new(key, AES.MODE_GCM, self.iv).decrypt_and_verify(self._encrypted_wallet, + self.aes_auth_tag) + return password.decode("utf_8", "replace"), count + except ValueError: + continue + + return False, count + +############### Metamask ############### + +@register_wallet_class +class WalletMetamask(object): + opencl_algo = -1 + + _savepossiblematches = True + _possible_passwords_file = "possible_passwords.log" + + _dump_privkeys_file = None + _dump_wallet_file = None + + _using_extract = False + + _mobileWallet = False + + def data_extract_id(): + return "mt" + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read().decode("utf-8","ignore").replace("\\","") + except: return False + return ("\"data\"" in walletdata and "\"iv\"" in walletdata and "\"salt\"" in walletdata) or ("\"lib\":\"original\"" in walletdata) # Metamask wallets have these three keys in the json (Other supported wallet times have one or the other, but not all three), metamask mobile has a lib:original string + + def __init__(self, iter_count, loading=False): + assert loading, 'use load_from_* to create a ' + self.__class__.__name__ + pbkdf2_library_name = load_pbkdf2_library().__name__ + aes_library_name = load_aes256_library().__name__ + global normalize + from unicodedata import normalize + self._iter_count = iter_count + self._passwords_per_second = 400000 if pbkdf2_library_name == "hashlib" else 100000 + self._passwords_per_second /= iter_count + if aes_library_name != "Crypto" and self._passwords_per_second > 2000: + self._passwords_per_second = 2000 + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + load_pbkdf2_library(warnings=False) + load_aes256_library(warnings=False) + global normalize + from unicodedata import normalize + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return max(int(round(self._passwords_per_second * seconds)), 1) + + # Load a metamask wallet file + @classmethod + def load_from_filename(cls, wallet_filename): + tryLoadJSONFile = False + try: + leveldb_records = ccl_leveldb.RawLevelDb(wallet_filename) + walletdata_list = [] + for record in leveldb_records.iterate_records_raw(): + # print(record) + # For LDB files and Ronin wallet log files + if b"vault" in record.key or b"encryptedVault" in record.key: + data = record.value.decode("utf-8", "ignore").replace("\\", "") + if "\"salt\"" in data: + if data in walletdata_list: + continue + + wallet_data = data[1:-1] + + if b"data" in record.key: + data = record.value.decode("utf-8", "ignore").replace("\\", "") + if "\"salt\"" in data: + walletStartText = "\"vault\"" + + wallet_data_start = data.lower().find(walletStartText) + + wallet_data_trimmed = data[wallet_data_start:] + + wallet_data_start = wallet_data_trimmed.find("\"data\"") + wallet_data_trimmed = wallet_data_trimmed[wallet_data_start - 1:] + + wallet_data_end = wallet_data_trimmed.find("}\"}") + wallet_data = wallet_data_trimmed[:wallet_data_end + 1] + + if wallet_data in walletdata_list: + continue + + walletdata_list.append(wallet_data) + + except ValueError: + tryLoadJSONFile = True + + except NameError: + print("\n********************************************************************************") + print("WARNING: Unable to load LevelDB module, likely due to it needing Python 3.8+") + print("********************************************************************************\n") + + tryLoadJSONFile = True + + + if tryLoadJSONFile: + # Try loading the wallet as a JSON file (If it has been copy/pasted from a browser) + with open(wallet_filename, "rb") as wallet_file: + wallet_data = wallet_file.read().decode("utf-8","ignore").replace("\\","") + + try: + wallet_json = json.loads(wallet_data) + + # The JSON data might be from a Metamask mobile wallet + except json.decoder.JSONDecodeError: + walletStartText = "vault" + wallet_data_start = wallet_data.lower().find(walletStartText) + wallet_data_trimmed = wallet_data[wallet_data_start:] + wallet_data_start = wallet_data_trimmed.find("cipher") + wallet_data_trimmed = wallet_data_trimmed[wallet_data_start - 2:] + wallet_data_end = wallet_data_trimmed.find("}") + wallet_data = wallet_data_trimmed[:wallet_data_end + 1] + wallet_json = json.loads(wallet_data) + + if "\"lib\":\"original\"" in wallet_data: + self = cls(5000, loading=True) + self.salt = wallet_json["salt"].encode() + self.encrypted_vault = base64.b64decode(wallet_json["cipher"]) + self.encrypted_block = base64.b64decode(wallet_json["cipher"])[:16] + self.iv = binascii.unhexlify(wallet_json["iv"]) + self._mobileWallet = True + elif "keyMetadata" in wallet_data: + hash_iterations = wallet_json["keyMetadata"]["params"]["iterations"] + self = cls(hash_iterations, loading=True) + self.salt = base64.b64decode(wallet_json["salt"]) + self.encrypted_vault = base64.b64decode(wallet_json["data"]) + self.encrypted_block = base64.b64decode(wallet_json["data"])[:16] + self.iv = base64.b64decode(wallet_json["iv"]) + else: + self = cls(10000, loading=True) + self.salt = base64.b64decode(wallet_json["salt"]) + self.encrypted_vault = base64.b64decode(wallet_json["data"]) + self.encrypted_block = base64.b64decode(wallet_json["data"])[:16] + self.iv = base64.b64decode(wallet_json["iv"]) + return self + + # Import extracted Metamask vault data necessary for password checking + @classmethod + def load_from_data_extract(cls, file_data): + # These are the same first encrypted block, iv and salt count retrieved above + extract_with_iterations = struct.calcsize("< 16s 16s 32s I 1?") + extract_without_iterations = struct.calcsize("< 16s 16s 32s 1?") + + if len(file_data) == extract_with_iterations: + encrypted_block, iv, salt, hash_iterations, isMobileWallet = struct.unpack(b"< 16s 16s 32s I 1?", file_data) + elif len(file_data) == extract_without_iterations: + encrypted_block, iv, salt, isMobileWallet = struct.unpack(b"< 16s 16s 32s 1?", file_data) + hash_iterations = 5000 if isMobileWallet else 10000 + else: + raise ValueError("unrecognized metamask extract format") + + self = cls(hash_iterations, loading=True) + if isMobileWallet: + self.salt = salt[:-8] + else: + self.salt = salt + self.encrypted_block = encrypted_block + self.iv = iv + self._mobileWallet = isMobileWallet + self.encrypted_vault = "" + self._using_extract = True + return self + + def difficulty_info(self): + if not self._mobileWallet: + return str(self._iter_count) + " PBKDF2-SHA256 iterations" + else: + return format(self._iter_count, ",") + " PBKDF2-SHA512 iterations" + + def init_logfile(self): + with open(self._possible_passwords_file, 'a') as logfile: + logfile.write( + "\n\n" + + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " New Recovery Started...\n" + + "This file contains passwords and blocks from passwords which `may` not exactly match those that " + "BTCRecover searches for by default. \n\n" + "Examples of successfully decrypted blocks will not just be random characters, " + "some examples of what correctly decryped blocks logs look like are:\n\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>[{\"type\":\"HD Key<==\n" + "Possible Password ==>btcr-test-password<== in Decrypted Block ==>\"{\\\"mnemonic\\\":<==\n" + "Possible Password ==>BTCR-test-passw0rd<== in Decrypted Block ==>{\"version\":\"v2\",<==\n" + "Note: The markers ==> and <== are not part of either your password or the decrypted block...\n\n" + "If the password works and was not correctly found, or your wallet detects a false positive, please report the decrypted block data at " + "https://github.com/3rdIteration/btcrecover/issues/\n\n") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print("* Note for Metamask (And related) Wallets... *") + print("* *") + print("* Writing all `possibly matched` and fully matched Passwords & *") + print("* Decrypted blocks to ", self._possible_passwords_file) + print("* This can be disabled with the --disablesavepossiblematches argument *") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *") + print() + + # A bit fragile because it assumes that some specific text is in the first encrypted block, + def check_decrypted_block(self, unencrypted_block, password): + if unencrypted_block[0] == ord("{") or unencrypted_block[0] == ord("[") or unencrypted_block[0] == ord('"'): + if b'"' in unencrypted_block[:4]: # If it really is a json wallet fragment, there will be a double quote in there within the first few characters... + try: + # Try to decode the decrypted block to ascii, this will pretty much always fail on anything other + # than the correct password + unencrypted_block.decode("ascii") + if self._savepossiblematches: + with open(self._possible_passwords_file, 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Possible Password ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("ascii") + + "<==\n") + except UnicodeDecodeError: + pass + + # Return True if + if re.search(b"\"type\"|version|mnemonic", unencrypted_block): + if self._savepossiblematches: + try: + with open('possible_passwords.log', 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Found Password ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("ascii") + + "<==\n") + return True # Only return true if we can successfully decode the block in to ascii + + except UnicodeDecodeError: # Likely a false positive if we can't... + with open('possible_passwords.log', 'a') as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + " Found Likely False Positive Password (with non-Ascii characters in decrypted block) ==>" + + password.decode("utf_8") + + "<== in Decrypted Block ==>" + + unencrypted_block.decode("utf-8", "ignore") + + "<==\n") + + return False + + def dump_wallet(self,key): + # If the dump wallet argument was used, just copy that path to dump-privkeys + if self._dump_wallet_file: + self._dump_privkeys_file = self._dump_wallet_file + + if self._dump_privkeys_file and not self._using_extract: + # Decrypt vault + if not self._mobileWallet: + decrypted_vault = AES.new(key, AES.MODE_GCM, nonce=self.iv).decrypt(self.encrypted_vault).decode("utf-8", "ignore") + else: + decrypted_vault = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self.encrypted_vault).decode("utf-8", "ignore") + + # Parse to JSON + decoder = json.JSONDecoder() + decrypted_vault_json, extrachars = decoder.raw_decode(decrypted_vault) + + try: + # Convert ascii list to string (Needed for some environments) + mnemonic = decrypted_vault_json[0]['data']['mnemonic'] + mnemonic = ''.join(map(chr, mnemonic)) + decrypted_vault_json[0]['data']['mnemonic'] = mnemonic + except TypeError: + pass # The conversion will fail if mnemonic is stored as a normal string + except KeyError: + pass # The conversion will fail if there are extra items in the wallet and it's a normal string (like with Binance Chain wallet) + + #Dump to file + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write(json.dumps(decrypted_vault_json)) + + def return_verified_password_or_false(self, passwords): # Metamask + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo, int)) \ + else self._return_verified_password_or_false_cpu(passwords) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, arg_passwords): # Metamask + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + for count, password in enumerate(passwords, 1): + if not self._mobileWallet: + key = hashlib.pbkdf2_hmac('sha256', password, self.salt, self._iter_count, 32) + decrypted_block = AES.new(key, AES.MODE_GCM, nonce=self.iv).decrypt(self.encrypted_block) + else: + key = hashlib.pbkdf2_hmac('sha512', password, self.salt, self._iter_count, 32) + decrypted_block = AES.new(key, AES.MODE_CBC, self.iv).decrypt(self.encrypted_block) + + + if self.check_decrypted_block(decrypted_block, password): + # This just dumps the wallet private keys (if required) + self.dump_wallet(key) + + return password.decode("utf_8", "replace"), count + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + if not self._mobileWallet: + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha256, passwords, self.salt, self._iter_count, 32) + else: + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512, passwords, self.salt, self._iter_count, 32) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, result) in enumerate(results, 1): + if not self._mobileWallet: + decrypted_block = AES.new(result, AES.MODE_GCM, nonce=self.iv).decrypt(self.encrypted_block) + else: + decrypted_block = AES.new(result, AES.MODE_CBC, self.iv).decrypt(self.encrypted_block) + + if self.check_decrypted_block(decrypted_block, password): + # This just dumps the wallet private keys + self.dump_wallet(result) + + return password.decode("utf_8", "replace"), count + + return False, count + +############### Bither ############### + +@register_wallet_class +class WalletBither(object): + opencl_algo = -1 + + def data_extract_id(): + return "bt" + + def passwords_per_seconds(self, seconds): + return max(int(round(self._passwords_per_second * seconds)), 1) + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + wallet_header = wallet_file.read(16) + # returns "maybe yes" or "definitely no" (mSIGNA wallets are also SQLite 3) + if wallet_header[0:-1] == b'SQLite format 3' and wallet_header[-1] == 0: + return None + else: + return False + + def __init__(self, loading = False): + if not hashlib_ripemd160_available: + print("Warning: Native RIPEMD160 not available via Hashlib, using Pure-Python (This will significantly reduce performance)") + + assert loading, 'use load_from_* to create a ' + self.__class__.__name__ + # loading crypto libraries is done in load_from_* + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global pylibscrypt, coincurve + from lib import pylibscrypt + + try: + import coincurve + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + load_aes256_library(warnings=False) + self.__dict__ = state + + # Load a Bither wallet file (the part of it we need) + @classmethod + def load_from_filename(cls, wallet_filename): + import sqlite3 + wallet_conn = sqlite3.connect(wallet_filename) + + is_bitcoinj_compatible = None + # Try to find an encrypted loose key first; they're faster to check + try: + wallet_cur = wallet_conn.execute("SELECT encrypt_private_key FROM addresses LIMIT 1") + key_data = wallet_cur.fetchone() + if key_data: + key_data = key_data[0] is_bitcoinj_compatible = True # if found, the KDF & encryption are bitcoinj compatible else: - e1 = "no encrypted keys present in addresses table" - except sqlite3.OperationalError as e1: - if str(e1).startswith(b"no such table"): - key_data = None - else: raise # unexpected error + e1 = "no encrypted keys present in addresses table" + except sqlite3.OperationalError as e1: + if str(e1).startswith("no such table"): + key_data = None + else: raise # unexpected error + + if not key_data: + # Newer wallets w/o loose keys have a password_seed table with a single row + try: + wallet_cur = wallet_conn.execute("SELECT password_seed FROM password_seed LIMIT 1") + key_data = wallet_cur.fetchone() + except sqlite3.OperationalError as e2: + raise ValueError("Not a Bither wallet: {}, {}".format(e1, e2)) # it might be an mSIGNA wallet + if not key_data: + error_exit("can't find an encrypted key or password seed in the Bither wallet") + key_data = key_data[0] + + # Create a bitcoinj wallet (which loads required libraries); we may or may not actually use it + bitcoinj_wallet = WalletBitcoinj(loading=True) + + # key_data is forward-slash delimited; it contains an optional pubkey hash, an encrypted key, an IV, a salt + key_data = key_data.split("/") + if len(key_data) == 1: + key_data = key_data.split(":") # old Bither wallets used ":" as the delimiter + pubkey_hash = key_data.pop(0) if len(key_data) == 4 else None + if len(key_data) != 3: + error_exit("unrecognized Bither encrypted key format (expected 3-4 slash-delimited elements, found {})" + .format(len(key_data))) + (encrypted_key, iv, salt) = key_data + encrypted_key = base64.b16decode(encrypted_key, casefold=True) + + # The first salt byte is optionally a flags byte + salt = base64.b16decode(salt, casefold=True) + if len(salt) == 9: + flags = ord(salt[0]) + salt = salt[1:] + else: + flags = 1 # this is the is_compressed flag; if not present it defaults to compressed + if len(salt) != 8: + error_exit("unexpected salt length ({}) in Bither wallet".format(len(salt))) + + # Return a WalletBitcoinj object to do the work if it's compatible with one (it's faster) + if is_bitcoinj_compatible: + if len(encrypted_key) != 48: + error_exit("unexpected encrypted key length in Bither wallet (expected 48, found {})" + .format(len(encrypted_key))) + # only need the last 2 encrypted blocks (half of which is padding) plus the salt (don't need the iv) + bitcoinj_wallet._part_encrypted_key = encrypted_key[-32:] + bitcoinj_wallet._scrypt_salt = salt + bitcoinj_wallet._scrypt_n = 16384 # Bither hardcodes the rest + bitcoinj_wallet._scrypt_r = 8 + bitcoinj_wallet._scrypt_p = 1 + return bitcoinj_wallet + + # Constuct and return a WalletBither object + else: + if not pubkey_hash: + error_exit("pubkey hash160 not present in Bither password_seed") + global coincurve + + try: + import coincurve + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + self = cls(loading=True) + self._passwords_per_second = bitcoinj_wallet._passwords_per_second # they're the same + self._iv_encrypted_key = base64.b16decode(iv, casefold=True) + encrypted_key + self._salt = salt # already hex decoded + self._pubkey_hash160 = base64.b16decode(pubkey_hash, casefold=True)[1:] # strip the bitcoin version byte + self._is_compressed = bool(flags & 1) # 1 is the is_compressed flag + return self + + # Import a Bither private key that was extracted by extract-bither-privkey.py + @classmethod + def load_from_data_extract(cls, privkey_data): + assert len(privkey_data) == 40, "extract-bither-privkey.py only extracts keys from bitcoinj compatible wallets" + bitcoinj_wallet = WalletBitcoinj(loading=True) + # The final 2 encrypted blocks + bitcoinj_wallet._part_encrypted_key = privkey_data[:32] + # The 8-byte salt and hardcoded scrypt parameters + bitcoinj_wallet._scrypt_salt = privkey_data[32:] + bitcoinj_wallet._scrypt_n = 16384 + bitcoinj_wallet._scrypt_r = 8 + bitcoinj_wallet._scrypt_p = 1 + return bitcoinj_wallet + + def difficulty_info(self): + return "scrypt N, r, p = 16384, 8, 1 + ECC" + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, passwords): # Bither + # Copy a few globals into local for a small speed boost + l_scrypt = pylibscrypt.scrypt + l_aes256_cbc_decrypt = aes256_cbc_decrypt + l_sha256 = hashlib.sha256 + hashlib_new = hashlib.new + iv_encrypted_key = self._iv_encrypted_key # 16-byte iv + encrypted_key + salt = self._salt + pubkey_from_secret = coincurve.PublicKey.from_valid_secret + cutils = coincurve.utils + + # Convert strings (lazily) to UTF-16BE bytestrings + passwords = map(lambda p: p.encode("utf_16_be", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + derived_aeskey = l_scrypt(password, salt, 16384, 8, 1, 32) # scrypt params are hardcoded except the salt + + # Decrypt and check if the last 16-byte block of iv_encrypted_key is valid PKCS7 padding + privkey_end = l_aes256_cbc_decrypt(derived_aeskey, iv_encrypted_key[-32:-16], iv_encrypted_key[-16:]) + padding_len = privkey_end[-1] + if not (1 <= padding_len <= 16 and privkey_end.endswith((chr(padding_len) * padding_len).encode())): + continue + privkey_end = privkey_end[:-padding_len] # trim the padding + + # Decrypt the rest of the encrypted_key, derive its pubkey, and compare it to what's expected + privkey = l_aes256_cbc_decrypt(derived_aeskey, iv_encrypted_key[:16], iv_encrypted_key[16:-16]) + privkey_end + # privkey can be any size, but libsecp256k1 expects a 256-bit key < the group's order: + privkey = cutils.int_to_bytes_padded( cutils.bytes_to_int(privkey) % cutils.GROUP_ORDER_INT ) + pubkey = pubkey_from_secret(privkey).format(self._is_compressed) + # Compute the hash160 of the public key, and check for a match + if ripemd160(l_sha256(pubkey).digest()) == self._pubkey_hash160: + password = password.decode("utf_16_be", "replace") + return password, count + + return False, count + + +############### BIP-38 ############### + +def public_key_to_address(pubkey, network_prefix): + ripemd160 = hash160(pubkey) + #print("Prefix:", network_prefix) + address = base58.b58encode_check(network_prefix + ripemd160) + #print("Address:", address) + return address + +def compress(pub): + x = pub[1:33] + y = pub[33:] + if int.from_bytes(y, byteorder='big') % 2: + prefix = bytes([0x03]) + else: + prefix = bytes([0x02]) + return prefix + x + +def private_key_to_public_key(s): + sk = ecdsa.SigningKey.from_string(s, curve=ecdsa.SECP256k1) + return (bytes([0x04]) + sk.verifying_key.to_string()) + +def bip38decrypt_ec(prefactor, encseedb, encpriv, has_compression_flag, has_lotsequence_flag, outputlotsequence=False, network_prefix='00'): + owner_entropy = encpriv[4:12] + enchalf1half1 = encpriv[12:20] + enchalf2 = encpriv[20:] + if has_lotsequence_flag: + lotsequence = owner_entropy[4:] + else: + lotsequence = False + if lotsequence is False: + passfactor = prefactor + else: + passfactor = double_sha256(prefactor + owner_entropy) + passfactor_int = int.from_bytes(passfactor, byteorder='big') + if passfactor_int == 0 or passfactor_int >= secp256k1_n: + if outputlotsequence: + return False, False, False + else: + return False + key = encseedb[32:] + aes = AESModeOfOperationECB(key) + tmp = aes.decrypt(enchalf2) + enchalf1half2_seedblastthird = int.from_bytes(tmp, byteorder='big') ^ int.from_bytes(encseedb[16:32], byteorder='big') + enchalf1half2_seedblastthird = enchalf1half2_seedblastthird.to_bytes(16, byteorder='big') + enchalf1half2 = enchalf1half2_seedblastthird[:8] + enchalf1 = enchalf1half1 + enchalf1half2 + seedb = aes.decrypt(enchalf1) + seedb = int.from_bytes(seedb, byteorder='big') ^ int.from_bytes(encseedb[:16], byteorder='big') + seedb = seedb.to_bytes(16, byteorder='big') + enchalf1half2_seedblastthird[8:] + assert len(seedb) == 24 + try: + factorb = double_sha256(seedb) + factorb_int = int.from_bytes(factorb, byteorder='big') + assert factorb_int != 0 + assert not factorb_int >= secp256k1_n + except: + if outputlotsequence: + return False, False, False + else: + return False + priv = ((passfactor_int * factorb_int) % secp256k1_n).to_bytes(32, byteorder='big') + pub = private_key_to_public_key(priv) + if has_compression_flag: + privcompress = bytes([0x1]) + pub = compress(pub) + else: + privcompress = bytes([]) + address = public_key_to_address(pub, network_prefix) + addrhex = bytearray(address, 'ascii') + addresshash = double_sha256(addrhex)[:4] + if addresshash == encpriv[0:4]: + priv = base58.b58encode_check(bytes([0x80]) + priv + privcompress) + if outputlotsequence: + if lotsequence is not False: + lotsequence = int(lotsequence, 16) + sequence = lotsequence % 4096 + lot = (lotsequence - sequence) // 4096 + return priv, lot, sequence + else: + return priv, False, False + else: + return priv + else: + if outputlotsequence: + return False, False, False + else: + return False + +def bip38decrypt_non_ec(scrypthash, encpriv, has_compression_flag, has_lotsequence_flag, outputlotsequence=False, network_prefix='00'): + msg1 = encpriv[4:20] + msg2 = encpriv[20:36] + key = scrypthash[32:] + aes = AESModeOfOperationECB(key) + msg1 = aes.decrypt(msg1) + msg2 = aes.decrypt(msg2) + half1 = int.from_bytes(msg1, byteorder='big') ^ int.from_bytes(scrypthash[:16], byteorder='big') + half2 = int.from_bytes(msg2, byteorder='big') ^ int.from_bytes(scrypthash[16:32], byteorder='big') + priv = half1.to_bytes(16, byteorder='big') + half2.to_bytes(16, byteorder='big') + priv_int = int.from_bytes(priv, byteorder='big') + if priv_int == 0 or priv_int >= secp256k1_n: + if outputlotsequence: + return False, False, False + else: + return False + pub = private_key_to_public_key(priv) + if has_compression_flag: + privcompress = bytes([0x1]) + pub = compress(pub) + else: + privcompress = bytes([]) + address = public_key_to_address(pub, network_prefix) + addrhex = bytearray(address, 'ascii') + addresshash = double_sha256(addrhex)[:4] + if addresshash == encpriv[0:4]: + priv = base58.b58encode_check(bytes([0x80]) + priv + privcompress) + if outputlotsequence: + return priv, False, False + else: + return priv + else: + if outputlotsequence: + return False, False, False + else: + return False + +def prefactor_to_passpoint(prefactor, has_lotsequence_flag, encpriv): + owner_entropy = encpriv[4:12] + if has_lotsequence_flag: + passfactor = double_sha256(prefactor + owner_entropy) + else: + passfactor = prefactor + passpoint = compress(private_key_to_public_key(passfactor)) + return passpoint + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletBIP38(object): + opencl_algo = -1 + + def __init__(self, enc_privkey, bip38_network = 'bitcoin'): + global pylibscrypt, ecdsa, double_sha256, hash160, normalize, base58, AESModeOfOperationECB, secp256k1_n + from lib import pylibscrypt + from lib.bitcoinlib.config.secp256k1 import secp256k1_n + from lib.bitcoinlib.encoding import double_sha256, hash160 + from lib.bitcoinlib import networks + from unicodedata import normalize + from lib.cashaddress import base58 + from lib.pyaes import AESModeOfOperationECB + + try: + import ecdsa + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load ecdsa module which is required for BIP38 wallets... You can install it with the command 'pip3 install ecdsa") + + + self.enc_privkey = base58.b58decode_check(enc_privkey) + assert len(self.enc_privkey) == 39 + + self.network = networks.Network(bip38_network) + + prefix = int.from_bytes(self.enc_privkey[:2], byteorder='big') + assert prefix == 0x0142 or prefix == 0x0143 + self.ec_multiplied = prefix == 0x0143 + + COMPRESSION_FLAGBYTES = [0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c, 0xe0, 0xe8, 0xf0, 0xf8] + LOTSEQUENCE_FLAGBYTES = [0x04, 0x0c, 0x14, 0x1c, 0x24, 0x2c, 0x34, 0x3c] + flagbyte = int.from_bytes(self.enc_privkey[2:3], byteorder='big') + self.has_compression_flag = flagbyte in COMPRESSION_FLAGBYTES + self.has_lotsequence_flag = flagbyte in LOTSEQUENCE_FLAGBYTES + + self.enc_privkey = self.enc_privkey[3:] + + if not self.ec_multiplied: + self.salt = self.enc_privkey[0:4] + else: + owner_entropy = self.enc_privkey[4:12] + self.salt = owner_entropy[:4] if self.has_lotsequence_flag else owner_entropy + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global pylibscrypt, ecdsa, double_sha256, hash160, normalize, base58, AESModeOfOperationECB, secp256k1_n + from lib import pylibscrypt + from lib.bitcoinlib.config.secp256k1 import secp256k1_n + from lib.bitcoinlib.encoding import double_sha256, hash160 + from lib.bitcoinlib import networks + from unicodedata import normalize + from lib.cashaddress import base58 + from lib.pyaes import AESModeOfOperationECB + + try: + import ecdsa + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load ecdsa module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return max(int(round(10 * seconds)), 1) + + def difficulty_info(self): + return "sCrypt N=14, r=8, p=8" + + def return_verified_password_or_false(self, passwords): # BIP38 Encrypted Private Keys + return self._return_verified_password_or_false_opencl(passwords) if (not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(passwords) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_opencl(self, arg_passwords): # BIP38 Encrypted Private Keys + l_scrypt = pylibscrypt.scrypt + + passwords = map(lambda p: normalize("NFC", p).encode("utf_8", "ignore"), arg_passwords) + + if not self.ec_multiplied: + clResult = self.opencl_algo.cl_scrypt(self.opencl_context_scrypt, passwords, 14, 3, 3, 64, self.salt) + passwords = map(lambda p: normalize("NFC", p).encode("utf_8", "ignore"), arg_passwords) + results = zip(passwords, clResult) + for count, (password, scrypthash) in enumerate(results, 1): + self.decrypted_privkey = bip38decrypt_non_ec(scrypthash, self.enc_privkey, self.has_compression_flag, self.has_lotsequence_flag, network_prefix = self.network.prefix_address) + if self.decrypted_privkey: + print("Decrypted BIP38 Key:", self.decrypted_privkey) + return password.decode("utf_8", "replace"), count + else: + clPrefactors = self.opencl_algo.cl_scrypt(self.opencl_context_scrypt, passwords, 14, 3, 3, 32, self.salt) + passpoints = map(lambda p: prefactor_to_passpoint(p, self.has_lotsequence_flag, self.enc_privkey), clPrefactors) + encseedbs = map(lambda p: l_scrypt(p, self.enc_privkey[0:12], 1024, 1, 1, 64), passpoints) + passwords = map(lambda p: normalize("NFC", p).encode("utf_8", "ignore"), arg_passwords) + results = zip(passwords, clPrefactors, encseedbs) + for count, (password, prefactor, encseedb) in enumerate(results, 1): + self.decrypted_privkey = bip38decrypt_ec(prefactor, encseedb, self.enc_privkey, self.has_compression_flag, self.has_lotsequence_flag, network_prefix = self.network.prefix_address) + if self.decrypted_privkey: + print("Decrypted BIP38 Key:", self.decrypted_privkey) + return password.decode("utf_8", "replace"), count + + return False, count + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): # BIP38 Encrypted Private Keys + l_scrypt = pylibscrypt.scrypt + + passwords = map(lambda p: normalize("NFC", p).encode("utf_8", "ignore"), passwords) + for count, password in enumerate(passwords, 1): + if not self.ec_multiplied: + scrypthash = l_scrypt(password, self.salt, 1 << 14, 8, 8, 64) + self.decrypted_privkey = bip38decrypt_non_ec(scrypthash, self.enc_privkey, self.has_compression_flag, self.has_lotsequence_flag, network_prefix = self.network.prefix_address) + if self.decrypted_privkey: + print("Decrypted BIP38 Key:", self.decrypted_privkey) + return password.decode("utf_8", "replace"), count + else: + prefactor = l_scrypt(password, self.salt, 1 << 14, 8, 8, 32) + passpoint = prefactor_to_passpoint(prefactor, self.has_lotsequence_flag, self.enc_privkey) + encseedb = l_scrypt(passpoint, self.enc_privkey[0:12], 1024, 1, 1, 64) + self.decrypted_privkey = bip38decrypt_ec(prefactor, encseedb, self.enc_privkey, self.has_compression_flag, self.has_lotsequence_flag, network_prefix = self.network.prefix_address) + if self.decrypted_privkey: + print("Decrypted BIP38 Key:", self.decrypted_privkey) + return password.decode("utf_8", "replace"), count + + return False, count + + +############### BIP-39 ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletBIP39(object): + opencl_algo = -1 + def __init__( + self, + mpk=None, + addresses=None, + address_limit=None, + addressdb_filename=None, + mnemonic=None, + lang=None, + path=None, + wallet_type="bip39", + is_performance=False, + force_p2sh=False, + checksinglexpubaddress=False, + force_p2tr=False, + force_bip44=False, + force_bip84=False, + disable_p2sh=False, + disable_p2tr=False, + disable_bip44=False, + disable_bip84=False, + ): + from . import btcrseed + + wallet_type = wallet_type.lower() + + wallet_type_names = [] + for cls, desc in btcrseed.selectable_wallet_classes: + wallet_type_name = cls.__name__.replace("Wallet", "", 1).lower() + if wallet_type_name == "electrum1": # Don't include Electrum 1 seeds in the list of options for passphrase recovery + continue + else: + wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower()) + if wallet_type_names[-1] == wallet_type: + btcrseed_cls = cls + if wallet_type_name == "electrum2": # Need to spell out that "extra words" are required and let the btcrseed class know... (This removes ambiguity around the seed length) + btcrseed_cls._passphrase_recovery = True + break + else: + wallet_type_names.sort() + sys.exit("--wallet-type must be one of: " + ", ".join(wallet_type_names)) + + global disable_security_warnings + btcrseed_cls.set_securityWarningsFlag(disable_security_warnings) + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library() + + # Create a btcrseed.WalletBIP39 object which will do most of the work; + # this also interactively prompts the user if not enough command-line options were included + if addressdb_filename: + from .addressset import AddressSet + print("Loading address database ...") + hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) + else: + hash160s = None + + self.btcrseed_wallet = btcrseed_cls.create_from_params( + mpk, + addresses, + address_limit, + hash160s, + path, + is_performance, + force_p2sh=force_p2sh, + checksinglexpubaddress=checksinglexpubaddress, + force_p2tr=force_p2tr, + force_bip44=force_bip44, + force_bip84=force_bip84, + disable_p2sh=disable_p2sh, + disable_p2tr=disable_p2tr, + disable_bip44=disable_bip44, + disable_bip84=disable_bip84, + ) + + if is_performance and not mnemonic: + mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + self.btcrseed_wallet.config_mnemonic(mnemonic, lang) + + # Verify that the entered mnemonic is valid + if not self.btcrseed_wallet.verify_mnemonic_syntax(btcrseed.mnemonic_ids_guess): + error_exit("one or more words are missing from the mnemonic") + skip_checksum = getattr(args, "skip_mnemonic_checksum", False) + if not skip_checksum and not self.btcrseed_wallet._verify_checksum(btcrseed.mnemonic_ids_guess): + error_exit("invalid mnemonic (the checksum is wrong)") + # Either the checksum was verified or the user chose to skip verification, so + # assume all mnemonic guesses will be processed: + self.btcrseed_wallet._checksum_ratio = 1 + + self._mnemonic = " ".join(btcrseed.mnemonic_ids_guess) + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return self.btcrseed_wallet.passwords_per_seconds(seconds) + + def difficulty_info(self): + return "2048 PBKDF2-SHA512 iterations + ECC" + + def return_verified_password_or_false(self, mnemonic_ids_list): # BIP39-Passphrase + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if (self.opencl and not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + if type(self.btcrseed_wallet) is btcrecover.btcrseed.WalletElectrum2: + derivation_salt = b"electrum" + password + else: + derivation_salt = b"mnemonic" + password + + seed_bytes = pbkdf2_hmac("sha512", self._mnemonic.encode(), derivation_salt, 2048) + + if type(self.btcrseed_wallet) is not btcrecover.btcrseed.WalletXLM: + seed_bytes = hmac.new(b"Bitcoin seed", seed_bytes, hashlib.sha512).digest() + + if self.btcrseed_wallet._verify_seed(seed_bytes): + return password.decode("utf_8", "replace"), count + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + salt_list = [] + for password in passwords: + if type(self.btcrseed_wallet) is btcrecover.btcrseed.WalletElectrum2: + salt_list.append(b"electrum" + password) + else: + salt_list.append(b"mnemonic" + password) + + clResult = self.opencl_algo.cl_pbkdf2_saltlist(self.opencl_context_pbkdf2_sha512, self._mnemonic.encode(), salt_list, 2048, 64) + + #Placeholder until OpenCL kernel can be patched to support this... + #clResult = [] + #for salt in salt_list: + # clResult.append(pbkdf2_hmac("sha512", self._mnemonic.encode(), salt, 2048)) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, result) in enumerate(results, 1): + if type(self.btcrseed_wallet) is not btcrecover.btcrseed.WalletXLM: + seed_bytes = hmac.new(b"Bitcoin seed", result, hashlib.sha512).digest() + else: + seed_bytes = result + + if self.btcrseed_wallet._verify_seed(seed_bytes): + return password.decode("utf_8", "replace"), count + + return False, count + +############### SLIP-39 ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletSLIP39(object): + opencl_algo = -1 + + def __init__(self, mpk = None, addresses = None, address_limit = None, addressdb_filename = None, + slip39_shares = None, lang = None, path = None, wallet_type = "bip39", is_performance = False): + + if not shamir_mnemonic_available: + print() + print("ERROR: Cannot import shamir-mnemonic which is required for SLIP39 wallets, install it via 'pip3 install shamir-mnemonic[cli]'") + exit() + + from . import btcrseed + + wallet_type = wallet_type.lower() + + wallet_type_names = [] + for cls, desc in btcrseed.selectable_wallet_classes: + wallet_type_name = cls.__name__.replace("Wallet", "", 1).lower() + if wallet_type_name not in ["ethereum", "bip39", "litecoin", "dogecoin", "bch", "dash", "ripple", "digibyte", "vertcoin"]: # SLIP39 implementation only supports common coins for now (Covers most of Trezor T) + continue + else: + wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower()) + if wallet_type_names[-1] == wallet_type: + btcrseed_cls = cls + break + else: + wallet_type_names.sort() + sys.exit("For SLIP39, --wallet-type must be one of: " + ", ".join(wallet_type_names)) + + global disable_security_warnings + btcrseed_cls.set_securityWarningsFlag(disable_security_warnings) + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library() + + # Create a btcrseed.WalletBIP39 object which will do most of the work; + # this also interactively prompts the user if not enough command-line options were included + if addressdb_filename: + from .addressset import AddressSet + print("Loading address database ...") + hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) + else: + hash160s = None + + self.btcrseed_wallet = btcrseed_cls.create_from_params( + mpk, addresses, address_limit, hash160s, path, is_performance) + + self.btcrseed_wallet._derivation_salts = [""] + + if is_performance and not slip39_shares: + slip39_shares = ["duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard"] + + print("\nLoading SLIP39 Shares") + + # Gather the SLIP39 Shares + # Implementation is a lightly modified version of the recover function from cli.py in the shamir-mnemonic repository + # https://github.com/trezor/python-shamir-mnemonic/blob/master/shamir_mnemonic/cli.py + # Licence in the Licences folder... + + recovery_state = shamir_mnemonic.recovery.RecoveryState() + + def print_group_status(idx: int) -> None: + group_size, group_threshold = recovery_state.group_status(idx) + group_prefix = style(recovery_state.group_prefix(idx), bold=True) + bi = style(str(group_size), bold=True) + if not group_size: + click.echo(f"{EMPTY} {bi} shares from group {group_prefix}") + else: + prefix = FINISHED if group_size >= group_threshold else INPROGRESS + bt = style(str(group_threshold), bold=True) + click.echo(f"{prefix} {bi} of {bt} shares needed from group {group_prefix}") + + def print_status() -> None: + bn = style(str(recovery_state.groups_complete()), bold=True) + bt = style(str(recovery_state.parameters.group_threshold), bold=True) + click.echo() + if recovery_state.parameters.group_count > 1: + click.echo(f"Completed {bn} of {bt} groups needed:") + for i in range(recovery_state.parameters.group_count): + print_group_status(i) + + while not recovery_state.is_complete(): + try: + if slip39_shares is not None and len(slip39_shares) > 0: + mnemonic_str = slip39_shares.pop() + else: + mnemonic_str = click.prompt("Enter a recovery share") + share = shamir_mnemonic.share.Share.from_mnemonic(mnemonic_str) + if not recovery_state.matches(share): + error("This mnemonic is not part of the current set. Please try again.") + continue + if share in recovery_state: + error("Share already entered.") + continue + + recovery_state.add_share(share) + print_status() + + except click.Abort: + return + except Exception as e: + error(str(e)) + + print("\nSLIP39 Shares Successfully Loaded\n") + + self.recovery_state = recovery_state + + self.btcrseed_wallet._checksum_ratio = 1 + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return 500 + + def difficulty_info(self): + return "40,000 PBKDF2-SHA256 iterations + ECC" + + def return_verified_password_or_false(self, mnemonic_ids_list): # BIP39-Passphrase + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if (self.opencl and not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + master_secret = self.recovery_state.recover(password) + + seed_bytes = hmac.new(b"Bitcoin seed", master_secret, hashlib.sha512).digest() + + if self.btcrseed_wallet._verify_seed(seed_bytes): + return password.decode("utf_8", "replace"), count + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + salt_list = [] + for password in passwords: + if type(self.btcrseed_wallet) is btcrecover.btcrseed.WalletElectrum2: + salt_list.append(b"electrum" + password) + else: + salt_list.append(b"mnemonic" + password) + + clResult = self.opencl_algo.cl_pbkdf2_saltlist(self.opencl_context_pbkdf2_sha512, self._mnemonic.encode(), salt_list, 2048, 64) + + #Placeholder until OpenCL kernel can be patched to support this... + #clResult = [] + #for salt in salt_list: + # clResult.append(pbkdf2_hmac("sha512", self._mnemonic.encode(), salt, 2048)) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, result) in enumerate(results, 1): + seed_bytes = hmac.new(b"Bitcoin seed", result, hashlib.sha512).digest() + if self.btcrseed_wallet._verify_seed(seed_bytes): + return password.decode("utf_8", "replace"), count + + return False, count + + +############### Cardano ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletCardano(WalletBIP39): + opencl_algo = -1 + + def __init__(self, addresses=None, addressdb_filename=None, + mnemonic=None, lang=None, path=None, is_performance=False): + from . import btcrseed + + btcrseed_cls = btcrecover.btcrseed.WalletCardano + + global disable_security_warnings + btcrseed_cls.set_securityWarningsFlag(disable_security_warnings) + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library() + + # Create a btcrseed.WalletBIP39 object which will do most of the work; + # this also interactively prompts the user if not enough command-line options were included + if addressdb_filename: + from .addressset import AddressSet + print("Loading address database ...") + hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) + else: + hash160s = None + + self.btcrseed_wallet = btcrseed_cls.create_from_params(addresses=addresses) + #addresses, hash160s, path, is_performance) + + if is_performance and not mnemonic: + mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + self.btcrseed_wallet.config_mnemonic(mnemonic, lang) + + # Verify that the entered mnemonic is valid + if not self.btcrseed_wallet.verify_mnemonic_syntax(btcrseed.mnemonic_ids_guess): + error_exit("one or more words are missing from the mnemonic") + skip_checksum = getattr(args, "skip_mnemonic_checksum", False) + if not skip_checksum and not self.btcrseed_wallet._verify_checksum(btcrseed.mnemonic_ids_guess): + error_exit("invalid mnemonic (the checksum is wrong)") + # Either the checksum was verified or the user chose to skip verification + self.btcrseed_wallet._checksum_ratio = 1 + + self._mnemonic = " ".join(btcrseed.mnemonic_ids_guess) + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global normalize, hmac + from unicodedata import normalize + import hmac + load_pbkdf2_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return self.btcrseed_wallet.passwords_per_seconds(seconds) + + def difficulty_info(self): + return "4096 PBKDF2-SHA512 iterations (2048 for Ledger)" + + def return_verified_password_or_false(self, mnemonic_ids_list): # BIP39-Passphrase + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if ( + self.opencl and not isinstance(self.opencl_algo, int)) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, arg_passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + + _derive_seed_list = self.btcrseed_wallet._derive_seed(self._mnemonic.split(" "), passwords) + + for derivation_type, derived_seed, salt in _derive_seed_list: + if self.btcrseed_wallet._verify_seed(derivation_type, derived_seed, salt): + + return salt.decode(), arg_passwords.index(salt.decode())+1 # found it + + return False, len(arg_passwords) + + def _return_verified_password_or_false_opencl(self, arg_passwords): + rootKeys = [] + + if self.btcrseed_wallet._check_ledger: + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + salt_list = [] + for password in passwords: + salt_list.append(b"mnemonic" + password) + mnemonic_list = [] + + clResult = self.opencl_algo.cl_pbkdf2_saltlist(self.opencl_context_pbkdf2_sha512_saltlist, self._mnemonic.encode(), + salt_list, 2048, 64) + + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + results = zip(passwords, clResult) + + for password, result in results: + rootKeys.append((password, "ledger", cardano.generateRootKey_Ledger(result))) + + if self.btcrseed_wallet._check_icarus or self.btcrseed_wallet._check_trezor: + if self.btcrseed_wallet._check_icarus: + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + entropy = cardano.mnemonic_to_entropy(words=self._mnemonic, + wordlist=self.btcrseed_wallet.current_wordlist, + langcode=self.btcrseed_wallet._lang, + trezorDerivation=False) + + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512, passwords, + entropy, 4096, 96) + + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + results = zip(passwords, clResult) + + for password, result in results: + rootKeys.append((password, "icarus", cardano.generateRootKey_Icarus(result))) + + if self.btcrseed_wallet._check_trezor: + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + entropy = cardano.mnemonic_to_entropy(words=self._mnemonic, + wordlist = self.btcrseed_wallet.current_wordlist, + langcode = self.btcrseed_wallet._lang, + trezorDerivation = True) + + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512, passwords, + entropy, 4096, 96) + + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), arg_passwords) + results = zip(passwords, clResult) + + for password, result in results: + rootKeys.append((password, "trezor", cardano.generateRootKey_Icarus(result))) + + for (password, derivationType, masterkey) in rootKeys: + if password == b"btcr-test-password": + print("Derivation Type:", derivationType) + (kL, kR), AP, cP = masterkey + print("Master Key") + print("kL:", kL.hex()) + print("kR:", kR.hex()) + print("AP:", AP.hex()) + print("cP:", cP.hex()) + + print("#Rootkeys:", len(rootKeys)) + + if self.btcrseed_wallet._verify_seed(derivationType, masterkey, password): + return password.decode(), arg_passwords.index(password.decode()) + 1 # found it + + return False, len(arg_passwords) + +############### Py_Crypto_HD_Wallet Based Wallets #################### +class WalletPyCryptoHDWallet(WalletBIP39): + def __init__(self, mpk = None, addresses = None, address_limit = None, addressdb_filename = None, + mnemonic = None, lang = None, path = None, wallet_type = "bip39", is_performance = False): + from . import btcrseed + + wallet_type = wallet_type.lower() + + wallet_type_names = [] + for cls, desc in btcrseed.selectable_wallet_classes: + wallet_type_name = cls.__name__.replace("Wallet", "", 1).lower() + wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower()) + if wallet_type_names[-1] == wallet_type: + btcrseed_cls = cls + break + else: + wallet_type_names.sort() + sys.exit("--wallet-type must be one of: " + ", ".join(wallet_type_names)) + + global disable_security_warnings + btcrseed_cls.set_securityWarningsFlag(disable_security_warnings) + global normalize, hmac + from unicodedata import normalize + import hmac + + # Create a btcrseed.WalletBIP39 object which will do most of the work; + # this also interactively prompts the user if not enough command-line options were included + if addressdb_filename: + from .addressset import AddressSet + print("Loading address database ...") + hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) + else: + hash160s = None + + self.btcrseed_wallet = btcrseed_cls.create_from_params( + mpk, addresses, address_limit, hash160s, path, is_performance) + + if is_performance and not mnemonic: + mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + self.btcrseed_wallet.config_mnemonic(mnemonic, lang) + + # Verify that the entered mnemonic is valid + if not self.btcrseed_wallet.verify_mnemonic_syntax(btcrseed.mnemonic_ids_guess): + error_exit("one or more words are missing from the mnemonic") + skip_checksum = getattr(args, "skip_mnemonic_checksum", False) + if not skip_checksum and not self.btcrseed_wallet._verify_checksum(btcrseed.mnemonic_ids_guess): + error_exit("invalid mnemonic (the checksum is wrong)") + # Either the checksum was verified or skipping was requested + self.btcrseed_wallet._checksum_ratio = 1 + + self._mnemonic = " ".join(btcrseed.mnemonic_ids_guess) + + def return_verified_password_or_false(self, mnemonic_ids_list): # BIP39-Passphrase + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if (self.opencl and not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + + if self.btcrseed_wallet._verify_seed(mnemonic = self._mnemonic.split(" "), passphrase = password): + return password.decode("utf_8", "replace"), count + + return False, count + + +############### Py_Crypto_HD_Wallet Based Wallets #################### +class WalletEthereumValidator(WalletBIP39): + + def __init__(self, mpk = None, addresses = None, address_limit = None, addressdb_filename = None, + mnemonic = None, lang = None, path = None, wallet_type = "EthereumValidator", is_performance = False): + from . import btcrseed + + btcrseed_cls = btcrseed.WalletEthereumValidator + + global disable_security_warnings + btcrseed_cls.set_securityWarningsFlag(disable_security_warnings) + global normalize, hmac + from unicodedata import normalize + import hmac + + # Create a btcrseed.WalletBIP39 object which will do most of the work; + # this also interactively prompts the user if not enough command-line options were included + if addressdb_filename: + from .addressset import AddressSet + print("Loading address database ...") + hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) + else: + hash160s = None + + self.btcrseed_wallet = btcrseed_cls.create_from_params( + mpk, addresses, address_limit, hash160s, path, is_performance) + + if is_performance and not mnemonic: + mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + self.btcrseed_wallet.config_mnemonic(mnemonic, lang) + + # Verify that the entered mnemonic is valid + if not self.btcrseed_wallet.verify_mnemonic_syntax(btcrseed.mnemonic_ids_guess): + error_exit("one or more words are missing from the mnemonic") + skip_checksum = getattr(args, "skip_mnemonic_checksum", False) + if not skip_checksum and not self.btcrseed_wallet._verify_checksum(btcrseed.mnemonic_ids_guess): + error_exit("invalid mnemonic (the checksum is wrong)") + # Either the checksum was verified or verification was skipped + self.btcrseed_wallet._checksum_ratio = 1 + + self._mnemonic = " ".join(btcrseed.mnemonic_ids_guess) + + def difficulty_info(self): + return "2048 PBKDF2-SHA512 iterations + BLS Derivation" + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, passwords): + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + passwords = map(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), passwords) + + for count, password in enumerate(passwords, 1): + + if self.btcrseed_wallet._verify_seed(mnemonic = self._mnemonic.split(" "), passphrase = password): + return password.decode("utf_8", "replace"), count + + return False, count + +############### Cadano Yoroi Wallet ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletYoroi(object): + opencl_algo = -1 + + def __init__(self, master_password = None, is_performance = False): + if is_performance: + # Just use a test master password, a modified version of the one from the unit tests + master_password = b'AA97F83D70BF83B32F8AC936AC32067653EE899979CCFDA67DFCBD535948C42A77DC' \ + b'9E719BF4ECE7DEB18BA3CD86F53C5EC75DE2126346A791250EC09E570E8241EE4F84' \ + b'0902CDFCBABC605ABFF30250BFF4903D0090AD1C645CEE4CDA53EA30BF419F4ECEA7' \ + b'909306EAE4B671FA7EEE3C2F65BE1235DEA4433F20B97F7BB8933521C657C61BBE6C' \ + b'031A7F1FEEF48C6978090ED009DD578A5382770A' + + self.master_password = master_password + + self.saltHex = master_password[:64] + self.nonceHex = master_password[64:88] + self.tagHex = master_password[88:120] + self.ciphertextHex = master_password[120:] + + self.salt = binascii.unhexlify(self.saltHex) + self.nonce = binascii.unhexlify(self.nonceHex) + self.tag = binascii.unhexlify(self.tagHex) + self.ciphertext = binascii.unhexlify(self.ciphertextHex) + + global emip3 + + try: + from lib.emip3 import emip3 + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load pycryptodome module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global emip3 + + try: + from lib.emip3 import emip3 + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load pycryptodome module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return 260 #This is the approximate performanc on an i7-8750H (The large number of PBKDF2 iterations means this wallet type would get a major boost from GPU acceleration) + + def difficulty_info(self): + return "19162 PBKDF2-SHA512 iterations + ChaCha20_Poly1305" + + def return_verified_password_or_false(self, mnemonic_ids_list): # Yoroi Cadano Wallet + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if (self.opencl and not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): # Yoroi Cadano Wallet + # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings + + for count, password in enumerate(passwords, 1): + try: + emip3.decryptWithPassword(password.encode(), self.master_password) + return password, count + except ValueError: #ChaCha20_Poly1305 throws a value error if the password is incorrect + pass + + return False, count + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_opencl(self, arg_passwords): # Yoroi Cadano Wallet + from Crypto.Cipher import ChaCha20_Poly1305 + + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512, passwords, self.salt, 19162, 32) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult) + + for count, (password, key) in enumerate(results, 1): + try: + cipher = ChaCha20_Poly1305.new(key=key, nonce=self.nonce) + plaintext = cipher.decrypt_and_verify(self.ciphertext, self.tag) + return password.decode("utf_8", "replace"), count + except ValueError: # ChaCha20_Poly1305 throws a value error if the password is incorrect + pass + + return False, count + +############### Brainwallet ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletBrainwallet(object): + opencl_algo = -1 + + # Dictionary containing all the hash suffixes for memwallet https://github.com/dvdbng/memwallet + hash_suffix = dict([ + ('bitcoin', 1), + ('litecoin', 2), + ('monero', 3), + ('ethereum', 4) + ]) + + def __init__(self, addresses = None, addressdb = None, check_compressed = True, check_uncompressed = True, + force_check_p2sh = False, isWarpwallet = False, salt = None, crypto = 'bitcoin', is_performance = False): + global hmac, coincurve, base58, pylibscrypt + if not hashlib_ripemd160_available: + print("Warning: Native RIPEMD160 not available via Hashlib, using Pure-Python (This will significantly reduce performance)") + import lib.pylibscrypt as pylibscrypt + from lib.cashaddress import base58 + import hmac + try: + import coincurve + except ModuleNotFoundError: + exit("\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + load_pbkdf2_library() + + if is_performance and not addresses: + addresses = "1D6asa4hPt9uomZZgwjKsEmdkSYsCkX542" + + self.compression_checks = [] + if check_compressed : self.compression_checks.append(True) + if check_uncompressed : self.compression_checks.append(False) + + self.isWarpwallet = isWarpwallet + + if salt: + self.salt = salt.encode() + else: + self.salt = b"" + + if crypto is None: + self.crypto = 'bitcoin' + else: + self.crypto = crypto + + from . import btcrseed + # Load addresses + from .addressset import AddressSet + + input_address_p2sh = False + input_address_standard = False + self.address_type_checks = [] + + if addresses: + self.hash160s = btcrseed.WalletBase._addresses_to_hash160s(addresses) + for address in addresses: + if address[0] == "3": + input_address_p2sh = True + else: + input_address_standard = True + else: + print("No Addresses Provided ... ") + print("Loading address database ...") + + if not addressdb: + print("No AddressDB specified, trying addresses.db") + addressdb = "addresses.db" + + self.hash160s = AddressSet.fromfile(open(addressdb, "rb")) + print("Loaded", len(self.hash160s), "addresses from database ...") + input_address_p2sh = True + input_address_standard = True + + if input_address_p2sh or force_check_p2sh: self.address_type_checks.append(True) + if input_address_standard and not (force_check_p2sh): self.address_type_checks.append(False) + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global hmac, coincurve, base58, pylibscrypt + import lib.pylibscrypt as pylibscrypt + from lib.cashaddress import base58 + import hmac + + try: + import coincurve + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + load_pbkdf2_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + if self.isWarpwallet: + return 1 + else: + return 120000 # CPU Processing is going to be in the order if 120,000 kP/s + + def difficulty_info(self): + if self.isWarpwallet: + return "sCrypt N=18, r=8, p = 1 + 65536 SHA-256 PBKDF2 Iterations" + else: + return "1 SHA-256 iteration" + + def return_verified_password_or_false(self, password_list): # Brainwallet + return self._return_verified_password_or_false_opencl(password_list) if (self.opencl and not isinstance(self.opencl_algo,int)) \ + else self._return_verified_password_or_false_cpu(password_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def _return_verified_password_or_false_cpu(self, passwords): # Brainwallet + l_sha256 = hashlib.sha256 + l_scrypt = pylibscrypt.scrypt + hashlib_new = hashlib.new + global pbkdf2_hmac + pubkey_from_secret = coincurve.PublicKey.from_valid_secret + + for count, password in enumerate(passwords, 1): + # Generate the initial Keypair + if self.isWarpwallet: + #print("S1 Params - Pass: ", password.encode() + (self.hash_suffix[self.crypto]).to_bytes(1, 'big'), + #" Salt: ", self.salt + (self.hash_suffix[self.crypto]).to_bytes(1, 'big')) + + # s1 = scrypt(key=(passphrase||), salt=(salt||), N=2^18, r=8, p=1, dkLen=32) + s1 = l_scrypt(password= password.encode() + (self.hash_suffix[self.crypto]).to_bytes(1, 'big'), + salt=self.salt + (self.hash_suffix[self.crypto]).to_bytes(1, 'big'), + N=1 << 18, r=8, p=1, olen=32) + + # s2 = pbkdf2(key=(passphrase||), salt=(salt||), c=2^16, dkLen=32, prf=HMAC_SHA256) + s2 = pbkdf2_hmac("sha256", password.encode() + (self.hash_suffix[self.crypto] + 1).to_bytes(1, 'big'), + salt=self.salt + (self.hash_suffix[self.crypto] + 1).to_bytes(1, 'big'), iterations= 1 << 16, dklen=32) + + #print("S2:", s2) + + # Privkey = s1 ⊕ s2 + privkey = bytes(x ^ y for x, y in zip(s1, s2)) + + #print("Privkey:", privkey.hex()) + + else: + privkey = (l_sha256(password.encode()).digest()) + + + # Convert the private keys to public keys and addresses for verification. + for isCompressed in self.compression_checks: + if isCompressed: + privcompress = bytes([0x1]) + else: + privcompress = bytes([]) + + pubkey = pubkey_from_secret(privkey).format(compressed = isCompressed) + + pubkey_hash160 = ripemd160(l_sha256(pubkey).digest()) + + for input_address_p2sh in self.address_type_checks: + if (input_address_p2sh): # Handle P2SH Segwit Address + WITNESS_VERSION = "\x00\x14" + witness_program = WITNESS_VERSION.encode() + pubkey_hash160 + + hash160 = ripemd160(l_sha256(witness_program).digest()) + else: + hash160 = pubkey_hash160 + + if hash160 in self.hash160s: + privkey_wif = base58.b58encode_check(bytes([0x80]) + privkey + privcompress) + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": NOTE Brainwallet Found using ", end="") + if isCompressed: + print("COMPRESSED address") + else: + print("UNCOMPRESSED address") + + #print("Password Found:", password, ", PrivKey:", privkey_wif, ", Compressed: ", isCompressed) + return password, count + + + return False, count + + def _return_verified_password_or_false_opencl(self, arg_passwords): # Brainwallet + l_sha256 = hashlib.sha256 + l_scrypt = pylibscrypt.scrypt + hashlib_new = hashlib.new + pubkey_from_secret = coincurve.PublicKey.from_valid_secret + + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + # Generate the initial Keypair + if self.isWarpwallet: + # There are actually issues with OpenCL_Brute sCrypt kernel so it doesn't work with the parameters required + # (The sCrypt library in opencl_brute is hardcoded at N= 15, + # the one contributed for the BIP38 fork is hardcoded at 14, but even changing this to 18 doesn't produce the + # correct results...) + # + # Have left the equivalent CPU code in for reference, verification and to help anyone else who wants to fix this... + # Just be aware that you will also need to uncomment the line in the opencl_helper file that creates the context + + # Prepare passwords with correc suffixes for both s1 and s2 + passwords_s1 = [] + passwords_s2 = [] + for password in passwords: + passwords_s1.append(password + (self.hash_suffix[self.crypto]).to_bytes(1, 'big')) + passwords_s2.append(password + (self.hash_suffix[self.crypto] + 1).to_bytes(1, 'big')) + + # s1 = scrypt(key=(passphrase||), salt=(salt||), N=2^18, r=8, p=1, dkLen=32) + + # CPU code for sCrypt. (Testing & Verification) + clResult_s1 = [] + for password in passwords_s1: + s1 = l_scrypt(password=password, + salt=self.salt + (self.hash_suffix[self.crypto]).to_bytes(1, 'big'), + N=1 << 18, r=8, p=1, olen=32) + #print("S1:", s1) + clResult_s1.append(s1) + + #print("ClResult (CPU):", clResult_s1) + + # OpenCL Code + # clResult_s1 = self.opencl_algo_2.cl_scrypt(ctx=self.opencl_context_scrypt, + # passwords=passwords_s1, + # N_value=18, r_value=8, p_value=1, desired_key_length=32, + # hex_salt=self.salt + (self.hash_suffix[self.crypto]).to_bytes(1, 'big')) + # + # print("ClResult (GPU):", clResult_s1) + + # s2 = pbkdf2(key=(passphrase||), salt=(salt||), c=2^16, dkLen=32, prf=HMAC_SHA256) + + # Placeholder CPU code for sCrypt. (Testing & Verification) + # clResult_s2 = [] + # for password in passwords_s2: + # + # s2 = pbkdf2_hmac("sha256", password, + # salt=self.salt + (self.hash_suffix[self.crypto] + 1).to_bytes(1, 'big'), + # iterations=1 << 16, dklen=32) + # + # clResult_s2.append(s2) + # + # print("ClResult (CPU):", clResult_s2) + + # OpenCL Code + clResult_s2 = self.opencl_algo_3.cl_pbkdf2(ctx=self.opencl_context_pbkdf2_sha256, passwordlist=passwords_s2, + salt=self.salt + (self.hash_suffix[self.crypto] + 1).to_bytes(1, 'big'), + iters=1 << 16, dklen=32) + + #print("ClResult (GPU):", clResult_s2) + + # Privkey = s1 ⊕ s2 + clResult_privkeys = [] + for s1, s2 in zip(clResult_s1, clResult_s2): + clResult_privkeys.append(bytes(x ^ y for x, y in zip(s1, s2))) + + else: + # Standard Sha256 Passphrase Hash + clResult_privkeys = self.opencl_algo.cl_sha256(self.opencl_context_sha256, passwords) + + # Convert the private keys to public keys and addresses for verification. + for isCompressed in self.compression_checks: + + pubkeys = [] + for privkey in clResult_privkeys: + + if isCompressed: + privcompress = bytes([0x1]) + else: + privcompress = bytes([]) + + pubkeys.append(pubkey_from_secret(privkey).format(compressed=isCompressed)) + + clResult_hashed_pubkey = self.opencl_algo.cl_sha256(self.opencl_context_sha256, pubkeys) + + hash160s_standard = [] + for hashed_pubkey in clResult_hashed_pubkey: + hash160s_standard.append(ripemd160(hashed_pubkey)) + + hash160s = [] + for pubkey_hash160 in hash160s_standard: + for input_address_p2sh in self.address_type_checks: + if (input_address_p2sh): # Handle P2SH Segwit Address + WITNESS_VERSION = "\x00\x14" + witness_program = WITNESS_VERSION.encode() + pubkey_hash160 + hash160s.append(ripemd160(l_sha256(witness_program).digest())) + else: + hash160s.append(pubkey_hash160) + + # This list is consumed, so recreated it and zip + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + results = zip(passwords, clResult_privkeys, hash160s) + + for count, (password, privkey, hash160) in enumerate(results, 1): + + # Compute the hash160 of the public key, and check for a match + if hash160 in self.hash160s: + privkey_wif = base58.b58encode_check(bytes([0x80]) + privkey + privcompress) + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": NOTE Brainwallet Found using ", + end="") + if isCompressed: + print("COMPRESSED address") + else: + print("UNCOMPRESSED address") + + #print("Password Found:", password, ", PrivKey:", privkey_wif, ", Compressed: ", isCompressed) + return password.decode("utf_8", "replace"), count + + return False, len(arg_passwords) + +############### Brainwallet ############### + +# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts +class WalletRawPrivateKey(object): + opencl_algo = -1 + + # Dictionary containing all the hash suffixes for memwallet https://github.com/dvdbng/memwallet + hash_suffix = dict([ + ('bitcoin', 1), + ('litecoin', 2), + ('monero', 3), + ('ethereum', 4) + ]) + + def __init__(self, addresses = None, addressdb = None, check_compressed = True, check_uncompressed = True, + force_check_p2sh = False, crypto = 'bitcoin', is_performance = False, correct_wallet_password = None): + global hmac, coincurve, base58 + if not hashlib_ripemd160_available: + print("Warning: Native RIPEMD160 not available via Hashlib, using Pure-Python (This will significantly reduce performance)") + + from lib.cashaddress import base58 + import hmac + try: + import coincurve + except ModuleNotFoundError: + exit("\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + load_pbkdf2_library() + + if is_performance and not addresses: + addresses = "1D6asa4hPt9uomZZgwjKsEmdkSYsCkX542" + + self.compression_checks = [] + if check_compressed : self.compression_checks.append(True) + if check_uncompressed : self.compression_checks.append(False) + + if crypto is None: + self.crypto = 'bitcoin' + else: + self.crypto = crypto.lower() + + from . import btcrseed + # Load addresses + from .addressset import AddressSet + + input_address_p2sh = False + input_address_standard = False + self.address_type_checks = [] + + self.hash160s = None + + if addresses: + if self.crypto == 'ethereum': + self.hash160s = btcrseed.WalletEthereum._addresses_to_hash160s(addresses) + input_address_standard = True + else: + self.hash160s = btcrseed.WalletBase._addresses_to_hash160s(addresses) + + for address in addresses: + if address[0] == "3": + input_address_p2sh = True + else: + input_address_standard = True + + if addressdb: + self.hash160s = AddressSet.fromfile(open(addressdb, "rb")) + print("Loaded", len(self.hash160s), "addresses from database ...") + input_address_p2sh = True + input_address_standard = True + + if input_address_p2sh or force_check_p2sh: self.address_type_checks.append(True) + if input_address_standard and not (force_check_p2sh): self.address_type_checks.append(False) + + self.correct_wallet_password = correct_wallet_password + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + global hmac, coincurve, base58, pylibscrypt + import lib.pylibscrypt as pylibscrypt + from lib.cashaddress import base58 + import hmac + + try: + import coincurve + except ModuleNotFoundError: + exit( + "\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + + load_pbkdf2_library(warnings=False) + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + return 60000 # CPU Processing is going to be in the order if 120,000 kP/s + + def difficulty_info(self): + return "1 SHA-256 iteration" + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, passwords): # Raw Privatekey + l_sha256 = hashlib.sha256 + hashlib_new = hashlib.new + pubkey_from_secret = coincurve.PublicKey.from_valid_secret + + for count, password in enumerate(passwords, 1): + # Generate the initial Keypair + #print("Key:", password, " Length:", len(password)) + + #Just swap a placeholder in for performance measurement + if ("Measure Performance" in password): password = "9cf68de3a8bec8f4649a5a1eb9340886a68a85c0c3ae722393ef3dd7a6c4da58" + + #Work out what kind of private key we are handling + WIFPrivKey = False + + if len(password) == 64: # Likely Hex Private Key (Don't need to do anything) + pass + + elif len(password) == 52 and password[0] in ["L","K"]: #Compressed Private Key + try: + # Check whether we have a valid Base58check first (This will weed out most invalid options) + base58.b58decode_check(password) + print("***NOTICE*** Found possible Private key (Has valid Base58Checksum):", password, "checking if supplied address matches...") + + # Convert to Hex for checking against supplied address + password = binascii.hexlify(base58.b58decode_check(password)[1:-1]) + WIFPrivKey = True + + except: + continue + + elif len(password) == 51 and password[0] == "5": # Uncompressed Private Key + try: + # Check whether we have a valid Base58check first (This will weed out most invalid options) + base58.b58decode_check(password) + print("***NOTICE*** Found possible Private key (Has valid Base58Checksum):", password, "checking if supplied address matches...") + + # Convert to Hex for checking against supplied address + password = binascii.hexlify(base58.b58decode_check(password)[1:]) + WIFPrivKey = True + except: + continue + elif len(password) == 58 and password[0:3] == "6Pn": # BIP38 Encrypted Private key + try: + # Check whether we have a valid Base58check first (This will weed out most invalid options) + base58.b58decode_check(password) + print("***NOTICE*** Found possible BIP38 Private key (Has valid Base58Checksum):", password, "checking if supplied address matches...") + + except: + continue + + if self.correct_wallet_password: + print("Attempting BIP38 decryption...") + test_wallet = WalletBIP38(enc_privkey = password) + correct_password, count = test_wallet.return_verified_password_or_false([self.correct_wallet_password]) + if correct_password: + password = binascii.hexlify(base58.b58decode_check(test_wallet.decrypted_privkey)[1:-1]) + else: + print("Incorrect decryption password supplied, unable to check further...") + continue + + WIFPrivKey = True + else: + print("No decryption password supplied, unable to check further...") + continue + else: # Unsupported Private Key + continue + + # Convert the private key from text to raw private key... + try: + privkey = binascii.unhexlify(password) + except binascii.Error as e: + message = "\n\nWarning: Invalid Private Key (Length or Characters)" + "\nKey Tried: " + password + "\nDouble check your tokenlist/passwordlist and ensure that only valid characters/wildcards are used..." + "\nSpecific Issue: " + str(e) + print(message) + continue + + if len(privkey) != 32: + message = "\n\nWarning: Invalid Private Key (Should be 64 Characters long)" + "\nKey Tried: " + password + "\nKey Length: " + str(len(privkey)*2) + "\nDouble check your tokenlist/passwordlist and ensure that only valid characters/wildcards are used..." + print(message) + continue + + # Don't spam this at performance measurement step + if password != "9cf68de3a8bec8f4649a5a1eb9340886a68a85c0c3ae722393ef3dd7a6c4da58": + if not self.hash160s: + if WIFPrivKey: + print("Warning: No addresses supplied, unable to check Base58 private key any further.. ") + else: + print("Warning: No addresses supplied combined with hexidicimal private key, this will never find a result... ") - if not key_data: - # Newer wallets w/o loose keys have a password_seed table with a single row - try: - wallet_cur = wallet_conn.execute(b"SELECT password_seed FROM password_seed LIMIT 1") - key_data = wallet_cur.fetchone() - except sqlite3.OperationalError as e2: - raise ValueError("Not a Bither wallet: {}, {}".format(e1, e2)) # it might be an mSIGNA wallet - if not key_data: - error_exit("can't find an encrypted key or password seed in the Bither wallet") - key_data = key_data[0] - # Create a bitcoinj wallet (which loads required libraries); we may or may not actually use it - bitcoinj_wallet = WalletBitcoinj(loading=True) + # Convert the private keys to public keys and addresses for verification. + for isCompressed in self.compression_checks: - # key_data is forward-slash delimited; it contains an optional pubkey hash, an encrypted key, an IV, a salt - key_data = key_data.split(b"/") - if len(key_data) == 1: - key_data = key_data.split(b":") # old Bither wallets used ":" as the delimiter - pubkey_hash = key_data.pop(0) if len(key_data) == 4 else None - if len(key_data) != 3: - error_exit("unrecognized Bither encrypted key format (expected 3-4 slash-delimited elements, found {})" - .format(len(key_data))) - (encrypted_key, iv, salt) = key_data - encrypted_key = base64.b16decode(encrypted_key, casefold=True) + if isCompressed: + privcompress = bytes([0x1]) + else: + privcompress = bytes([]) - # The first salt byte is optionally a flags byte - salt = base64.b16decode(salt, casefold=True) - if len(salt) == 9: - flags = ord(salt[0]) - salt = salt[1:] - else: - flags = 1 # this is the is_compressed flag; if not present it defaults to compressed - if len(salt) != 8: - error_exit("unexpected salt length ({}) in Bither wallet".format(len(salt))) + # Sometimes it's possible that a privatekey (with a valid checksum) will still be invalid in terms of generating a usable address + try: + pubkey = pubkey_from_secret(privkey).format(compressed = isCompressed) + except Exception as e: + print("Exception for Privkey: ", password, " : ", e) + continue - # Return a WalletBitcoinj object to do the work if it's compatible with one (it's faster) - if is_bitcoinj_compatible: - if len(encrypted_key) != 48: - error_exit("unexpected encrypted key length in Bither wallet (expected 48, found {})" - .format(len(encrypted_key))) - # only need the last 2 encrypted blocks (half of which is padding) plus the salt (don't need the iv) - bitcoinj_wallet._part_encrypted_key = encrypted_key[-32:] - bitcoinj_wallet._scrypt_salt = salt - bitcoinj_wallet._scrypt_n = 16384 # Bither hardcodes the rest - bitcoinj_wallet._scrypt_r = 8 - bitcoinj_wallet._scrypt_p = 1 - return bitcoinj_wallet + if self.crypto == 'ethereum': + pubkey_hash160 = keccak(pubkey[1:])[-20:] + else: + pubkey_hash160 = ripemd160(l_sha256(pubkey).digest()) - # Constuct and return a WalletBither object - else: - if not pubkey_hash: - error_exit("pubkey hash160 not present in Bither password_seed") - global coincurve - import coincurve - self = cls(loading=True) - self._passwords_per_second = bitcoinj_wallet._passwords_per_second # they're the same - self._iv_encrypted_key = base64.b16decode(iv, casefold=True) + encrypted_key - self._salt = salt # already hex decoded - self._pubkey_hash160 = base64.b16decode(pubkey_hash, casefold=True)[1:] # strip the bitcoin version byte - self._is_compressed = bool(flags & 1) # 1 is the is_compressed flag - return self + for input_address_p2sh in self.address_type_checks: + if (input_address_p2sh): # Handle P2SH Segwit Address + WITNESS_VERSION = "\x00\x14" + witness_program = WITNESS_VERSION.encode() + pubkey_hash160 + hash160 = ripemd160(l_sha256(witness_program).digest()) + else: + hash160 = pubkey_hash160 + + if hash160 in self.hash160s: + privkey_wif = base58.b58encode_check(bytes([0x80]) + privkey + privcompress) + #if self.crypto == 'bitcoin': + # print("\n* * * * *\nPrivkey Found (HEX):", password, ", PrivKey (WIF):", privkey_wif, ", Compressed: ", isCompressed, "\n* * * * *") + if WIFPrivKey: + return privkey_wif, count + else: + return password, count - # Import a Bither private key that was extracted by extract-bither-privkey.py + + return False, count + +############### Ethereum UTC Keystore ############### + +@register_wallet_class +class WalletEthKeystore(object): + opencl_algo = -1 + + _dump_privkeys_file = None + + # This just dumps the wallet private keys + def dump_privkeys(self, correct_password): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Private Keys (For copy/paste) are below...\n") + key = eth_keyfile.decode_keyfile_json(self.wallet_json, correct_password) + logfile.write("0x" + key.hex()) + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + except: return False + return (b"cipherparams" in walletdata and b"kdfparams" in walletdata and b"imTokenMeta" not in walletdata) # These are fairly distinctive for Eth UTC v3 files + + def __init__(self, loading=False): + assert loading, 'use load_from_* to create a ' + self.__class__.__name__ + + def __setstate__(self, state): + # (re-)load the required libraries after being unpickled + self.__dict__ = state + + def passwords_per_seconds(self, seconds): + if self.wallet_json['crypto']['kdf'] == 'scrypt': + return 8 + + if self.wallet_json['crypto']['kdf'] == 'pbkdf2': + return 6 + + # Load a Eth Keystore file @classmethod - def load_from_data_extract(cls, privkey_data): - assert len(privkey_data) == 40, "extract-bither-privkey.py only extracts keys from bitcoinj compatible wallets" - bitcoinj_wallet = WalletBitcoinj(loading=True) - # The final 2 encrypted blocks - bitcoinj_wallet._part_encrypted_key = privkey_data[:32] - # The 8-byte salt and hardcoded scrypt parameters - bitcoinj_wallet._scrypt_salt = privkey_data[32:] - bitcoinj_wallet._scrypt_n = 16384 - bitcoinj_wallet._scrypt_r = 8 - bitcoinj_wallet._scrypt_p = 1 - return bitcoinj_wallet + def load_from_filename(cls, wallet_filename): + if not module_eth_keyfile_available: + print("eth-keyfile module is required for Eth Keystores (it can normally be installed with the command: pip3 install eth-keyfile)") + exit() + wallet_json = eth_keyfile.load_keyfile(wallet_filename) + self = cls(loading=True) + self.wallet_json = wallet_json + return self def difficulty_info(self): - return "scrypt N, r, p = 16384, 8, 1 + ECC" + if self.wallet_json['crypto']['kdf'] == 'scrypt': + return "Scrypt" + \ + " N=" + str(int(math.log2(self.wallet_json['crypto']['kdfparams']['n']))) + \ + " R=" + str(self.wallet_json['crypto']['kdfparams']['r']) + \ + " P=" + str(self.wallet_json['crypto']['kdfparams']['p']) + + if self.wallet_json['crypto']['kdf'] == 'pbkdf2': + return str(self.wallet_json['crypto']['kdfparams']['c']) + " PBKDF2 Iterations" # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): - # Copy a few globals into local for a small speed boost - l_scrypt = pylibscrypt.scrypt - l_aes256_cbc_decrypt = aes256_cbc_decrypt - l_sha256 = hashlib.sha256 - hashlib_new = hashlib.new - iv_encrypted_key = self._iv_encrypted_key # 16-byte iv + encrypted_key - salt = self._salt - pubkey_from_secret = coincurve.PublicKey.from_valid_secret - cutils = coincurve.utils - - # Convert strings (lazily) to UTF-16BE bytestrings - passwords = itertools.imap(lambda p: p.encode("utf_16_be", "ignore"), passwords) + def return_verified_password_or_false(self, arg_passwords): # Ethereum Keystore (UTC) File + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) for count, password in enumerate(passwords, 1): - derived_aeskey = l_scrypt(password, salt, 16384, 8, 1, 32) # scrypt params are hardcoded except the salt + try: + eth_keyfile.decode_keyfile_json(self.wallet_json,password) + except ValueError: # Throws a value error if MAC mismatches + continue - # Decrypt and check if the last 16-byte block of iv_encrypted_key is valid PKCS7 padding - privkey_end = l_aes256_cbc_decrypt(derived_aeskey, iv_encrypted_key[-32:-16], iv_encrypted_key[-16:]) - padding_len = ord(privkey_end[-1]) - if not (1 <= padding_len <= 16 and privkey_end.endswith(chr(padding_len) * padding_len)): + if self._dump_privkeys_file: + self.dump_privkeys(password) + return password.decode("utf_8", "replace"), count + + return False, count + +############### Imtoken Keystore ############### + +# Imtoken keystores are basically a modified Eth keystore format + +@register_wallet_class +class WalletImtokenKeystore(WalletEthKeystore): + opencl_algo = -1 + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + except: return False + return (b"imTokenMeta" in walletdata) + + @classmethod + def load_from_filename(cls, wallet_filename): + if not module_eth_keyfile_available: + print("eth-keyfile module is required for Eth Keystores (it can normally be installed with the command: pip3 install eth-keyfile)") + exit() + wallet_json = eth_keyfile.load_keyfile(wallet_filename) + wallet_json["version"] = 3 + self = cls(loading=True) + self.wallet_json = wallet_json + return self + + def passwords_per_seconds(self, seconds): + if self.wallet_json['crypto']['kdf'] == 'pbkdf2': + return 60 + + def dump_privkeys(self, correct_password): + with open(self._dump_privkeys_file, 'a') as logfile: + try: + key = eth_keyfile.decode_keyfile_json(self.wallet_json, correct_password) + logfile.write("BIP39 Root Key (For copy/paste) is below...\n") + logfile.write(key.decode()) + except: + print("WARNING KEY DUMP FAILED: Found correct password but can't decode wallet XPRV, try running BTCRecover with identity.json from the imtoken data folder") + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, arg_passwords): # Ethereum Keystore (UTC) File + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + for count, password in enumerate(passwords, 1): + try: + eth_keyfile.decode_keyfile_json(self.wallet_json,password) + except ValueError: # Throws a value error if MAC mismatches continue - privkey_end = privkey_end[:-padding_len] # trim the padding - # Decrypt the rest of the encrypted_key, derive its pubkey, and compare it to what's expected - privkey = l_aes256_cbc_decrypt(derived_aeskey, iv_encrypted_key[:16], iv_encrypted_key[16:-16]) + privkey_end - # privkey can be any size, but libsecp256k1 expects a 256-bit key < the group's order: - privkey = cutils.int_to_bytes_padded( cutils.bytes_to_int(privkey) % cutils.GROUP_ORDER_INT ) - pubkey = pubkey_from_secret(privkey).format(self._is_compressed) - # Compute the hash160 of the public key, and check for a match - if hashlib_new("ripemd160", l_sha256(pubkey).digest()).digest() == self._pubkey_hash160: - password = password.decode("utf_16_be", "replace") - return password.encode("ascii", "replace") if tstr == str else password, count + if self._dump_privkeys_file: + self.dump_privkeys(password) + return password.decode("utf_8", "replace"), count return False, count +############### btc.com Wallet (blocktrail wallet) ############### +# Used to recover the wallet password to enable recovery from the btc.com recovery PDF. +# The logic and variable names here follow the code/logic from https://github.com/blocktrail/wallet-recovery-tool/ +# as closely as possible -############### BIP-39 ############### +@register_wallet_class +class Walletbtc_com(object): + opencl_algo = -1 + + def decode(self, mnemonic): + paddingDummy = 129 # Because salts with length > 128 should be forbidden + mnemo = Mnemonic("english") + decoded_data = mnemo.to_entropy(mnemonic) + + padFinish = 0 + while True: + if decoded_data[padFinish] == paddingDummy: + padFinish = padFinish + 1 + else: + break -# @register_wallet_class - not a "registered" wallet since there are no wallet files nor extracts -class WalletBIP39(object): + return decoded_data[padFinish:] - def __init__(self, mpk = None, addresses = None, address_limit = None, addressdb_filename = None, - mnemonic = None, lang = None, path = None, wallet_type = "bitcoin", is_performance = False): - from . import btcrseed - if wallet_type == "bitcoin": - btcrseed_cls = btcrseed.WalletBIP39 - elif wallet_type == "ethereum": - if addressdb_filename: - error_exit("can't use an address database with Ethereum wallets") - btcrseed_cls = btcrseed.WalletEthereum - else: - error_exit("--wallet-type must be one of: bitcoin, ethereum") + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + except: return False + return (b"passwordEncryptedSecretMnemonic" in walletdata) # A pretty unique tag in - global normalize, hmac - from unicodedata import normalize - import hmac - load_pbkdf2_library() + @classmethod + def load_from_filename(cls, wallet_filename): + self = cls() - # Create a btcrseed.WalletBIP39 object which will do most of the work; - # this also interactively prompts the user if not enough command-line options were included - if addressdb_filename: - from .addressset import AddressSet - print("Loading address database ...") - hash160s = AddressSet.fromfile(open(addressdb_filename, "rb")) - else: - hash160s = None - self.btcrseed_wallet = btcrseed_cls.create_from_params( - mpk, addresses, address_limit, hash160s, path, is_performance) - if is_performance and not mnemonic: - mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" - self.btcrseed_wallet.config_mnemonic(mnemonic, lang) + # Open a JSON file containing the wallet data, as parsed by the browser based btc.com wallet recovery tool + # (You can manually create this file, all BTCRecover uses is the password encrypted secret mnemonic, + # you can leave the rest out to avoid exposing the actal wallet private keys to the system running BTCRecover) + with open(wallet_filename) as wallet_file: + wallet_json = json.load(wallet_file) - # Verify that the entered mnemonic is valid - if not self.btcrseed_wallet.verify_mnemonic_syntax(btcrseed.mnemonic_ids_guess): - error_exit("one or more words are missing from the mnemonic") - if not self.btcrseed_wallet._verify_checksum(btcrseed.mnemonic_ids_guess): - error_exit("invalid mnemonic (the checksum is wrong)") - # We just verified the mnemonic checksum is valid, so 100% of the guesses will also be valid: - self.btcrseed_wallet._checksum_ratio = 1 + PasswordEncryptedSecret = wallet_json['passwordEncryptedSecretMnemonic'].strip() + passwordEncryptedSecretMnemonic = self.decode(PasswordEncryptedSecret) - self._mnemonic = b" ".join(btcrseed.mnemonic_ids_guess) + # Save the salt length + self.saltLen = passwordEncryptedSecretMnemonic[0] + passwordEncryptedSecretMnemonic = passwordEncryptedSecretMnemonic[1:] - def __setstate__(self, state): - # (re-)load the required libraries after being unpickled - global normalize, hmac - from unicodedata import normalize - import hmac - load_pbkdf2_library(warnings=False) - self.__dict__ = state + # Save the salt + self.salt = passwordEncryptedSecretMnemonic[:self.saltLen] + passwordEncryptedSecretMnemonic = passwordEncryptedSecretMnemonic[self.saltLen:] + + # Save the iterations # + self.iterations = int.from_bytes(passwordEncryptedSecretMnemonic[:4], 'little') + passwordEncryptedSecretMnemonic = passwordEncryptedSecretMnemonic[4:] + + # Construct and save the header + self.header = self.decode(PasswordEncryptedSecret)[:1 + self.saltLen + 4] + + # Save the IV + self.iv = passwordEncryptedSecretMnemonic[:16] + passwordEncryptedSecretMnemonic = passwordEncryptedSecretMnemonic[16:] + + # Save the cyphertext and tag + self.ct_t = passwordEncryptedSecretMnemonic + return self def passwords_per_seconds(self, seconds): - return self.btcrseed_wallet.passwords_per_seconds(seconds) + # Haven't worked this out, but this is a ballpark figure + return 200 - def difficulty_info(self): - return "2048 PBKDF2-SHA512 iterations + ECC" + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password + # is correct return it, else return False for item 0; return a count of passwords checked for item 1 + def return_verified_password_or_false(self, arg_passwords): # btc.com wallet password + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) + + for count, password in enumerate(passwords, 1): + key = hashlib.pbkdf2_hmac('sha512', password, self.salt, self.iterations, 32) + cipher = AES.new(key, AES.MODE_GCM, self.iv) + cipher.update(self.header) + try: + decrypted_data = cipher.decrypt_and_verify(self.ct_t[:32], self.ct_t[32:]) + except ValueError: # Throws a value error if MAC mismatches + continue + + return password.decode("utf_8", "replace"), count + + return False, count + + +############### ToastWallet ############### +@register_wallet_class +class Wallettoastwallet(object): + opencl_algo = -1 + _dump_privkeys_file = None + + def __init__(self, loading = False): + if not nacl_available: + exit("Toastwallet Requires the nacl module, this can be installed via pip3 install pynacl") + + def dump_privkeys(self, hash1): + with open(self._dump_privkeys_file, 'a') as logfile: + logfile.write("Below are all of the accounts in the wallet, these private keys can be imported into XRP wallets like Xaman\nNickname Address Privkey\n") + for account in self.wallet_json['accounts'].keys(): + addr_salt = binascii.unhexlify( + self.wallet_json['accounts'][account]['ppsalt'].encode())[4:] + addr_secret = binascii.unhexlify( + self.wallet_json['accounts'][account]['ppsecret'].encode())[4:] + + ppsecretk32key = nacl.pwhash.scrypt.kdf(size=32, password=hash1, salt=addr_salt, opslimit=4, + memlimit=33554432) + + box = nacl.secret.SecretBox(ppsecretk32key) + + secret_clear = box.decrypt(addr_secret) + + logfile.write(self.wallet_json['accounts'][account]['nickname'] + " " + account + " " + bytes.fromhex(secret_clear.hex()).decode() + "\n") + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + try: + walletdata = wallet_file.read() + except: return False + return (b"ppdata" in walletdata) and (b"rpdata" in walletdata) + + @classmethod + def load_from_filename(cls, wallet_filename): + self = cls() + + # Open a JSON file containing the wallet data, as parsed by the browser based btc.com wallet recovery tool + # (You can manually create this file, all BTCRecover uses is the password encrypted secret mnemonic, + # you can leave the rest out to avoid exposing the actal wallet private keys to the system running BTCRecover) + with open(wallet_filename) as wallet_file: + wallet_file.seek(8) # Skip over the wallet fingerprint at the start + wallet_json = json.load(wallet_file) + + self.salt1 = binascii.unhexlify(wallet_json['ppdata']['salt1'].encode())[4:] + self.salt2 = binascii.unhexlify(wallet_json['ppdata']['salt2'].encode())[4:] + self.target_hash = binascii.unhexlify(wallet_json['ppdata']['hash'].encode())[4:] + self.wallet_json = wallet_json + + return self + + def passwords_per_seconds(self, seconds): + # Haven't worked this out, but this is a ballpark figure + return 2000 # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password # is correct return it, else return False for item 0; return a count of passwords checked for item 1 - def return_verified_password_or_false(self, passwords): - # Convert Unicode strings (lazily) to normalized UTF-8 bytestrings - if tstr == unicode: - passwords = itertools.imap(lambda p: normalize("NFKD", p).encode("utf_8", "ignore"), passwords) + def return_verified_password_or_false(self, arg_passwords): # toastwallet wallet password + # Convert Unicode strings (lazily) to UTF-8 bytestrings + passwords = map(lambda p: p.encode("utf_8", "ignore"), arg_passwords) for count, password in enumerate(passwords, 1): - seed_bytes = pbkdf2_hmac(b"sha512", self._mnemonic, b"mnemonic" + password, 2048) - seed_bytes = hmac.new(b"Bitcoin seed", seed_bytes, hashlib.sha512).digest() - if self.btcrseed_wallet._verify_seed(seed_bytes): - return password if tstr == str else password.decode("utf_8", "replace"), count + hash1 = nacl.pwhash.scrypt.kdf(size=16, password=password, salt=self.salt1, opslimit=4, memlimit=33554432) + hash2 = nacl.pwhash.scrypt.kdf(size=16, password=hash1, salt=self.salt2, opslimit=4, memlimit=33554432) + + if self.target_hash == hash2: + # If we aren't dumping these files, then just return... + if self._dump_privkeys_file: + # This just dumps the wallet private keys (if required) + self.dump_privkeys(hash1) + + return password.decode("utf_8", "replace"), count return False, count @@ -2454,14 +5593,15 @@ def load_aes256_library(force_purepython = False, warnings = True): return Crypto # just so the caller can check which version was loaded except ImportError: if warnings and not missing_pycrypto_warned: - print(prog+": warning: can't find PyCrypto, using aespython instead", file=sys.stderr) + print("Warning: Can't find PyCrypto, using aespython instead", file=sys.stderr) missing_pycrypto_warned = True # This version is attributed to GitHub user serprex; please see the aespython # README.txt for more information. It measures over 30x faster than the more # common "slowaes" package (although it's still 30x slower than the PyCrypto) # - import aespython + + from lib import aespython expandKey = aespython.key_expander.expandKey AESCipher = aespython.aes_cipher.AESCipher def aes256_decrypt_factory(BlockMode): @@ -2470,9 +5610,10 @@ def aes256_decrypt(key, iv, ciphertext): stream_cipher = BlockMode(block_cipher, 16) stream_cipher.set_iv(bytearray(iv)) plaintext = bytearray() - for i in xrange(0, len(ciphertext), 16): + for i in range(0, len(ciphertext), 16): plaintext.extend( stream_cipher.decrypt_block(bytearray(ciphertext[i:i+16])) ) # input must be a list - return str(plaintext) + return plaintext + return aes256_decrypt aes256_cbc_decrypt = aes256_decrypt_factory(aespython.CBCMode) aes256_ofb_decrypt = aes256_decrypt_factory(aespython.OFBMode) @@ -2480,7 +5621,7 @@ def aes256_decrypt(key, iv, ciphertext): # Creates a key derivation function (in global namespace) named pbkdf2_hmac() using either the -# hashlib.pbkdf2_hmac from Python 2.7.8+ if it's available, or a pure python library (passlib). +# hashlib.pbkdf2_hmac from Python if it's available, or a pure python library (passlib). # The created function takes a hash name, two bytestring arguments and two integer arguments: # hash_name (e.g. b"sha1"), password, salt, iter_count, key_len (the length of the returned key) missing_pbkdf2_warned = False @@ -2492,13 +5633,12 @@ def load_pbkdf2_library(force_purepython = False, warnings = True): return hashlib # just so the caller can check which version was loaded except AttributeError: if warnings and not missing_pbkdf2_warned: - print(prog+": warning: hashlib.pbkdf2_hmac requires Python 2.7.8+, using passlib instead", file=sys.stderr) + print("Warning: Can't load hashlib.pbkdf2_hmac, using passlib instead", file=sys.stderr) missing_pbkdf2_warned = True # - import passlib.utils.pbkdf2 - passlib_pbkdf2 = passlib.utils.pbkdf2.pbkdf2 - pbkdf2_hmac = lambda hash_name, *args: passlib_pbkdf2(*args, prf= b"hmac-" + hash_name) - return passlib # just so the caller can check which version was loaded + import lib.passlib.crypto.digest + pbkdf2_hmac = lib.passlib.crypto.digest.pbkdf2_hmac + return lib.passlib # just so the caller can check which version was loaded ################################### Argument Parsing ################################### @@ -2522,25 +5662,25 @@ def _do_safe_print(*args, **kwargs): encoding = "ascii" converted_args = [] for arg in args: - if isinstance(arg, unicode): - arg = arg.encode(encoding, errors="replace") + #if isinstance(arg, str): + # arg = arg.encode(encoding, errors="replace") converted_args.append(arg) return converted_args # -print = safe_print +#print = safe_print # Calls sys.exit with an error message, taking unnamed arguments as print() does def error_exit(*messages): - sys.exit(b" ".join(map(str, _do_safe_print(prog+": error:", *messages)))) + sys.exit(" ".join(map(str, _do_safe_print("Error:", *messages)))) # Ensures all chars in the string fall inside the acceptable range for the current mode def check_chars_range(s, error_msg, no_replacement_chars=False): - assert isinstance(s, tstr), "check_chars_range: s is of " + unicode(tstr) - if tstr == str: + assert isinstance(s, tstr), "check_chars_range: s is of " + str(tstr) + if tstr != str: # For ASCII mode, checks that the input string's chars are all 7-bit US-ASCII for c in s: if ord(c) > 127: # 2**7 - 1 - error_exit(error_msg, "has character with code point", ord(c), "> max (127 / ASCII)\n" + error_exit(error_msg, "has character with code point", ord(c), "(", c , ")", "> max (127 / ASCII)\n" "(see the Unicode Support section in the Tutorial and the --utf8 option)") else: # For Unicode mode, a REPLACEMENT CHARACTER indicates a failed conversion from UTF-8 @@ -2552,7 +5692,7 @@ def check_chars_range(s, error_msg, no_replacement_chars=False): for c in s: c = ord(c) if 0xD800 <= c <= 0xDBFF or 0xDC00 <= c <= 0xDFFF: - error_exit(error_msg, "has character with code point > max ("+unicode(sys.maxunicode)+" / Unicode BMP)") + error_exit(error_msg, "has character with code point > max ("+str(sys.maxunicode)+" / Unicode BMP)") # Returns an (order preserved) list or string with duplicate elements removed @@ -2560,7 +5700,7 @@ def check_chars_range(s, error_msg, no_replacement_chars=False): # (N.B. not a generator function, so faster for small inputs, not for large) def duplicates_removed(iterable): if args.no_dupchecks >= 4: - if isinstance(iterable, basestring) or isinstance(iterable, list): + if isinstance(iterable, str) or isinstance(iterable, list): return iterable return list(iterable) seen = set() @@ -2569,22 +5709,22 @@ def duplicates_removed(iterable): if x not in seen: unique.append(x) seen.add(x) - if len(unique) == len(iterable) and (isinstance(iterable, basestring) or isinstance(iterable, list)): + if len(unique) == len(iterable) and (isinstance(iterable, str) or isinstance(iterable, list)): return iterable - elif isinstance(iterable, basestring): + elif isinstance(iterable, str): return type(iterable)().join(unique) return unique # Converts a wildcard set into a string, expanding ranges and removing duplicates, # e.g.: "hexa-fA-F" -> "hexabcdfABCDEF" def build_wildcard_set(set_string): - return duplicates_removed(re.sub(br"(.)-(.)", expand_single_range, set_string)) + return duplicates_removed(re.sub(r"(.)-(.)", expand_single_range, set_string)) # def expand_single_range(m): char_first, char_last = map(ord, m.groups()) if char_first > char_last: - raise ValueError("first character in wildcard range '"+unichr(char_first)+"' > last '"+unichr(char_last)+"'") - return tstr().join(map(tchr, xrange(char_first, char_last+1))) + raise ValueError("first character in wildcard range '"+chr(char_first)+"' > last '"+chr(char_last)+"'") + return tstr().join(map(tchr, range(char_first, char_last+1))) # Returns an integer count of valid wildcards in the string, or # a string error message if any invalid wildcards are present @@ -2593,10 +5733,10 @@ def count_valid_wildcards(str_with_wildcards, permit_contracting_wildcards = Fal # Remove all valid wildcards, syntax checking the min to max ranges; if any %'s are left they are invalid try: valid_wildcards_removed, count = \ - re.subn(br"%(?:(?:(\d+),)?(\d+))?(?:i?[{}]|i?\[.+?\]{}|(?:;.+?;(\d+)?|;(\d+))?b)" - .format(wildcard_keys, b"|[<>-]" if permit_contracting_wildcards else b""), + re.subn(r"%(?:(?:(\d+),)?(\d+))?(?:i?[{}]|i?\[.+?\]{}|(?:;.+?;(\d+)?|;(\d+))?b)" + .format(wildcard_keys, "|[<>-]" if permit_contracting_wildcards else ""), syntax_check_range, str_with_wildcards) - except ValueError as e: return unicode(e) + except ValueError as e: return str(e) if tstr("%") in valid_wildcards_removed: invalid_wildcard_msg = "invalid wildcard (%) syntax (use %% to escape a %)" # If checking with permit_contracting_wildcards==True returns something different, @@ -2610,9 +5750,9 @@ def count_valid_wildcards(str_with_wildcards, permit_contracting_wildcards = Fal # Expand any custom wildcard sets for the sole purpose of checking for exceptions (e.g. %[z-a]) # We know all wildcards present have valid syntax, so we don't need to use the full regex, but # we do need to capture %% to avoid parsing this as a wildcard set (it isn't one): %%[not-a-set] - for wildcard_set in re.findall(br"%[\d,i]*\[(.+?)\]|%%", str_with_wildcards): + for wildcard_set in re.findall(r"%[\d,i]*\[(.+?)\]|%%", str_with_wildcards): if wildcard_set: - try: re.sub(br"(.)-(.)", expand_single_range, wildcard_set) + try: re.sub(r"(.)-(.)", expand_single_range, wildcard_set) except ValueError as e: return tstr(e) return count # @@ -2621,7 +5761,7 @@ def syntax_check_range(m): if minlen and maxlen and int(minlen) > int(maxlen): raise ValueError("max wildcard length ("+maxlen+") must be >= min length ("+minlen+")") if maxlen and int(maxlen) == 0: - print(prog+": warning: %0 or %0,0 wildcards always expand to empty strings", file=sys.stderr) + print("Warning: %0 or %0,0 wildcards always expand to empty strings", file=sys.stderr) if bpos2: bpos = bpos2 # at most one of these is not None if bpos and int(bpos) == 0: raise ValueError("backreference wildcard position must be > 0") @@ -2636,34 +5776,34 @@ def load_savestate(autosave_file): # Try to load both save slots, ignoring pickle errors at first autosave_file.seek(0) try: - savestate0 = cPickle.load(autosave_file) + savestate0 = pickle.load(autosave_file) except Exception as e: first_error = e - else: assert autosave_file.tell() <= SAVESLOT_SIZE, "load_savestate: slot 0 data <= "+unicode(SAVESLOT_SIZE)+" bytes long" + else: assert autosave_file.tell() <= SAVESLOT_SIZE, "load_savestate: slot 0 data <= "+str(SAVESLOT_SIZE)+" bytes long" autosave_file.seek(0, os.SEEK_END) autosave_len = autosave_file.tell() if autosave_len > SAVESLOT_SIZE: # if the second save slot is present autosave_file.seek(SAVESLOT_SIZE) try: - savestate1 = cPickle.load(autosave_file) + savestate1 = pickle.load(autosave_file) except Exception: pass - else: assert autosave_file.tell() <= 2*SAVESLOT_SIZE, "load_savestate: slot 1 data <= "+unicode(SAVESLOT_SIZE)+" bytes long" + else: assert autosave_file.tell() <= 2*SAVESLOT_SIZE, "load_savestate: slot 1 data <= "+str(SAVESLOT_SIZE)+" bytes long" else: # Convert an old format file to a new one by making it at least SAVESLOT_SIZE bytes long autosave_file.write((SAVESLOT_SIZE - autosave_len) * b"\0") # # Determine which slot is more recent, and use it if savestate0 and savestate1: - use_slot = 0 if savestate0[b"skip"] >= savestate1[b"skip"] else 1 + use_slot = 0 if savestate0["skip"] >= savestate1["skip"] else 1 elif savestate0: if autosave_len > SAVESLOT_SIZE: - print(prog+": warning: data in second autosave slot was corrupted, using first slot", file=sys.stderr) + print("Warning: Data in second autosave slot was corrupted, using first slot", file=sys.stderr) use_slot = 0 elif savestate1: - print(prog+": warning: data in first autosave slot was corrupted, using second slot", file=sys.stderr) + print("Warning: Data in first autosave slot was corrupted, using second slot", file=sys.stderr) use_slot = 1 else: - print(prog+": warning: data in both primary and backup autosave slots is corrupted", file=sys.stderr) + print("Warning: Data in both primary and backup autosave slots is corrupted", file=sys.stderr) raise first_error if use_slot == 0: savestate = savestate0 @@ -2730,8 +5870,9 @@ def readlines(self, size = -1): # def __iter__(self): return self - def next(self): - return self.readline() if self._peeked else self._file.next() + + def __next__(self): + return self.readline() if self._peeked else self._file.__next__() # reset_before_calling = {"seek", "tell", "truncate", "write", "writelines"} def __getattr__(self, name): @@ -2780,7 +5921,7 @@ def open_or_use(filename, mode = "r", funccall_file.seek(0) # The file has contents; if it shouldn't: if new_or_empty: return None - if tstr == unicode: + if tstr == str: if "b" in mode: assert not isinstance(funccall_file, io.TextIOBase), "already opened file not an io.TextIOBase; produces bytes" else: @@ -2788,7 +5929,7 @@ def open_or_use(filename, mode = "r", return MakePeekable(funccall_file) if make_peekable else funccall_file; # if permit_stdin and filename == "-": - if tstr == unicode and "b" not in mode: + if tstr == str and "b" not in mode: sys.stdin = io.open(sys.stdin.fileno(), mode, encoding= sys.stdin.encoding or "utf_8_sig", errors= decoding_errors) if make_peekable: @@ -2798,7 +5939,7 @@ def open_or_use(filename, mode = "r", # If there was no file specified, but a default exists if not filename and default_filename: if permit_stdin and default_filename == "-": - if tstr == unicode and "b" not in mode: + if tstr == str and "b" not in mode: sys.stdin = io.open(sys.stdin.fileno(), mode, encoding= sys.stdin.encoding or "utf_8_sig", errors= decoding_errors) if make_peekable: @@ -2822,10 +5963,17 @@ def open_or_use(filename, mode = "r", if new_or_empty and os.path.exists(filename) and (os.path.getsize(filename) > 0 or not os.path.isfile(filename)): return None # - if tstr == unicode and "b" not in mode: - file = io.open(filename, mode, encoding="utf_8_sig", errors=decoding_errors) + if tstr == str and "b" not in mode: + if filename[-3:] == ".gz": + mode = mode + "t" + file = gzip.open(filename, mode, encoding="utf_8_sig", errors=decoding_errors) + else: + file = io.open(filename, mode, encoding="utf_8_sig", errors=decoding_errors) else: - file = open(filename, mode) + if filename[-3:] == ".gz": + file = gzip.open(filename, mode) + else: + file = open(filename, mode) # if "b" not in mode: if file.read(5) == br"{\rtf": @@ -2842,21 +5990,21 @@ def enable_pause(): if pause_registered is None: if sys.stdin.isatty(): atexit.register(lambda: not multiprocessing.current_process().name.startswith("PoolWorker-") and - raw_input("Press Enter to exit ...")) + input("Press Enter to exit ...")) pause_registered = True else: - print(prog+": warning: ignoring --pause since stdin is not interactive (or was redirected)", file=sys.stderr) + print("Warning: Ignoring --pause since stdin is not interactive (or was redirected)", file=sys.stderr) pause_registered = False ADDRESSDB_DEF_FILENAME = "addresses.db" # copied from btrseed # can raise an exception on some platforms -try: cpus = multiprocessing.cpu_count() -except StandardError: cpus = 1 +try: logical_cpu_cores = multiprocessing.cpu_count() +except Exception: logical_cpu_cores = 1 parser_common = argparse.ArgumentParser(add_help=False) -prog = unicode(parser_common.prog) +prog = str(parser_common.prog) parser_common_initialized = False def init_parser_common(): global parser_common, parser_common_initialized, typo_types_group, bip39_group @@ -2865,6 +6013,9 @@ def init_parser_common(): parser_common.add_argument("--wallet", metavar="FILE", help="the wallet file (this, --data-extract, or --listpass is required)") parser_common.add_argument("--typos", type=int, metavar="COUNT", help="simulate up to this many typos; you must choose one or more typo types from the list below") parser_common.add_argument("--min-typos", type=int, default=0, metavar="COUNT", help="enforce a min # of typos included per guess") + parser_common.add_argument("--password-repeats-pretypos", action="store_true", help="Also test multiple repititions of each candidate password BEFORE any typos are applied(eg: passwordpassword)") + parser_common.add_argument("--password-repeats-posttypos", action="store_true", help="Also test multiple repititions of each candidate password AFTER any typos are applied (eg: passwordpassword)") + parser_common.add_argument("--max-password-repeats", type=int, default=2, metavar="COUNT", help="Max number of additional repetitions of the password to produce (Both the PRE and POST repeats functions use this)") typo_types_group = parser_common.add_argument_group("typo types") typo_types_group.add_argument("--typos-capslock", action="store_true", help="try the password with caps lock turned on") typo_types_group.add_argument("--typos-swap", action="store_true", help="swap two adjacent characters") @@ -2872,50 +6023,123 @@ def init_parser_common(): typo_types_group.add_argument("--typos-"+typo_name, **typo_args) typo_types_group.add_argument("--typos-insert", metavar="WILDCARD-STRING", help="insert a string or wildcard") for typo_name in itertools.chain(("swap",), simple_typo_args.keys(), ("insert",)): - typo_types_group.add_argument("--max-typos-"+typo_name, type=int, default=sys.maxint, metavar="#", help="limit the number of --typos-"+typo_name+" typos") + typo_types_group.add_argument("--max-typos-"+typo_name, type=int, default=sys.maxsize, metavar="#", help="limit the number of --typos-"+typo_name+" typos") typo_types_group.add_argument("--max-adjacent-inserts", type=int, default=1, metavar="#", help="max # of --typos-insert strings that can be inserted between a single pair of characters (default: %(default)s)") parser_common.add_argument("--custom-wild", metavar="STRING", help="a custom set of characters for the %%c wildcard") parser_common.add_argument("--utf8", action="store_true", help="enable Unicode mode; all input must be in UTF-8 format") parser_common.add_argument("--regex-only", metavar="STRING", help="only try passwords which match the given regular expr") parser_common.add_argument("--regex-never", metavar="STRING", help="never try passwords which match the given regular expr") + parser_common.add_argument("--length-min", type=int, default=0, metavar="COUNT", help="skip passwords shorter than given length") + parser_common.add_argument("--length-max", type=int, default=999999, metavar="COUNT", help="skip passwords longer than given length") + parser_common.add_argument("--truncate-length", type=int, default=999999, metavar="COUNT", help="Truncate passwords to be this number of characters long (Useful in situations like Trezor Passprase, etc)") parser_common.add_argument("--delimiter", metavar="STRING", help="the delimiter between tokens in the tokenlist or columns in the typos-map (default: whitespace)") parser_common.add_argument("--skip", type=int, default=0, metavar="COUNT", help="skip this many initial passwords for continuing an interrupted search") - parser_common.add_argument("--threads", type=int, default=cpus, metavar="COUNT", help="number of worker threads (default: number of CPUs, %(default)s)") - parser_common.add_argument("--worker", metavar="ID#/TOTAL#", help="divide the workload between TOTAL# servers, where each has a different ID# between 1 and TOTAL#") + parser_common.add_argument("--threads", type=int, metavar="COUNT", help="number of worker threads (default: number of logical CPU cores") + parser_common.add_argument("--worker", metavar="ID#(ID#2, ID#3)/TOTAL#", help="divide the workload between TOTAL# servers, where each has a different ID# between 1 and TOTAL# (You can optionally assign between 1 and TOTAL IDs of work to a server (eg: 1,2/3 will assign both slices 1 and 2 of the 3 to the server...)") parser_common.add_argument("--max-eta", type=int, default=168, metavar="HOURS", help="max estimated runtime before refusing to even start (default: %(default)s hours, i.e. 1 week)") parser_common.add_argument("--no-eta", action="store_true", help="disable calculating the estimated time to completion") + parser_common.add_argument("--dynamic-passwords-count", action="store_true", help=argparse.SUPPRESS) #help="start trying the passwords while they are being counted") parser_common.add_argument("--no-dupchecks", "-d", action="count", default=0, help="disable duplicate guess checking to save memory; specify up to four times for additional effect") parser_common.add_argument("--no-progress", action="store_true", default=not sys.stdout.isatty(), help="disable the progress bar") + parser_common.add_argument( + "--pre-start-seconds", + type=float, + default=30.0, + metavar="SECONDS", + help="limit how long the pre-start benchmark runs for (default: %(default)s seconds); use 0 to skip it", + ) + parser_common.add_argument( + "--skip-pre-start", + action="store_true", + help="skip the pre-start benchmark; equivalent to --pre-start-seconds 0", + ) parser_common.add_argument("--android-pin", action="store_true", help="search for the spending pin instead of the backup password in a Bitcoin Wallet for Android/BlackBerry") parser_common.add_argument("--blockchain-secondpass", action="store_true", help="search for the second password instead of the main password in a Blockchain wallet") + parser_common.add_argument("--blockchain-correct-mainpass", metavar="STRING", help="The main password for blockchain.com wallets, eithere entered using this argument, or prompted to enter at runtime") parser_common.add_argument("--msigna-keychain", metavar="NAME", help="keychain whose password to search for in an mSIGNA vault") parser_common.add_argument("--data-extract",action="store_true", help="prompt for data extracted by one of the extract-* scripts instead of using a wallet file") + parser_common.add_argument("--data-extract-string", metavar="Data-Extract",help="Directly pass data extracted by one of the extract-* scripts instead of using a wallet file") parser_common.add_argument("--mkey", action="store_true", help=argparse.SUPPRESS) # deprecated, use --data-extract instead parser_common.add_argument("--privkey", action="store_true", help=argparse.SUPPRESS) # deprecated, use --data-extract instead + parser_common.add_argument("--btcrseed", action="store_true",help=argparse.SUPPRESS) # Internal helper argument parser_common.add_argument("--exclude-passwordlist", metavar="FILE", nargs="?", const="-", help="never try passwords read (exactly one per line) from this file or from stdin") parser_common.add_argument("--listpass", action="store_true", help="just list all password combinations to test and exit") parser_common.add_argument("--performance", action="store_true", help="run a continuous performance test (Ctrl-C to exit)") parser_common.add_argument("--pause", action="store_true", help="pause before exiting") + parser_common.add_argument( + "--beep-on-find", + action="store_true", + help="play a two-tone alert roughly every ten seconds when a password is found", + ) + parser_common.add_argument( + "--beep-on-find-pcspeaker", + action="store_true", + help="force the alert to use the internal PC speaker when a password is found", + ) + parser_common.add_argument("--possible-passwords-file", metavar="FILE", default = "possible_passwords.log", help="Specify the file to save possible close matches to. (Defaults to possible_passwords.log)") + parser_common.add_argument("--disable-save-possible-passwords", action="store_true", help="Disable saving possible matches to file") parser_common.add_argument("--version","-v",action="store_true", help="show full version information and exit") - bip39_group = parser_common.add_argument_group("BIP-39 passwords") - bip39_group.add_argument("--bip39", action="store_true", help="search for a BIP-39 password instead of from a wallet") + parser_common.add_argument("--disablesecuritywarnings", "--dsw", action="store_true", help="Disable Security Warning Messages") + dump_group = parser_common.add_argument_group("Wallet Decryption and Key Dumping") + dump_group.add_argument("--dump-wallet", metavar="FILE", help="Dump decrypted wallet to a file specified here.") + dump_group.add_argument("--dump-privkeys", metavar="FILE", help="Dump a list of private keys (For import in to Electrum) to a file specified here") + dump_group.add_argument("--correct-wallet-password", metavar="STRING", help="The correct wallet password (This can be used instead of a passwordlist or tokenlist)") + dump_group.add_argument("--correct-wallet-secondpassword", metavar="STRING", help="The correct wallet second password (This can be used instead of a passwordlist or tokenlist)") + common_seedkey_group = parser_common.add_argument_group("Brainwallet/BIP39/RawPrivkey Shared Arguments") + common_seedkey_group.add_argument("--addrs", metavar="ADDRESS", nargs="+", help="if not using an mpk, address(es) in the wallet") + common_seedkey_group.add_argument("--skip-compressed", action="store_true", default=False, help="Skip check using compressed keys") + common_seedkey_group.add_argument("--skip-uncompressed", action="store_true", default=False, help="Skip check using uncompressed keys, common in older brainwallets.") + common_seedkey_group.add_argument("--force-check-p2sh", action="store_true", default=False, help="For the checking of p2sh segwit addresses (This autodetects for Bitcoin, but will need to be forced for alts like litecoin)") + common_seedkey_group.add_argument("--addressdb", metavar="FILE", nargs="?", help="if not using addrs, use a full address database (default: %(const)s)", const=ADDRESSDB_DEF_FILENAME) + common_seedkey_group.add_argument("--wallet-type", metavar="TYPE", help="the wallet type, e.g. ethereum (default: bitcoin)") + brainwallet_group = parser_common.add_argument_group("Brainwallet") + brainwallet_group.add_argument("--rawprivatekey", action="store_true", help="Search for a Raw Private Key") + brainwallet_group.add_argument("--brainwallet", action="store_true", help="Search for a brainwallet") + brainwallet_group.add_argument("--warpwallet", action="store_true", default=False, help="Treat the brainwallet like a Warpwallet (or Memwallet)") + brainwallet_group.add_argument("--warpwallet-salt", metavar="STRING", help="For the checking of p2sh segwit addresses (This autodetects for Bitcoin, but will need to be forced for alts like litecoin)") + brainwallet_group.add_argument("--memwallet-coin", metavar="STRING", help="Coin type for memwallet brainwallets. (bitcoin, litecoin)") + bip38_group = parser_common.add_argument_group("BIP-38 Encrypted Private Keys (eg: From Bitaddress Paper Wallets)") + bip38_group.add_argument("--bip38-enc-privkey", metavar="ENC-PRIVKEY", help="encrypted private key") + bip38_group.add_argument("--bip38-currency", metavar="Coin Code", help="Currency name from Bitcoinlib (eg: bitcoin, litecoin, dash)") + bip39_group = parser_common.add_argument_group("BIP-39/SLIP39 passwords") + bip39_group.add_argument("--bip39", action="store_true", help="search for a BIP-39 passphrase instead of from a wallet") + bip39_group.add_argument("--slip39", action="store_true", help="search for a SLIP-39 passphrase instead of from a wallet") + bip39_group.add_argument("--slip39-shares", metavar="SLIP39-MNEMONIC", nargs="+", help="SLIP39 Share Mnemonics") bip39_group.add_argument("--mpk", metavar="XPUB", help="the master public key") - bip39_group.add_argument("--addrs", metavar="ADDRESS", nargs="+", help="if not using an mpk, address(es) in the wallet") - bip39_group.add_argument("--addressdb", metavar="FILE", nargs="?", help="if not using addrs, use a full address database (default: %(const)s)", const=ADDRESSDB_DEF_FILENAME) bip39_group.add_argument("--addr-limit", type=int, metavar="COUNT", help="if using addrs or addressdb, the generation limit") bip39_group.add_argument("--language", metavar="LANG-CODE", help="the wordlist language to use (see wordlists/README.md, default: auto)") - bip39_group.add_argument("--bip32-path", metavar="PATH", help="path (e.g. m/0'/0/) excluding the final index (default: BIP-44 account 0)") + bip39_group.add_argument("--bip32-path", metavar="PATH", nargs="+", help="path (e.g. m/0'/0/) excluding the final index. You can specify multiple derivation paths seperated by a space Eg: m/84'/0'/0'/0 m/84'/0'/1'/0 (default: BIP44,BIP49 & BIP84 account 0)") + bip39_group.add_argument("--substrate-path", metavar="PATH", nargs="+", help="Substrate path (eg: //hard/soft). You can specify multiple derivation paths by a space Eg: //hard /soft //hard/soft (default: No Path)") + bip39_group.add_argument("--checksinglexpubaddress", action="store_true", help="Check non-standard single address wallets (Like MyBitcoinWallet and PT.BTC") + bip39_group.add_argument("--force-p2sh", action="store_true", help="Force checking of P2SH segwit addresses for all derivation paths (Required for devices like CoolWallet S if if you are using P2SH segwit accounts on a derivation path that doesn't start with m/49')") + bip39_group.add_argument("--force-p2tr", action="store_true", help="Force checking of P2TR (Taproot) addresses for all derivation paths (Required for wallets like Bitkeep/Bitget that put all accounts on m/44')") + bip39_group.add_argument("--force-bip44", action="store_true", help="Force checking of BIP44 legacy (P2PKH) addresses even if they do not match the supplied addresses") + bip39_group.add_argument("--force-bip84", action="store_true", help="Force checking of BIP84 native SegWit (P2WPKH) addresses even if they do not match the supplied addresses") + bip39_group.add_argument("--disable-p2sh", action="store_true", help="Disable checking of P2SH segwit addresses") + bip39_group.add_argument("--disable-p2tr", action="store_true", help="Disable checking of P2TR (Taproot) addresses") + bip39_group.add_argument("--disable-bip44", action="store_true", help="Disable checking of BIP44 legacy (P2PKH) addresses") + bip39_group.add_argument("--disable-bip84", action="store_true", help="Disable checking of BIP84 native SegWit (P2WPKH) addresses") + bip39_group.add_argument("--mnemonic", metavar="MNEMONIC", help="Your best guess of the mnemonic (if not entered, you will be prompted)") + bip39_group.add_argument("--skip-mnemonic-checksum", action="store_true", + help="skip validating the checksum of the provided mnemonic") bip39_group.add_argument("--mnemonic-prompt", action="store_true", help="prompt for the mnemonic guess via the terminal (default: via the GUI)") - bip39_group.add_argument("--wallet-type", metavar="TYPE", help="the wallet type, e.g. ethereum (default: bitcoin)") + yoroi_group = parser_common.add_argument_group("Yoroi Cadano Wallet") + yoroi_group.add_argument("--yoroi-master-password", metavar="Master_Password", + help="Search for the password to decrypt a Yoroi wallet master_password provided") gpu_group = parser_common.add_argument_group("GPU acceleration") gpu_group.add_argument("--enable-gpu", action="store_true", help="enable experimental OpenCL-based GPU acceleration (only supports Bitcoin Core wallets and extracts)") gpu_group.add_argument("--global-ws", type=int, nargs="+", default=[4096], metavar="PASSWORD-COUNT", help="OpenCL global work size (default: 4096)") gpu_group.add_argument("--local-ws", type=int, nargs="+", default=[None], metavar="PASSWORD-COUNT", help="OpenCL local work size; --global-ws must be evenly divisible by --local-ws (default: auto)") - gpu_group.add_argument("--mem-factor", type=int, default=1, metavar="FACTOR", help="enable memory-saving space-time tradeoff for Armory") - gpu_group.add_argument("--calc-memory",action="store_true", help="list the memory requirements for an Armory wallet") gpu_group.add_argument("--gpu-names", nargs="+", metavar="NAME-OR-ID", help="choose GPU(s) on multi-GPU systems (default: auto)") gpu_group.add_argument("--list-gpus", action="store_true", help="list available GPU names and IDs, then exit") gpu_group.add_argument("--int-rate", type=int, default=200, metavar="RATE", help="interrupt rate: raise to improve PC's responsiveness at the expense of search performance (default: %(default)s)") + opencl_group = parser_common.add_argument_group("OpenCL acceleration") + opencl_group.add_argument("--enable-opencl", action="store_true", help="enable experimental OpenCL-based (GPU) acceleration (only supports BIP39 (for supported coin) and Electrum wallets)") + opencl_group.add_argument("--opencl-workgroup-size", type=int, nargs="+", metavar="PASSWORD-COUNT", help="OpenCL global work size (Seeds are tested in batches, this impacts that batch size)") + opencl_group.add_argument("--opencl-platform", type=int, nargs="+", metavar="ID", help="Choose the OpenCL platform (GPU) to use (default: auto)") + opencl_group.add_argument("--opencl-devices", metavar="ID1,ID2,ID3", help="Choose which OpenCL devices for a given to use as a comma seperated list eg: 1,2,4 (default: all)") + opencl_group.add_argument("--opencl-info", action="store_true", help="list available GPU names and IDs, then exit") + parser_common_initialized = True @@ -2929,12 +6153,55 @@ def register_simple_typo(name, help = None): if help: args["help"] = help typo_types_group.add_argument("--typos-"+name, **arg_params) - typo_types_group.add_argument("--max-typos-"+name, type=int, default=sys.maxint, metavar="#", help="limit the number of --typos-"+name+" typos") + typo_types_group.add_argument("--max-typos-"+name, type=int, default=sys.maxsize, metavar="#", help="limit the number of --typos-"+name+" typos") def decorator(simple_typo_generator): simple_typos[name] = simple_typo_generator return simple_typo_generator # the decorator returns it unmodified, it just gets registered return decorator +# A basic function which takes a list of arguments and strips the ones that will change the passwords that will be checked in some way +# This is called twice when trynig to restore an autosave file. (Once with the arguments from autosave file and once with current arguments passed) +def clean_autosave_args(argList, listName): + # Simple list of parameters and a boolean to indicate whether there is an associated parameter which needs to also be removed from the list + non_modifying_args = {("--dsw", False), + ("--enable-opencl", False), + ("--opencl-workgroup-size", True), + ("--opencl-platform", True), + ("--opencl-devices", True), + ("--no-eta", False), + ("--no-dupchecks", False), + ("--no-progress", False), + ("--enable-gpu", False), + ("--global-ws", True), + ("--local-ws", True), + ("--int-rate", True), + ("--threads", True), + ("--max-eta", True), + ("--skip-mnemonic-checksum", False), + ("--autosave", True) + } + + import copy + working_arglist = copy.deepcopy(argList) # Make a copy so we don't mess with the original arguments list + + # Strip non-modifying args from argument lists + for arg, parameter in non_modifying_args: + try: + arg_index = working_arglist.index(arg) + + if (parameter): + print("Restore Autosave: Permitting Non-Modifying Parameter from:", listName, arg, + working_arglist[arg_index + 1]) + del working_arglist[arg_index:arg_index + 2] + else: + print("Restore Autosave: Permitting Non-Modifying Parameterfrom:", listName, arg) + del working_arglist[arg_index] + + except ValueError: + pass + + return working_arglist + # Once parse_arguments() has completed, password_generator_factory() will return an iterator # (actually a generator object) configured to generate all the passwords requested by the # command-line options, and loaded_wallet.return_verified_password_or_false() can check @@ -2960,31 +6227,53 @@ def decorator(simple_typo_generator): # # TODO: document kwds usage (as used by unit tests) def parse_arguments(effective_argv, wallet = None, base_iterator = None, - perf_iterator = None, inserted_items = None, check_only = None, **kwds): - + perf_iterator = None, inserted_items = None, check_only = None, disable_security_warning_param = False, **kwds): # effective_argv is what we are effectively given, either via the command line, via embedded # options in the tokenlist file, or as a result of restoring a session, before any argument # processing or defaulting is done (unless it's is done by argparse). Each time effective_argv # is changed (due to reading a tokenlist or restore file), we redo parser.parse_args() which # changes args, so we only do this early on before most args processing takes place. + def _apply_beep_configuration(parsed_args): + pcspeaker = getattr(parsed_args, "beep_on_find_pcspeaker", False) + success_alert.configure_pc_speaker(pcspeaker) + success_alert.set_beep_on_find(getattr(parsed_args, "beep_on_find", False) or pcspeaker) + # If no args are present on the command line (e.g. user double-clicked the script # in the shell), enable --pause by default so user doesn't miss any error messages if not effective_argv: enable_pause() # Create a parser which can parse any supported option, and run it - global args + global args, passwordlist_first_line_num, passwordlist_embedded_arguments + passwordlist_first_line_num = 1 + passwordlist_embedded_arguments = False init_parser_common() parser = argparse.ArgumentParser(add_help=False) parser.add_argument("-h", "--help", action="store_true", help="show this help message and exit") parser.add_argument("--tokenlist", metavar="FILE", help="the list of tokens/partial passwords (required)") - parser.add_argument("--max-tokens", type=int, default=sys.maxint, metavar="COUNT", help="enforce a max # of tokens included per guess") + parser.add_argument("--keep-tokens-order", action="store_true", + help="try tokens in the order in which they are listed in the file, without trying their permutations") + parser.add_argument("--seedgenerator", action="store_true", + help=argparse.SUPPRESS) # Flag to be able to indicate to generators that we are doing seed generation, not password generation + parser.add_argument("--mnemonic-length", type=int, + help=argparse.SUPPRESS) # Argument used for generators in seed generation, not password generation + parser.add_argument("--seed-transform-wordswaps", type=int, + help=argparse.SUPPRESS) # Flag to be able to indicate to generators that we want to also try swapped words for seed generation + parser.add_argument("--seed-transform-trezor-common-mistakes", type=int, + help=argparse.SUPPRESS) # Flag to try Trezor common mistake substitutions during seed generation + parser.add_argument("--max-tokens", type=int, default=sys.maxsize, metavar="COUNT", help="enforce a max # of tokens included per guess") parser.add_argument("--min-tokens", type=int, default=1, metavar="COUNT", help="enforce a min # of tokens included per guess") parser._add_container_actions(parser_common) parser.add_argument("--autosave", metavar="FILE", help="autosave (5 min) progress to or restore it from a file") parser.add_argument("--restore", metavar="FILE", help="restore progress and options from an autosave file (must be the only option on the command line)") parser.add_argument("--passwordlist", metavar="FILE", nargs="?", const="-", help="instead of using a tokenlist, read complete passwords (exactly one per line) from this file or from stdin") parser.add_argument("--has-wildcards",action="store_true", help="parse and expand wildcards inside passwordlists (default: wildcards are only parsed inside tokenlists)") + parser.add_argument("--passwordlist-arguments", action="store_true", help="allow the first line of the passwordlist file to start with '#--' and supply additional command line options") + parser.add_argument("--wildcard-custom-list-e",metavar="FILE", help="Path to a custom list file which will be used fr the %%e expanding wildcard") + parser.add_argument("--wildcard-custom-list-f",metavar="FILE", help="Path to a custom list file which will be used fr the %%f expanding wildcard") + parser.add_argument("--wildcard-custom-list-j",metavar="FILE", help="Path to a custom list file which will be used fr the %%j expanding wildcard") + parser.add_argument("--wildcard-custom-list-k",metavar="FILE", help="Path to a custom list file which will be used fr the %%k expanding wildcard") + # # Optional bash tab completion support try: @@ -2994,14 +6283,24 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, pass # args = parser.parse_args(effective_argv) + _apply_beep_configuration(args) # Do this as early as possible so user doesn't miss any error messages if args.pause: enable_pause() + if args.keep_tokens_order and not args.tokenlist: + print("The --keep-tokens-order flag will be ignored since --tokenlist is not used") + + # Disable Security Warnings if parameter set... + global disable_security_warnings + if args.disablesecuritywarnings or disable_security_warning_param: + disable_security_warnings = True + else: + disable_security_warnings = False + # Set the character mode early-- it's used by a large portion of the # rest of this module (starting with the first call to open_or_use()) - if args.utf8: enable_unicode_mode() - else: enable_ascii_mode() + enable_unicode_mode() # If a simple passwordlist or base_iterator is being provided, re-parse the command line with fewer options # (--help is handled directly by argparse in this case) @@ -3009,10 +6308,36 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, parser = argparse.ArgumentParser(add_help=True) parser.add_argument("--passwordlist", required=not base_iterator, nargs="?", const="-", metavar="FILE", help="instead of using a tokenlist, read complete passwords (exactly one per line) from this file or from stdin") parser.add_argument("--has-wildcards",action="store_true", help="parse and expand wildcards inside passwordlists (default: disabled for passwordlists)") + parser.add_argument("--passwordlist-arguments", action="store_true", help="allow the first line of the passwordlist file to start with '#--' and supply additional command line options") + parser.add_argument("--tokenlist", metavar="FILE", help="the list of tokens/partial passwords (required)") + parser.add_argument("--max-tokens", type=int, default=sys.maxsize, metavar="COUNT", + help="enforce a max # of tokens included per guess") + parser.add_argument("--min-tokens", type=int, default=1, metavar="COUNT", + help="enforce a min # of tokens included per guess") + parser.add_argument("--seedgenerator", action="store_true", + help=argparse.SUPPRESS) # Flag to be able to indicate to generators that we are doing seed generation, not password generation + parser.add_argument("--keep-tokens-order", action="store_true", + help="try tokens in the order in which they are listed in the file, without trying their permutations") + parser.add_argument("--mnemonic-length", type=int, + help=argparse.SUPPRESS) # Argument used for generators in seed generation, not password generation + parser.add_argument("--seed-transform-wordswaps", type=int, + help=argparse.SUPPRESS) # Flag to be able to indicate to generators that we want to also try swapped words for seed generation + parser.add_argument("--seed-transform-trezor-common-mistakes", type=int, + help=argparse.SUPPRESS) # Flag to try Trezor common mistake substitutions during seed generation + parser.add_argument("--wildcard-custom-list-e", metavar="FILE", + help="Path to a custom list file which will be used fr the %%e expanding wildcard") + parser.add_argument("--wildcard-custom-list-f", metavar="FILE", + help="Path to a custom list file which will be used fr the %%f expanding wildcard") + parser.add_argument("--wildcard-custom-list-j", metavar="FILE", + help="Path to a custom list file which will be used fr the %%j expanding wildcard") + parser.add_argument("--wildcard-custom-list-k", metavar="FILE", + help="Path to a custom list file which will be used fr the %%k expanding wildcard") + parser._add_container_actions(parser_common) # Add these in as non-options so that args gets a copy of their values parser.set_defaults(autosave=False, restore=False) args = parser.parse_args(effective_argv) + _apply_beep_configuration(args) # Manually handle the --help option, now that we know which help (tokenlist, not passwordlist) to print elif args.help: @@ -3022,6 +6347,10 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # Version information is always printed by btcrecover.py, so just exit if args.version: sys.exit(0) + if args.opencl_info: + info = opencl_information() + info.printfullinfo() + exit(0) if args.performance and (base_iterator or args.passwordlist or args.tokenlist): error_exit("--performance cannot be used with --tokenlist or --passwordlist") @@ -3031,13 +6360,16 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if not devices_avail: error_exit("no supported GPUs found") for i, dev in enumerate(devices_avail, 1): - print("#"+unicode(i), dev.name.strip()) + print("#"+str(i), dev.name.strip()) sys.exit(0) # If we're not --restoring nor using a passwordlist, try to open the tokenlist_file now # (if we are restoring, we don't know what to open until after the restore data is loaded) - TOKENS_AUTO_FILENAME = b"btcrecover-tokens-auto.txt" - if not (args.restore or args.passwordlist or args.performance or base_iterator): + TOKENS_AUTO_FILENAME = "btcrecover-tokens-auto.txt" + + provided_passwordlist = kwds.get("passwordlist") + + if (not (args.restore or args.passwordlist or args.performance or base_iterator)) or (args.seedgenerator and not args.passwordlist): tokenlist_file = open_or_use(args.tokenlist, "r", kwds.get("tokenlist"), default_filename=TOKENS_AUTO_FILENAME, permit_stdin=True, make_peekable=True) if hasattr(tokenlist_file, "name") and tokenlist_file.name.startswith(TOKENS_AUTO_FILENAME): @@ -3045,27 +6377,71 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, else: tokenlist_file = None + if args.passwordlist_arguments and not args.passwordlist: + error_exit("--passwordlist-arguments requires --passwordlist") + + if args.passwordlist and args.passwordlist_arguments: + passwordlist_args_file = open_or_use( + args.passwordlist, + "r", + provided_passwordlist, + permit_stdin=True, + decoding_errors="replace", + ) + if passwordlist_args_file == sys.stdin: + error_exit("--passwordlist-arguments cannot be used with stdin") + first_line = passwordlist_args_file.readline() + if not first_line: + error_exit("--passwordlist-arguments requires a non-empty passwordlist file") + stripped_first_line = first_line[1:].strip() if first_line.startswith("#") else None + if not stripped_first_line or not stripped_first_line.startswith("--"): + error_exit("--passwordlist-arguments requires the first line to begin with '#--'") + print("Read additional options from passwordlist file: " + stripped_first_line, file=sys.stderr) + passwordlist_args = stripped_first_line.split() + effective_argv = passwordlist_args + effective_argv + args = parser.parse_args(effective_argv) + _apply_beep_configuration(args) + if args.pause: enable_pause() + for arg in passwordlist_args: + if arg.startswith("--pas"): # --passwordlist or --passwordlist-arguments + error_exit("the --passwordlist option is not permitted inside a passwordlist file") + elif arg.startswith("--to"): # --tokenlist + error_exit("the --tokenlist option is not permitted inside a passwordlist file") + elif arg.startswith("--pe"): # --performance + error_exit("the --performance option is not permitted inside a passwordlist file") + elif arg.startswith("--u"): # --utf8 + error_exit("the --utf8 option is not permitted inside a passwordlist file") + try: + passwordlist_args_file.seek(0) + except (AttributeError, io.UnsupportedOperation): + pass + if passwordlist_args_file not in (provided_passwordlist, None): + passwordlist_args_file.close() + passwordlist_first_line_num = 2 + passwordlist_embedded_arguments = True + # If the first line of the tokenlist file starts with "#\s*--", parse it as additional arguments # (note that command line arguments can override arguments in this file) tokenlist_first_line_num = 1 - if tokenlist_file and tokenlist_file.peek() == b"#": # if it's either a comment or additional args + if tokenlist_file and tokenlist_file.peek() == "#": # if it's either a comment or additional args first_line = tokenlist_file.readline()[1:].strip() tokenlist_first_line_num = 2 # need to pass this to parse_token_list - if first_line.startswith(b"--"): # if it's additional args, not just a comment - print(b"Read additional options from tokenlist file: "+first_line, file=sys.stderr) + if first_line.startswith("--"): # if it's additional args, not just a comment + print("Read additional options from tokenlist file: "+first_line, file=sys.stderr) tokenlist_args = first_line.split() # TODO: support quoting / escaping? effective_argv = tokenlist_args + effective_argv # prepend them so that real argv takes precedence args = parser.parse_args(effective_argv) # reparse the arguments + _apply_beep_configuration(args) # Check this again as early as possible so user doesn't miss any error messages if args.pause: enable_pause() for arg in tokenlist_args: - if arg.startswith(b"--to"): # --tokenlist + if arg.startswith("--to"): # --tokenlist error_exit("the --tokenlist option is not permitted inside a tokenlist file") - elif arg.startswith(b"--pas"): # --passwordlist + elif arg.startswith("--pas"): # --passwordlist error_exit("the --passwordlist option is not permitted inside a tokenlist file") - elif arg.startswith(b"--pe"): # --performance + elif arg.startswith("--pe"): # --performance error_exit("the --performance option is not permitted inside a tokenlist file") - elif arg.startswith(b"--u"): # --utf8 + elif arg.startswith("--u"): # --utf8 error_exit("the --utf8 option is not permitted inside a tokenlist file") @@ -3082,15 +6458,16 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if len(effective_argv) > 2 or "=" in effective_argv[0] and len(effective_argv) > 1: error_exit("the --restore option must be the only option when used") load_savestate(autosave_file) - effective_argv = savestate[b"argv"] # argv is effectively being replaced; it's reparsed below + effective_argv = savestate["argv"] # argv is effectively being replaced; it's reparsed below print("Restoring session:", " ".join(effective_argv)) - print("Last session ended having finished password #", savestate[b"skip"]) + print("Last session ended having finished password #", savestate["skip"]) restore_filename = args.restore # save this before it's overwritten below args = parser.parse_args(effective_argv) + _apply_beep_configuration(args) # Check this again as early as possible so user doesn't miss any error messages if args.pause: enable_pause() # If the order of passwords generated has changed since the last version, don't permit a restore - restored_ordering_version = savestate.get(b"ordering_version") + restored_ordering_version = savestate.get("ordering_version") if restored_ordering_version != __ordering_version__: if restored_ordering_version == __ordering_version__ + b"-Unicode": args.utf8 = True # backwards compatibility with versions < 0.15.0 @@ -3111,10 +6488,10 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if tokenlist_file and tokenlist_file.peek() == b"#": # if it's either a comment or additional args first_line = tokenlist_file.readline() tokenlist_first_line_num = 2 # need to pass this to parse_token_list - if re.match(b"#\s*--", first_line, re.UNICODE): # if it's additional args, not just a comment - print(prog+b": warning: all options loaded from restore file; ignoring options in tokenlist file '"+tokenlist_file.name+b"'", file=sys.stderr) + if re.match(r"#\s*--", first_line, re.UNICODE): # if it's additional args, not just a comment + print("Warning: all options loaded from restore file; ignoring options in tokenlist file '"+tokenlist_file.name+"'", file=sys.stderr) print("Using autosave file '"+restore_filename+"'") - args.skip = savestate[b"skip"] # override this with the most recent value + args.skip = savestate["skip"] # override this with the most recent value restored = True # a global flag for future reference # elif args.autosave: @@ -3123,16 +6500,30 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if autosave_file: # Load and compare to current arguments load_savestate(autosave_file) - restored_argv = savestate[b"argv"] + restored_argv = savestate["argv"] print("Restoring session:", " ".join(restored_argv)) - print("Last session ended having finished password #", savestate[b"skip"]) - if restored_argv != effective_argv: # TODO: be more lenient than an exact match? - error_exit("can't restore previous session: the command line options have changed") + print("Last session ended having finished password #", savestate["skip"]) + if restored_argv != effective_argv: # If the arguments provided are different to the save file, check if the difference actually makes a difference to password generation ordering/etc + savecheck_restored_argv = clean_autosave_args(restored_argv, "AutoSaveFile") + savecheck_effective_argv = clean_autosave_args(effective_argv, "CurrentArgs") + + args_difference = list(set(savecheck_effective_argv).symmetric_difference(set(savecheck_restored_argv))) + + if len(args_difference) > 0: # If none of the differences matter, let it go, otherwise exit... + print() + print("ERROR: Can't restore previous session: the command line options have changed in a way that will impact password generation.") + print() + print("Non-Changeable Args from Autosave:", " ".join(savecheck_restored_argv)) + print() + print("Non-Changeable Args from Current Command:", " ".join(savecheck_effective_argv)) + print() + error_exit("Disallowed Arguments Difference:", " ".join(args_difference)) + # If the order of passwords generated has changed since the last version, don't permit a restore - if __ordering_version__ != savestate.get(b"ordering_version"): + if __ordering_version__ != savestate.get("ordering_version"): error_exit("autosave was created with an incompatible version of "+prog) print("Using autosave file '"+args.autosave+"'") - args.skip = savestate[b"skip"] # override this with the most recent value + args.skip = savestate["skip"] # override this with the most recent value restored = True # a global flag for future reference # # Else if the specified file is empty or doesn't exist: @@ -3140,16 +6531,16 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, assert not (wallet or base_iterator or inserted_items), \ '--autosave is not supported with custom parse_arguments()' if args.listpass: - print(prog+": warning: --autosave is ignored with --listpass", file=sys.stderr) + print("Warning: --autosave is ignored with --listpass", file=sys.stderr) elif args.performance: - print(prog+": warning: --autosave is ignored with --performance", file=sys.stderr) + print("Warning: --autosave is ignored with --performance", file=sys.stderr) else: # create an initial savestate that is populated throughout the rest of parse_arguments() savestate = dict(argv = effective_argv, ordering_version = __ordering_version__) # Do some basic globals initialization; the rest are all done below - init_wildcards() + init_wildcards(args.wildcard_custom_list_e, args.wildcard_custom_list_f, args.wildcard_custom_list_j, args.wildcard_custom_list_k) init_password_generator() # Do a bunch of argument sanity checking @@ -3157,7 +6548,8 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # Either we're using a passwordlist file (though it's not yet opened), # or we're using a tokenlist file which should have been found and opened by now, # or we're running a performance test (and neither is open; already checked above). - if not (args.passwordlist or tokenlist_file or args.performance or base_iterator or args.calc_memory): + if not (args.passwordlist or tokenlist_file or args.performance or base_iterator or + ((args.correct_wallet_password or args.correct_wallet_password) and (args.dump_wallet or args.dump_privkeys))): error_exit("argument --tokenlist or --passwordlist is required (or file "+TOKENS_AUTO_FILENAME+" must be present)") if tokenlist_file and args.max_tokens < args.min_tokens: @@ -3170,24 +6562,24 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # Sanity check the --max-typos-* options for typo_name in itertools.chain(("swap",), simple_typos.keys(), ("insert",)): typo_max = args.__dict__["max_typos_"+typo_name] - if typo_max < sys.maxint: + if typo_max < sys.maxsize: # # Sanity check for when a --max-typos-* is specified, but the corresponding --typos-* is not if not args.__dict__["typos_"+typo_name]: - print(prog+": warning: --max-typos-"+typo_name+" is ignored without --typos-"+typo_name, file=sys.stderr) + print("Warning: --max-typos-"+typo_name+" is ignored without --typos-"+typo_name, file=sys.stderr) # # Sanity check for a a --max-typos-* <= 0 elif typo_max <= 0: - print(prog+": warning: --max-typos-"+typo_name, typo_max, "disables --typos-"+typo_name, file=sys.stderr) + print("Warning: --max-typos-"+typo_name, typo_max, "disables --typos-"+typo_name, file=sys.stderr) args.__dict__["typos_"+typo_name] = None # # Sanity check --max-typos-* vs the total number of --typos elif args.typos and typo_max > args.typos: - print(prog+": warning: --max-typos-"+typo_name+" ("+unicode(typo_max)+") is limited by the number of --typos ("+unicode(args.typos)+")", file=sys.stderr) + print("Warning: --max-typos-"+typo_name+" ("+str(typo_max)+") is limited by the number of --typos ("+str(args.typos)+")", file=sys.stderr) # Sanity check --typos--closecase if args.typos_closecase and args.typos_case: - print(prog+": warning: specifying --typos-case disables --typos-closecase", file=sys.stderr) + print("Warning: specifying --typos-case disables --typos-closecase", file=sys.stderr) args.typos_closecase = None # Build an ordered list of enabled simple typo generators. This list MUST be in the same relative @@ -3205,22 +6597,22 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if args.min_typos > 0: error_exit("no passwords are produced when no type of typo is chosen, but --min-typos were required") if args.typos: - print(prog+": warning: --typos has no effect because no type of typo was chosen", file=sys.stderr) + print("Warning: --typos has no effect because no type of typo was chosen", file=sys.stderr) # else: if args.typos is None: if args.min_typos: - print(prog+": warning: --typos COUNT not specified; assuming same as --min_typos ("+unicode(args.min_typos)+")", file=sys.stderr) + print("Warning: --typos COUNT not specified; assuming same as --min_typos ("+str(args.min_typos)+")", file=sys.stderr) args.typos = args.min_typos else: - print(prog+": warning: --typos COUNT not specified; assuming 1", file=sys.stderr) + print("Warning: --typos COUNT not specified; assuming 1", file=sys.stderr) args.typos = 1 # elif args.typos < args.min_typos: error_exit("--min_typos must be less than --typos") # elif args.typos <= 0: - print(prog+": warning: --typos", args.typos, " disables all typos", file=sys.stderr) + print("Warning: --typos", args.typos, " disables all typos", file=sys.stderr) enabled_simple_typos = args.typos_capslock = args.typos_swap = args.typos_insert = inserted_items = None # If any simple typos have been enabled, set max_simple_typos and sum_max_simple_typos appropriately @@ -3228,26 +6620,26 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if enabled_simple_typos: max_simple_typos = \ [args.__dict__["max_typos_"+name] for name in simple_typos.keys() if args.__dict__["typos_"+name]] - if min(max_simple_typos) == sys.maxint: # if none were specified + if min(max_simple_typos) == sys.maxsize: # if none were specified max_simple_typos = None - sum_max_simple_typos = sys.maxint - elif max(max_simple_typos) == sys.maxint: # if one, but not all were specified - sum_max_simple_typos = sys.maxint + sum_max_simple_typos = sys.maxsize + elif max(max_simple_typos) == sys.maxsize: # if one, but not all were specified + sum_max_simple_typos = sys.maxsize else: # else all were specified sum_max_simple_typos = sum(max_simple_typos) # Sanity check --max-adjacent-inserts (inserts are not a "simple" typo) if args.max_adjacent_inserts != 1: if not args.typos_insert: - print(prog+": warning: --max-adjacent-inserts has no effect unless --typos-insert is used", file=sys.stderr) + print("Warning: --max-adjacent-inserts has no effect unless --typos-insert is used", file=sys.stderr) elif args.max_adjacent_inserts < 1: - print(prog+": warning: --max-adjacent-inserts", args.max_adjacent_inserts, " disables --typos-insert", file=sys.stderr) + print("Warning: --max-adjacent-inserts", args.max_adjacent_inserts, " disables --typos-insert", file=sys.stderr) args.typos_insert = None elif args.max_adjacent_inserts > min(args.typos, args.max_typos_insert): if args.max_typos_insert < args.typos: - print(prog+": warning: --max-adjacent-inserts ("+unicode(args.max_adjacent_inserts)+") is limited by --max-typos-insert ("+unicode(args.max_typos_insert)+")", file=sys.stderr) + print("Warning: --max-adjacent-inserts ("+str(args.max_adjacent_inserts)+") is limited by --max-typos-insert ("+str(args.max_typos_insert)+")", file=sys.stderr) else: - print(prog+": warning: --max-adjacent-inserts ("+unicode(args.max_adjacent_inserts)+") is limited by the number of --typos ("+unicode(args.typos)+")", file=sys.stderr) + print("Warning: --max-adjacent-inserts ("+str(args.max_adjacent_inserts)+") is limited by the number of --typos ("+str(args.typos)+")", file=sys.stderr) # For custom inserted_items, temporarily set this to disable wildcard expansion of --insert if inserted_items: @@ -3258,7 +6650,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, global wildcard_keys if (args.passwordlist or base_iterator) and not \ (args.has_wildcards or args.typos_insert or args.typos_replace): - print(prog+": warning: ignoring unused --custom-wild", file=sys.stderr) + print("Warning: ignoring unused --custom-wild", file=sys.stderr) else: args.custom_wild = tstr_from_stdin(args.custom_wild) check_chars_range(args.custom_wild, "--custom-wild") @@ -3280,7 +6672,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, arg_val = tstr_from_stdin(arg_val) check_chars_range(arg_val, arg_name) count_or_error_msg = count_valid_wildcards(arg_val) - if isinstance(count_or_error_msg, basestring): + if isinstance(count_or_error_msg, str): error_exit(arg_name, arg_val, ":", count_or_error_msg) if count_or_error_msg: load_backreference_maps_from_token(arg_val) @@ -3301,7 +6693,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, typos_map = None if args.typos_map: sha1 = hashlib.sha1() if savestate else None - typos_map = parse_mapfile(open_or_use(args.typos_map, "r", kwds.get("typos_map")), sha1, b"--typos-map") + typos_map = parse_mapfile(open_or_use(args.typos_map, "r", kwds.get("typos_map")), sha1, "--typos-map") # # If autosaving, take the hash of the typos_map and either check it # during a session restore to make sure we're actually restoring @@ -3310,15 +6702,15 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, typos_map_hash = sha1.digest() del sha1 if restored: - if typos_map_hash != savestate[b"typos_map_hash"]: + if typos_map_hash != savestate["typos_map_hash"]: error_exit("can't restore previous session: the typos-map file has changed") else: - savestate[b"typos_map_hash"] = typos_map_hash + savestate["typos_map_hash"] = typos_map_hash # # Else if not args.typos_map but these were specified: elif (args.passwordlist or base_iterator) and args.delimiter: # With --passwordlist, --delimiter is only used for a --typos-map - print(prog+": warning: ignoring unused --delimiter", file=sys.stderr) + print("Warning: ignoring unused --delimiter", file=sys.stderr) # Compile the regex options global regex_only, regex_never @@ -3330,39 +6722,58 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, global custom_final_checker custom_final_checker = check_only + if args.length_min < 0: + print("Warning: length-min must be >= 0, assuming 0", file=sys.stderr) + args.length_min = 0 + + if args.length_max < args.length_min: + print("Warning: length-max must be >= length-min, assuming length-min", file=sys.stderr) + args.length_max = args.length_min + if args.skip < 0: - print(prog+": warning: --skip must be >= 0, assuming 0", file=sys.stderr) + print("Warning: --skip must be >= 0, assuming 0", file=sys.stderr) args.skip = 0 - if args.threads < 1: - print(prog+": warning: --threads must be >= 1, assuming 1", file=sys.stderr) - args.threads = 1 + threads_specified_by_user = args.threads is not None + + if args.threads: + if args.threads < 1: + print("Warning: --threads must be >= 1, assuming 1", file=sys.stderr) + args.threads = 1 + + if args.threads > 60: + if sys.platform == "win32": + print("WARNING: Windows doesn't support more than 60 threads") + args.threads = 60 if args.worker: # worker servers global worker_id, workers_total - match = re.match(br"(\d+)/(\d+)$", args.worker) - if not match: - error_exit("--worker ID#/TOTAL# must be have the format uint/uint") - worker_id = int(match.group(1)) - workers_total = int(match.group(2)) + workers_total = int(args.worker.split("/")[1]) + + worker_id = args.worker.split("/")[0].split(",") + worker_id = [int(x) -1 for x in worker_id] # now it's in the range [0, workers_total) + if workers_total < 2: error_exit("in --worker ID#/TOTAL#, TOTAL# must be >= 2") - if worker_id < 1: + if min(worker_id) < 0: error_exit("in --worker ID#/TOTAL#, ID# must be >= 1") - if worker_id > workers_total: + if max(worker_id) > workers_total: error_exit("in --worker ID#/TOTAL#, ID# must be <= TOTAL#") - worker_id -= 1 # now it's in the range [0, workers_total) global have_progress, progressbar if args.no_progress: have_progress = False else: try: - import progressbar + from lib import progressbar have_progress = True except ImportError: have_progress = False + ############################## + # Wallet Loading Related Arguments + ############################## + # --bip39 is implied if any bip39 option is used for action in bip39_group._group_actions: if args.__dict__[action.dest]: @@ -3374,14 +6785,20 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, args.data_extract = True required_args = 0 - if args.wallet: required_args += 1 - if args.data_extract: required_args += 1 - if args.bip39: required_args += 1 - if args.listpass: required_args += 1 - if wallet: required_args += 1 - if required_args != 1: - assert not wallet, 'custom wallet object not permitted with --wallet, --data-extract, --bip39, or --listpass' - error_exit("argument --wallet (or --data-extract, --bip39, or --listpass, exactly one) is required") + if args.wallet: required_args += 1 + if args.data_extract: required_args += 1 + if args.data_extract_string: required_args += 1 + if args.bip38_enc_privkey: required_args += 1 + if args.bip39: required_args += 1 + if args.yoroi_master_password: required_args += 1 + if args.brainwallet: required_args += 1 + if args.rawprivatekey: required_args += 1 + if args.warpwallet: required_args += 1 + if args.listpass: required_args += 1 + if wallet: required_args += 1 + if required_args != 1 and (args.seedgenerator == False): + assert not wallet, 'custom wallet object not permitted with --wallet, --data-extract, --brainwallet, --warpwallet, --bip39, --yoroi-master-password, --bip38_enc_privkey, or --listpass' + error_exit("argument --wallet (or --data-extract, --bip39, --brainwallet, --warpwallet, --rawprivatekey, --yoroi-master-password, --bip38_enc_privkey, or --listpass, exactly one) is required") # If specificed, use a custom wallet object instead of loading a wallet file or data-extract global loaded_wallet @@ -3393,87 +6810,392 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if args.android_pin: loaded_wallet = WalletAndroidSpendingPIN.load_from_filename(args.wallet) elif args.blockchain_secondpass: - loaded_wallet = WalletBlockchainSecondpass.load_from_filename(args.wallet) + if args.blockchain_correct_mainpass: + loaded_wallet = WalletBlockchainSecondpass.load_from_filename(args.wallet, args.blockchain_correct_mainpass) + elif args.correct_wallet_password: + loaded_wallet = WalletBlockchainSecondpass.load_from_filename(args.wallet, args.correct_wallet_password) + else: + loaded_wallet = WalletBlockchainSecondpass.load_from_filename(args.wallet) elif args.wallet == "__null": loaded_wallet = WalletNull() else: load_global_wallet(args.wallet) if type(loaded_wallet) is WalletBitcoinj: - print(prog+": notice: for MultiBit, use a .key file instead of a .wallet file if possible") + print("Notice: for MultiBit, use a .key file instead of a .wallet file if possible") if isinstance(loaded_wallet, WalletMultiBit) and not args.android_pin: - print(prog+": notice: use --android-pin to recover the spending PIN of\n" + print("Notice: use --android-pin to recover the spending PIN of\n" " a Bitcoin Wallet for Android/BlackBerry backup (instead of the backup password)") if args.msigna_keychain and not isinstance(loaded_wallet, WalletMsigna): - print(prog+": warning: ignoring --msigna-keychain (wallet file is not an mSIGNA vault)") + print("Warning: ignoring --msigna-keychain (wallet file is not an mSIGNA vault)") + + + if args.bip38_enc_privkey: + if args.bip38_currency: + loaded_wallet = WalletBIP38(args.bip38_enc_privkey, args.bip38_currency) + else: + loaded_wallet = WalletBIP38(args.bip38_enc_privkey) + + # Parse --bip39 related options, and create a WalletBIP39 object + if args.bip39: + if args.mnemonic: + mnemonic = args.mnemonic + elif args.mnemonic_prompt: + encoding = sys.stdin.encoding or "ASCII" + if "utf" not in encoding.lower(): + print("terminal does not support UTF; mnemonics with non-ASCII chars might not work", file=sys.stderr) + mnemonic = input("Please enter your mnemonic (seed)\n> ") + if not mnemonic: + sys.exit("canceled") + else: + mnemonic = None + + args.wallet_type = args.wallet_type.strip().lower() if args.wallet_type else "bip39" + + if args.wallet_type == "cardano": + loaded_wallet = WalletCardano(args.addrs, args.addressdb, mnemonic, + args.language, args.bip32_path, args.performance) + elif args.wallet_type in ['avalanche', 'tron', 'solana', 'cosmos', 'tezos','stellar','multiversx']: + loaded_wallet = WalletPyCryptoHDWallet(args.mpk, args.addrs, args.addr_limit, args.addressdb, mnemonic, + args.language, args.bip32_path, args.wallet_type, args.performance) + elif args.wallet_type in ['polkadotsubstrate']: + loaded_wallet = WalletPyCryptoHDWallet(args.mpk, args.addrs, args.addr_limit, args.addressdb, mnemonic, + args.language, args.substrate_path, args.wallet_type, args.performance) + elif args.wallet_type == "ethereumvalidator": + loaded_wallet = WalletEthereumValidator(args.mpk, args.addrs, args.addr_limit, args.addressdb, mnemonic, + args.language, args.bip32_path, args.wallet_type, args.performance) + elif args.slip39: + loaded_wallet = WalletSLIP39(args.mpk, args.addrs, args.addr_limit, args.addressdb, args.slip39_shares, + args.language, args.bip32_path, args.wallet_type, args.performance) + else: + loaded_wallet = WalletBIP39(args.mpk, args.addrs, args.addr_limit, args.addressdb, mnemonic, + args.language, args.bip32_path, args.wallet_type, args.performance, + force_p2sh = args.force_p2sh, + checksinglexpubaddress = args.checksinglexpubaddress, + force_p2tr = args.force_p2tr, + force_bip44 = args.force_bip44, + force_bip84 = args.force_bip84, + disable_p2sh = args.disable_p2sh, + disable_p2tr = args.disable_p2tr, + disable_bip44 = args.disable_bip44, + disable_bip84 = args.disable_bip84) + + + if args.yoroi_master_password: + loaded_wallet = WalletYoroi(args.yoroi_master_password, args.performance) + + if args.brainwallet or args.warpwallet: + loaded_wallet = WalletBrainwallet(addresses = args.addrs, + addressdb = args.addressdb, + check_compressed = not(args.skip_compressed), + check_uncompressed = not(args.skip_uncompressed), + force_check_p2sh = args.force_check_p2sh, + isWarpwallet=args.warpwallet, + salt=args.warpwallet_salt, + crypto=args.memwallet_coin) + + if args.rawprivatekey: + loaded_wallet = WalletRawPrivateKey(addresses = args.addrs, + addressdb = args.addressdb, + check_compressed = not(args.skip_compressed), + check_uncompressed = not(args.skip_uncompressed), + force_check_p2sh = args.force_check_p2sh, + crypto=args.wallet_type, + correct_wallet_password = args.correct_wallet_password) + + # Set the default number of threads to use. For GPU processing, things like hyperthreading are unhelpful, so use physical cores only... + if not args.threads: + if not args.enable_opencl or type(loaded_wallet) is WalletElectrum28 or type(loaded_wallet) is WalletMetamask: # Not (generally) worthwhile having more than 2 threads when using OpenCL due to the relatively simply hash verification (unlike seed recovery) + args.threads = logical_cpu_cores + else: + if args.btcrseed or args.bip39 or args.wallet_type: # BIP39 wallets generally benefit from as much CPU power as possible + args.threads = logical_cpu_cores + else: + args.threads = 2 + if args.threads > 60: + if sys.platform == "win32": + print("Note: Windows doesn't support more than 60 threads, setting threads to 60...") + args.threads = 60 # Prompt for data extracted by one of the extract-* scripts # instead of loading a wallet file - if args.data_extract: + if args.data_extract or args.data_extract_string: key_crc_base64 = kwds.get("data_extract") # for unittest + if args.data_extract_string: + key_crc_base64 = args.data_extract_string if not key_crc_base64: if tokenlist_file == sys.stdin: - print(prog+": warning: order of data on stdin is: optional extra command-line arguments, key data, rest of tokenlist", file=sys.stderr) + print("Warning: order of data on stdin is: optional extra command-line arguments, key data, rest of tokenlist", file=sys.stderr) elif args.passwordlist == "-" and not sys.stdin.isatty(): # if isatty, friendly prompts are provided instead - print(prog+": warning: order of data on stdin is: key data, password list", file=sys.stderr) + print("Warning: order of data on stdin is: key data, password list", file=sys.stderr) # key_prompt = "Please enter the data from the extract script\n> " # the default friendly prompt try: if not sys.stdin.isatty() or sys.stdin.peeked: key_prompt = "Reading extract data from stdin\n" # message to use if key data has already been entered except AttributeError: pass - key_crc_base64 = raw_input(key_prompt) + key_crc_base64 = input(key_prompt) # # Emulates load_global_wallet(), but using the base64 key data instead of a wallet # file (this sets the loaded_wallet global, and returns the validated CRC) key_crc = load_from_base64_key(key_crc_base64) # - # Armory's extract script provides an encrypted full private key (but not the master private key nor the chaincode) - if isinstance(loaded_wallet, WalletArmory): - print("WARNING: an Armory private key, once decrypted, provides access to that key's Bitcoin", file=sys.stderr) - # if isinstance(loaded_wallet, WalletMsigna): if args.msigna_keychain: - print(prog+": warning: ignoring --msigna-keychain (the extract script has already chosen the keychain)") + print("Warning: ignoring --msigna-keychain (the extract script has already chosen the keychain)") elif args.msigna_keychain: - print(prog+": warning: ignoring --msigna-keychain (--data-extract is not from an mSIGNA vault)") + print("Warning: ignoring --msigna-keychain (--data-extract is not from an mSIGNA vault)") # # If autosaving, either check the key_crc during a session restore to make sure we're # actually restoring the exact same session, or save it for future such checks if savestate: if restored: - if key_crc != savestate[b"key_crc"]: + if key_crc != savestate["key_crc"]: error_exit("can't restore previous session: the encrypted key entered is not the same") else: - savestate[b"key_crc"] = key_crc + savestate["key_crc"] = key_crc + ############################################# + # + # Wallet is certainly loaded by this point... + # + ############################################# - # Parse --bip39 related options, and create a WalletBIP39 object - if args.bip39: - if args.mnemonic_prompt: - encoding = sys.stdin.encoding or "ASCII" - if "utf" not in encoding.lower(): - print("terminal does not support UTF; mnemonics with non-ASCII chars might not work", file=sys.stderr) - mnemonic = raw_input("Please enter your mnemonic (seed)\n> ") - if not mnemonic: - sys.exit("canceled") - if isinstance(mnemonic, str): - mnemonic = mnemonic.decode(encoding) # convert from terminal's encoding to unicode + if args.dump_wallet: + try: + if loaded_wallet._dump_wallet_file: + pass + except AttributeError: + exit("This wallet type does not currently support dumping the decrypted wallet file... (But it might support decrypting private keys (--dump-privkeys), so give that a try)") + + loaded_wallet._dump_wallet_file = args.dump_wallet + + if args.dump_privkeys: + try: + if loaded_wallet._dump_privkeys_file: + pass + except AttributeError: + exit("This wallet type does not currently support dumping the decrypted private keys...") + + loaded_wallet._dump_privkeys_file = args.dump_privkeys + + if (args.dump_privkeys or args.dump_wallet) and \ + (args.correct_wallet_password or args.correct_wallet_secondpassword) and \ + (not (args.passwordlist or args.tokenlist or args.performance)): + print("\nDumping Wallet File or Keys...") + if args.correct_wallet_secondpassword: + result, count = loaded_wallet.return_verified_password_or_false([args.correct_wallet_secondpassword]) + elif args.correct_wallet_password: + result, count = loaded_wallet.return_verified_password_or_false([args.correct_wallet_password]) + + if result: + print("\nWallet successfully dumped...") else: - mnemonic = None + print("\nUnable to decrypt wallet, likely due to incorrect password..") + + exit() + + + if args.disable_save_possible_passwords: + loaded_wallet._savepossiblematches = False + else: + try: + loaded_wallet._possible_passwords_file = args.possible_passwords_file + loaded_wallet.init_logfile() + except AttributeError: # Not all wallet types will automatically prodce a logfile + pass + + ############################## + # OpenCL related arguments + ############################## + + try: #This will fail during some older unit tests if there is no loaded wallet + loaded_wallet.opencl = False + loaded_wallet.opencl_algo = -1 + loaded_wallet.opencl_context_pbkdf2_sha1 = -1 + except AttributeError: + pass + + # Parse and syntax check all of the GPU related options + if args.enable_opencl: + try: + if(len(loaded_wallet.btcrseed_wallet._path_indexes) > 1): + print("=======================================================================") + print() + print("Performance Warning:\n" + "OpenCL Acceleration for BIP39 Passphrase (or Electrum extra words" + "is very sensitive to extra CPU load, this can dramaticaly slow things down " + "You are currently checking multiple derivation paths, (this is the default) " + "and if you know which derivation path your wallet used, you should disable " + "all unnecessary paths\n" + "See https://btcrecover.readthedocs.io/en/latest/bip39-accounts-and-altcoins/") + print() + print("=======================================================================") + + if(loaded_wallet.btcrseed_wallet._addrs_to_generate > 1): + print("=======================================================================") + print() + print("Performance Warning:\n" + "OpenCL Acceleration for BIP39 Passphrase (or Electrum extra words" + "is very sensitive to extra CPU load, this can dramaticaly slow things down" + "You have selected an address generation limit greater than 1," + "and this may not be required depending on your wallet type" + "See https://btcrecover.readthedocs.io/en/latest/Seedrecover_Quick_Start_Guide/#running-seedrecoverpy") + print() + print("=======================================================================") + except: + pass + if args.warpwallet: + print("=======================================================================") + print() + print("Warning: Warpwallet GPU doesn't accelerate the sCrypt portion of hashing, " + "so will seem unresponsive for large chunks of time (~15 minutes) " + "and isn't any faster than pure CPU processing") + print() + print("=======================================================================") + try: + if loaded_wallet._iter_count == 0: # V0 blockchain wallets have an iter_count of zero and don't benefit from GPU acceleration... + print("ERROR: The version of your blockchain.com wallet doesn't support OpenCL acceleration, this cannot changed. Please disable it and try again...") + exit() + except AttributeError: #BIP39 wallets don't have an iter_count in the same way as other wallets + pass + # Force the multiprocessing mode so that OpenCL will still be happy to run multiple threads. (Otherwise it crashes in Linux) + try: + multiprocessing.set_start_method('spawn') + except RuntimeError: # Catch and ignore error if running multiple phases + pass + print() + print("OpenCL: Available Platforms") + info = opencl_information() + info.printplatforms() + print() + if not hasattr(loaded_wallet, "_return_verified_password_or_false_opencl"): + if args.bip39: + error_exit("Wallet Type: " + loaded_wallet.__class__.__name__ + " does not support OpenCL acceleration for Passphrase Recovery") + else: + error_exit("Wallet Type: " + loaded_wallet.__class__.__name__ + " does not support OpenCL acceleration") + + loaded_wallet.opencl = True + # Append GPU related arguments to be sent to BTCrpass + + # + if args.opencl_platform: + loaded_wallet.opencl_platform = args.opencl_platform[0] + loaded_wallet.opencl_device_worksize = 0 + for device in pyopencl.get_platforms()[args.opencl_platform[0]].get_devices(): + if device.max_work_group_size > loaded_wallet.opencl_device_worksize: + loaded_wallet.opencl_device_worksize = device.max_work_group_size + # + # Else if specific devices weren't requested, try to build a good default list + else: + btcrecover.opencl_helpers.auto_select_opencl_platform(loaded_wallet) + + print("OpenCL: Using Platform:", loaded_wallet.opencl_platform) + + if args.opencl_devices: + loaded_wallet.opencl_devices = args.opencl_devices.split(",") + loaded_wallet.opencl_devices = [int(x) for x in loaded_wallet.opencl_devices] + if max(loaded_wallet.opencl_devices) > (len(pyopencl.get_platforms()[loaded_wallet.opencl_platform].get_devices()) - 1): + print("Error: Invalid OpenCL device selected") + exit() + + loaded_wallet.opencl_algo = 0 + + if args.opencl_workgroup_size: + loaded_wallet.opencl_device_worksize = args.opencl_workgroup_size[0] + loaded_wallet.chunksize = args.opencl_workgroup_size[0] + else: + if args.bip38_enc_privkey: + # Optimal Chunksize Examples + # NVidia MX250 2GB = 7 (~7 p/s Slower than CPU in the same PC...) + # NVidia 1660Ti 6GB = 16 (~18.5 p/s Almost identical CPU in the same PC...) + # NVidia 3090 24GB = 16 (~54 p/s for 2x GPUs, 27 p/s for one, both GPUs make it faster then the 24 core CPU that gets ~31 p/s... Scales nicely with 6x 3090s to ~155 p/s... + # Seems like chunksize of 16 is basically optimal and needs ~2gb VRAM per thread...Increasing chunksize beyond 16 gives no performance benefit and hits workgroup limits... + # Probably just worth leaving it at 16 and exiting if less than a 6gb GPU... (As performance won't be worthwhile anyway) + device_min_vmem = 99999 + for device in pyopencl.get_platforms()[loaded_wallet.opencl_platform].get_devices(): + if (device.global_mem_size / 1073741824.0) < device_min_vmem: + device_min_vmem = device.global_mem_size / 1073741824.0 + print("OpenCL: Minimum GPU Memory Available for platform:", device_min_vmem, "GB") + if device_min_vmem < 6: + print("OpenCL: Insufficient GPU Memory for sCrypt Acceleration... Exiting...") + print("You can force OpenCL Acceleration by manually specifying a --opencl-workgroup-size to something like 7 (As opposed to the normal 16) or by using less CPU threads, so try 1 (As opposed to the normal 2)") + print("Note: Even if this doesn't hang or crash, it will likely run slower than your CPU...") + exit() + else: + print("OpenCL: Sufficient GPU VRAM for sCrypt... Ok to run!") + loaded_wallet.chunksize = 16 + else: + loaded_wallet.chunksize = loaded_wallet.opencl_device_worksize - args.wallet_type = args.wallet_type.strip().lower() if args.wallet_type else "bitcoin" - loaded_wallet = WalletBIP39(args.mpk, args.addrs, args.addr_limit, args.addressdb, mnemonic, - args.language, args.bip32_path, args.wallet_type, args.performance) + print("OpenCL: Using Work Group Size: ", loaded_wallet.chunksize) + + if ( + not threads_specified_by_user + and (args.btcrseed or args.bip39) + ): + try: + platform_devices = pyopencl.get_platforms()[ + loaded_wallet.opencl_platform + ].get_devices() + except Exception: + platform_devices = [] + + if platform_devices: + device_indices = getattr(loaded_wallet, "opencl_devices", None) + if device_indices: + unique_indices = [] + for index in device_indices: + if ( + isinstance(index, int) + and 0 <= index < len(platform_devices) + and index not in unique_indices + ): + unique_indices.append(index) + else: + unique_indices = list(range(len(platform_devices))) + + total_vram_bytes = 0 + for index in unique_indices: + device = platform_devices[index] + if device.type & ( + pyopencl.device_type.GPU + | pyopencl.device_type.ACCELERATOR + ): + total_vram_bytes += device.global_mem_size + + if total_vram_bytes: + threads_by_vram = total_vram_bytes // BIP39_OPENCL_MEMORY_PER_THREAD_BYTES + if threads_by_vram == 0: + threads_by_vram = 1 + recommended_threads = min(logical_cpu_cores, threads_by_vram) + if recommended_threads < 1: + recommended_threads = 1 + + if recommended_threads < args.threads: + total_vram_gb = total_vram_bytes / (1024 ** 3) + per_thread_gb = ( + BIP39_OPENCL_MEMORY_PER_THREAD_BYTES / (1024 ** 3) + ) + print( + "OpenCL: Defaulting to {} worker {} based on {:.2f} GB of GPU memory (~{:.1f} GB per thread)".format( + recommended_threads, + "threads" + if recommended_threads != 1 + else "thread", + total_vram_gb, + per_thread_gb, + ) + ) + args.threads = recommended_threads + print() # Parse and syntax check all of the GPU related options - if args.enable_gpu or args.calc_memory: + if args.enable_gpu: if not hasattr(loaded_wallet, "init_opencl_kernel"): - error_exit(loaded_wallet.__class__.__name__ + " does not support GPU acceleration") - if isinstance(loaded_wallet, WalletBitcoinCore) and args.calc_memory: - error_exit("--calc-memory is not supported for Bitcoin Core wallets") - devices_avail = get_opencl_devices() # all available OpenCL device objects + error_exit(loaded_wallet.__class__.__name__ + " does not support GPU acceleration (Though it might support OpenCL acceleration using your GPU, so try --enable-opencl)") + devices_avail = list(get_opencl_devices()) # all available OpenCL device objects if not devices_avail: error_exit("no supported GPUs found") if args.int_rate <= 0: @@ -3484,7 +7206,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # Create a list of names of available devices, exactly the same way as --list-gpus except all lower case avail_names = [] # will be the *names* of available devices for i, dev in enumerate(devices_avail, 1): - avail_names.append("#"+unicode(i)+" "+dev.name.strip().lower()) + avail_names.append("#"+str(i)+" "+dev.name.strip().lower()) # devices = [] # will be the list of devices to actually use, taken from devices_avail for device_name in args.gpu_names: # for each name specified at the command line @@ -3504,7 +7226,9 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, best_score_sofar = -1 for dev in devices_avail: cur_score = 0 - if dev.type & pyopencl.device_type.ACCELERATOR: cur_score += 8 # always best + if dev.type & pyopencl.device_type.ACCELERATOR: + if "oclgrind" not in device.name.lower(): # Some simulators present as an accelerator... + cur_score += 8 # always best elif dev.type & pyopencl.device_type.GPU: cur_score += 4 # better than CPU if "nvidia" in dev.vendor.lower(): cur_score += 2 # is never an IGP: very good elif "amd" in dev.vendor.lower(): cur_score += 1 # sometimes an IGP: good @@ -3531,49 +7255,43 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # Check the values of --global-ws and --local-ws local_ws_warning = False if args.local_ws[0] is not None: # if one is specified, they're all specified - for i in xrange(len(args.local_ws)): + for i in range(len(args.local_ws)): if args.local_ws[i] < 1: error_exit("each --local-ws must be a postive integer") if args.local_ws[i] > devices[i].max_work_group_size: error_exit("--local-ws of", args.local_ws[i], "exceeds max of", devices[i].max_work_group_size, "for GPU '"+devices[i].name.strip()+"'") if args.global_ws[i] % args.local_ws[i] != 0: - error_exit("each --global-ws ("+unicode(args.global_ws[i])+") must be evenly divisible by its --local-ws ("+unicode(args.local_ws[i])+")") + error_exit("each --global-ws ("+str(args.global_ws[i])+") must be evenly divisible by its --local-ws ("+str(args.local_ws[i])+")") if args.local_ws[i] % 32 != 0 and not local_ws_warning: - print(prog+": warning: each --local-ws should probably be divisible by 32 for good performance", file=sys.stderr) + print("Warning: each --local-ws should probably be divisible by 32 for good performance", file=sys.stderr) local_ws_warning = True for ws in args.global_ws: if ws < 1: error_exit("each --global-ws must be a postive integer") - if isinstance(loaded_wallet, WalletArmory) and ws % 4 != 0: - error_exit("each --global-ws must be divisible by 4 for Armory wallets") if ws % 32 != 0: - print(prog+": warning: each --global-ws should probably be divisible by 32 for good performance", file=sys.stderr) + print("Warning: each --global-ws should probably be divisible by 32 for good performance", file=sys.stderr) break - # - extra_opencl_args = () - if isinstance(loaded_wallet, WalletBitcoinCore): - if args.mem_factor != 1: - print(prog+": warning: --mem-factor is ignored for Bitcoin Core wallets", file=sys.stderr) - elif isinstance(loaded_wallet, WalletArmory): - if args.mem_factor < 1: - error_exit("--mem-factor must be >= 1") - extra_opencl_args = args.mem_factor, args.calc_memory - loaded_wallet.init_opencl_kernel(devices, args.global_ws, args.local_ws, args.int_rate, *extra_opencl_args) - if args.threads != parser.get_default("threads"): - print(prog+": warning: --threads is ignored with --enable-gpu", file=sys.stderr) - args.threads = 1 + + if args.enable_gpu: + #If we are dealing with a Bitcoin Core wallet + if args.threads != parser.get_default("threads"): + print("Warning: --threads ignored for GPU based Bitcoin Core recovery", file=sys.stderr) + args.threads = 1 + extra_opencl_args = () + loaded_wallet.init_opencl_kernel(devices, args.global_ws, args.local_ws, args.int_rate, *extra_opencl_args) # # if not --enable-gpu: sanity checks else: - for argkey in "gpu_names", "global_ws", "local_ws", "int_rate", "mem_factor": + for argkey in "gpu_names", "global_ws", "local_ws", "int_rate": if args.__dict__[argkey] != parser.get_default(argkey): - print(prog+": warning: --"+argkey.replace("_", "-"), "is ignored without --enable-gpu", file=sys.stderr) + print("Warning: --"+argkey.replace("_", "-"), "is ignored without --enable-gpu", file=sys.stderr) # If specified, use a custom base password generator instead of a tokenlist or passwordlist file global base_password_generator, has_any_wildcards if base_iterator: - assert not args.passwordlist, "can't specify --passwordlist with base_iterator" + if args.seedgenerator is False: + assert not args.passwordlist, "can't specify --passwordlist with base_iterator" # (--tokenlist is already excluded by argparse when base_iterator is specified) base_password_generator = base_iterator has_any_wildcards = args.has_wildcards # allowed if requested @@ -3598,13 +7316,20 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # initial portion. If we manage to read up until EOF, then we won't need to disable ETA features. # TODO: support --autosave with --passwordlist files and short stdin inputs global passwordlist_file, initial_passwordlist, passwordlist_allcached - passwordlist_file = open_or_use(args.passwordlist, "r", kwds.get("passwordlist"), + passwordlist_file = open_or_use(args.passwordlist, "r", provided_passwordlist, permit_stdin=True, decoding_errors="replace") + try: + loaded_wallet.passwordlist_file = args.passwordlist # There are some instance where the generator will be initialised without a loaded wallet, so ignore these + except AttributeError: + pass + if passwordlist_file: initial_passwordlist = [] passwordlist_allcached = False has_any_wildcards = False base_password_generator = passwordlist_base_password_generator + if passwordlist_embedded_arguments and passwordlist_file != sys.stdin: + passwordlist_file.readline() # if passwordlist_file == sys.stdin: passwordlist_isatty = sys.stdin.isatty() @@ -3614,10 +7339,10 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, else: print("Reading passwordlist from stdin") # - for line_num in xrange(1, 1000000): + for line_num in range(1, 1000000): line = passwordlist_file.readline() eof = not line - line = line.rstrip(tstr("\r\n")) + line = line.strip("\r\n") if eof or passwordlist_isatty and line == "exit()": passwordlist_allcached = True break @@ -3628,7 +7353,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, line = None # add a None to the list so we can count line numbers correctly if args.has_wildcards and "%" in line: count_or_error_msg = count_valid_wildcards(line, permit_contracting_wildcards=True) - if isinstance(count_or_error_msg, basestring): + if isinstance(count_or_error_msg, str): passwordlist_warn(None if passwordlist_isatty else line_num, count_or_error_msg) line = None # add a None to the list so we can count line numbers correctly else: @@ -3642,7 +7367,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # if not passwordlist_allcached and not args.no_eta: # ETA calculations require that the passwordlist file is seekable or all in RAM - print(prog+": warning: --no-eta has been enabled because --passwordlist is stdin and is large", file=sys.stderr) + print("Warning: --no-eta has been enabled because --passwordlist is stdin and is large", file=sys.stderr) args.no_eta = True # if not passwordlist_allcached and args.has_wildcards: @@ -3653,11 +7378,11 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, if args.no_eta: # always true for --listpass and --performance if not args.no_dupchecks: if args.performance: - print(prog+": warning: --performance without --no-dupchecks will eventually cause an out-of-memory error", file=sys.stderr) + print("Warning: --performance without --no-dupchecks will eventually cause an out-of-memory error", file=sys.stderr) elif not args.listpass: - print(prog+": warning: --no-eta without --no-dupchecks can cause out-of-memory failures while searching", file=sys.stderr) + print("Warning: --no-eta without --no-dupchecks can cause out-of-memory failures while searching", file=sys.stderr) if args.max_eta != parser.get_default("max_eta"): - print(prog+": warning: --max-eta is ignored with --no-eta, --listpass, or --performance", file=sys.stderr) + print("Warning: --max-eta is ignored with --no-eta, --listpass, or --performance", file=sys.stderr) # If we're using a tokenlist file, call parse_tokenlist() to parse it. @@ -3694,7 +7419,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, sha1 = hashlib.sha1() if savestate else None try: for excluded_pw in exclude_file: - excluded_pw = excluded_pw.rstrip(tstr("\r\n")) + excluded_pw = excluded_pw.strip("\r\n") check_chars_range(excluded_pw, "--exclude-passwordlist file") password_dups.exclude(excluded_pw) # now is_duplicate(excluded_pw) will always return True if sha1: @@ -3712,16 +7437,36 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, exclude_passwordlist_hash = sha1.digest() del sha1 if restored: - if exclude_passwordlist_hash != savestate[b"exclude_passwordlist_hash"]: + if exclude_passwordlist_hash != savestate["exclude_passwordlist_hash"]: error_exit("can't restore previous session: the exclude-passwordlist file has changed") else: - savestate[b"exclude_passwordlist_hash"] = exclude_passwordlist_hash + savestate["exclude_passwordlist_hash"] = exclude_passwordlist_hash # # Normally password_dups isn't even created when --no-dupchecks is specified, but it's required # for exclude-passwordlist; instruct the password_dups to disable future duplicate checking if args.no_dupchecks: password_dups.disable_duplicate_tracking() + if not disable_security_warnings: + # Print a security warning before giving users the chance to enter ir seed.... + # Also a good idea to keep this warning as late as possible in terms of not needing it to be display for --version --help, or if there are errors in other parameters. + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + print( + "Most crypto wallet software and hardware wallets go to great lengths to protect your wallet password, seed phrase and private keys. BTCRecover isn't designed to offer this level of security, so it is possible that malware on your PC could gain access to this sensitive information while it is stored in memory in the use of this tool...") + print() + print( + "As a precaution, you should run this tool in a secure, offline environment and not simply use your normal, internet connected desktop environment... At the very least, you should disconnect your PC from the network and only reconnect it after moving your funds to a new seed... (Or if you run the tool on your internet conencted PC, move it to a new seed as soon as practical)") + print() + print("You can disable this message by running this tool with the --dsw argument") + print() + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + # If something has been redirected to stdin and we've been reading from it, close # stdin now so we don't keep the redirected files alive while running, but only @@ -3740,7 +7485,7 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, ) and not pause_registered ): sys.stdin.close() # this doesn't really close the fd try: os.close(0) # but this should, where supported - except StandardError: pass + except Exception: pass if tokenlist_file and not (pause_registered and tokenlist_file == sys.stdin): tokenlist_file.close() @@ -3751,25 +7496,26 @@ def parse_arguments(effective_argv, wallet = None, base_iterator = None, # running_hash -- (opt.) adds the map's data to the hash object # feature_name -- (opt.) used to generate more descriptive error messages # same_permitted -- (opt.) if True, the input value may be mapped to the same output value -def parse_mapfile(map_file, running_hash = None, feature_name = b"map", same_permitted = False): +def parse_mapfile(map_file, running_hash = None, feature_name = "map", same_permitted = False): map_data = dict() try: for line_num, line in enumerate(map_file, 1): - if line.startswith(b"#"): continue # ignore comments + if line.startswith("#"): continue # ignore comments # # Remove the trailing newline, then split the line exactly # once on the specified delimiter (default: whitespace) - split_line = line.rstrip(tstr("\r\n")).split(args.delimiter, 1) + if args.delimiter: args.delimiter = args.delimiter.encode().decode('unicode_escape') + split_line = line.strip("\r\n").split(args.delimiter, 1) if split_line in ([], [tstr('')]): continue # ignore empty lines if len(split_line) == 1: - error_exit(feature_name, b"file '"+map_file.name+b"' has an empty replacement list on line", line_num) + error_exit(feature_name, "file '"+map_file.name+"' has an empty replacement list on line", line_num) if args.delimiter is None: split_line[1] = split_line[1].rstrip() # ignore trailing whitespace by default - check_chars_range(tstr().join(split_line), feature_name + b" file" + (b" '" + map_file.name + b"'" if hasattr(map_file, "name") else b"")) + check_chars_range(tstr().join(split_line), feature_name + " file" + (" '" + map_file.name + "'" if hasattr(map_file, "name") else "")) for c in split_line[0]: # (c is the character to be replaced) - replacements = duplicates_removed(map_data.get(c, tstr()) + split_line[1]) + replacements = duplicates_removed(str(map_data.get(c, tstr())) + split_line[1]) if not same_permitted and c in replacements: - map_data[c] = filter(lambda r: r != c, replacements) + map_data[c] = "".join(list(filter(lambda r: r != c, replacements))) else: map_data[c] = replacements finally: @@ -3785,7 +7531,6 @@ def parse_mapfile(map_file, running_hash = None, feature_name = b"map", same_per return map_data - ################################### Tokenfile Parsing ################################### @@ -3844,14 +7589,14 @@ class AnchoredToken(object): MIDDLE = 3 # has .begin and .end attributes def __init__(self, token, line_num = "?"): - if token.startswith(b"^"): + if token.startswith("^"): # If it is a syntactically correct positional, relative, or middle anchor - match = re.match(br"\^(?:(?P\d+)?(?P,)(?P\d+)?|(?P[rR])?(?P\d+))[\^$]", token) + match = re.match(r"\^(?:(?P\d+)?(?P,)(?P\d+)?|(?P[rR])?(?P\d+))[\^$]", token) if match: # If it's a middle (ranged) anchor - if match.group(b"middle"): - begin = match.group(b"begin") - end = match.group(b"end") + if match.group("middle"): + begin = match.group("begin") + end = match.group("end") cached_str = tstr("^") # begin building the cached __str__ if begin is None: begin = 2 @@ -3861,7 +7606,7 @@ def __init__(self, token, line_num = "?"): cached_str += tstr(begin) cached_str += tstr(",") if end is None: - end = sys.maxint + end = sys.maxsize else: end = int(end) cached_str += tstr(end) @@ -3872,13 +7617,13 @@ def __init__(self, token, line_num = "?"): error_exit("anchor range of token on line", line_num, "must begin with 2 or greater") self.type = AnchoredToken.MIDDLE self.begin = begin - 1 - self.end = end - 1 if end != sys.maxint else end + self.end = end - 1 if end != sys.maxsize else end # # If it's a positional or relative anchor - elif match.group(b"pos"): - pos = int(match.group(b"pos")) + elif match.group("pos"): + pos = int(match.group("pos")) cached_str = tstr("^") # begin building the cached __str__ - if match.group(b"rel"): + if match.group("rel"): cached_str += tstr("r") + tstr(pos) + tstr("^") self.type = AnchoredToken.RELATIVE self.pos = pos @@ -3897,24 +7642,24 @@ def __init__(self, token, line_num = "?"): # # Else it's a begin anchor else: - if len(token) > 1 and token[1] in b"0123456789,": - print(prog+": warning: token on line", line_num, "looks like it might be a positional or middle anchor, " + + if len(token) > 1 and token[1] in "0123456789,": + print("Warning: token on line", line_num, "looks like it might be a positional or middle anchor, " + "but it can't be parsed correctly, so it's assumed to be a simple beginning anchor instead", file=sys.stderr) - if len(token) > 2 and token[1].lower() == b"r" and token[2] in b"0123456789": - print(prog+": warning: token on line", line_num, "looks like it might be a relative anchor, " + + if len(token) > 2 and token[1].lower() == "r" and token[2] in "0123456789": + print("Warning: token on line", line_num, "looks like it might be a relative anchor, " + "but it can't be parsed correctly, so it's assumed to be a simple beginning anchor instead", file=sys.stderr) cached_str = tstr("^") # begin building the cached __str__ self.type = AnchoredToken.POSITIONAL self.pos = 0 self.text = token[1:] # - if self.text.endswith(b"$"): + if self.text.endswith("$"): error_exit("token on line", line_num, "is anchored with both ^ at the beginning and $ at the end") # cached_str += self.text # finish building the cached __str__ # # Parse end anchor if present - elif token.endswith(b"$"): + elif token.endswith("$"): cached_str = token self.type = AnchoredToken.POSITIONAL self.pos = b"$" @@ -3922,10 +7667,10 @@ def __init__(self, token, line_num = "?"): # else: raise ValueError("token passed to AnchoredToken constructor is not an anchored token") # - self.cached_str = intern(cached_str) if type(cached_str) is str else cached_str + self.cached_str = sys.intern(cached_str) if type(cached_str) is str else cached_str self.cached_hash = hash(self.cached_str) if self.text == "": - print(prog+": warning: token on line", line_num, "contains only an anchor (and zero password characters)", file=sys.stderr) + print("Warning: token on line", line_num, "contains only an anchor (and zero password characters)", file=sys.stderr) # For sets def __hash__(self): return self.cached_hash @@ -3933,9 +7678,9 @@ def __eq__(self, other): return isinstance(other, AnchoredToken) and self.ca def __ne__(self, other): return not isinstance(other, AnchoredToken) or self.cached_str != other.cached_str # For sort (so that tstr() can be used as the key function) def __str__(self): return str(self.cached_str) - def __unicode__(self): return unicode(self.cached_str) + def __unicode__(self): return str(self.cached_str) # For hashlib - def __repr__(self): return self.__class__.__name__ + b"(" + repr(self.cached_str) + b")" + def __repr__(self): return self.__class__.__name__ + "(" + repr(self.cached_str) + ")" def parse_tokenlist(tokenlist_file, first_line_num = 1): global token_lists @@ -3951,9 +7696,9 @@ def parse_tokenlist(tokenlist_file, first_line_num = 1): for line_num, line in enumerate(tokenlist_file, first_line_num): # Ignore comments - if line.startswith(b"#"): - if re.match(b"#\s*--", line, re.UNICODE): - print(prog+": warning: all options must be on the first line, ignoring options on line", unicode(line_num), file=sys.stderr) + if line.startswith("#"): + if re.match(r"#\s*--", line, re.UNICODE): + print("Warning: all options must be on the first line, ignoring options on line", str(line_num), file=sys.stderr) continue # Start off assuming these tokens are optional (no preceding "+"); @@ -3962,7 +7707,27 @@ def parse_tokenlist(tokenlist_file, first_line_num = 1): # Remove the trailing newline, then split the line on the # specified delimiter (default: whitespace) to get a list of tokens - new_list.extend( line.rstrip(tstr("\r\n")).split(args.delimiter) ) + new_list.extend( line.strip("\r\n").split(args.delimiter) ) + + # A simple fix to handle the situation where someone has a space in a custom expanding wildcard + temp_new_list = [None] + tempToken = None + for token in new_list: + if token is None: continue + if "%" in token[:5] and "[" in token[:5] and "]" not in token: + tempToken = token + continue + + if tempToken is not None: + delimiter = " " + if args.delimiter is not None: + delimiter = args.delimiter + token = tempToken + delimiter + token + tempToken = None + + temp_new_list.append(token) + + new_list = temp_new_list # Ignore empty lines if new_list in ([None], [None, tstr('')]): continue @@ -3970,35 +7735,35 @@ def parse_tokenlist(tokenlist_file, first_line_num = 1): # If a "+" is present at the beginning followed by at least one token, # then exactly one of the token(s) is required. This is noted in the structure # by removing the preceding None we added above (and also delete the "+") - if new_list[1] == b"+" and len(new_list) > 2: + if new_list[1] == "+" and len(new_list) > 2: del new_list[0:2] # Check token syntax and convert any anchored tokens to an AnchoredToken object for i, token in enumerate(new_list): if token is None: continue - check_chars_range(token, "token on line " + unicode(line_num)) + check_chars_range(token, "token on line " + str(line_num)) # Syntax check any wildcards, and load any wildcard backreference maps count_or_error_msg = count_valid_wildcards(token, permit_contracting_wildcards=True) - if isinstance(count_or_error_msg, basestring): - error_exit("on line", unicode(line_num)+":", count_or_error_msg) + if isinstance(count_or_error_msg, str): + error_exit("on line", str(line_num)+":", count_or_error_msg) elif count_or_error_msg: has_any_wildcards = True # (a global) load_backreference_maps_from_token(token) # Check for tokens which look suspiciously like command line options # (using a private ArgumentParser member func is asking for trouble...) - if token.startswith(b"--") and parser_common._get_option_tuples(token): + if token.startswith("--") and parser_common._get_option_tuples(token): if line_num == 1: - print(prog+": warning: token on line 1 looks like an option, " + print("Warning: token on line 1 looks like an option, " "but line 1 did not start like this: #--option1 ...", file=sys.stderr) else: - print(prog+": warning: token on line", unicode(line_num), "looks like an option, " + print("Warning: token on line", str(line_num), "looks like an option, " " but all options must be on the first line", file=sys.stderr) # Parse anchor if present and convert to an AnchoredToken object - if token.startswith(b"^") or token.endswith(b"$"): + if token.startswith("^") or token.endswith("$"): token = AnchoredToken(token, line_num) # (the line_num is just for error messages) new_list[i] = token has_any_anchors = True @@ -4024,17 +7789,17 @@ def parse_tokenlist(tokenlist_file, first_line_num = 1): # restoring the exact same session, or save them for future such checks if savestate: global backreference_maps_sha1 - token_lists_hash = hashlib.sha1(repr(token_lists)).digest() + token_lists_hash = hashlib.sha1(repr(token_lists).encode('utf-8')).digest() backreference_maps_hash = backreference_maps_sha1.digest() if backreference_maps_sha1 else None if restored: - if token_lists_hash != savestate[b"token_lists_hash"]: + if token_lists_hash != savestate["token_lists_hash"]: error_exit("can't restore previous session: the tokenlist file has changed") - if backreference_maps_hash != savestate.get(b"backreference_maps_hash"): + if backreference_maps_hash != savestate.get("backreference_maps_hash"): error_exit("can't restore previous session: one or more backreference maps have changed") else: - savestate[b"token_lists_hash"] = token_lists_hash + savestate["token_lists_hash"] = token_lists_hash if backreference_maps_hash: - savestate[b"backreference_maps_hash"] = backreference_maps_hash + savestate["backreference_maps_hash"] = backreference_maps_hash # Load any map files referenced in wildcard backreferences in the passed token @@ -4043,12 +7808,12 @@ def load_backreference_maps_from_token(token): global backreference_maps_sha1 # initialized to None in init_wildcards() # We know all wildcards present have valid syntax, so we don't need to use the full regex, but # we do need to capture %% to avoid parsing this as a backreference (it isn't one): %%;file;b - for map_filename in re.findall(br"%[\d,]*;(.+?);\d*b|%%", token): + for map_filename in re.findall(r"%[\d,]*;(.+?);\d*b|%%", token): if map_filename and map_filename not in backreference_maps: if savestate and not backreference_maps_sha1: backreference_maps_sha1 = hashlib.sha1() backreference_maps[map_filename] = \ - parse_mapfile(open(map_filename, "r"), backreference_maps_sha1, b"backreference map", same_permitted=True) + parse_mapfile(open(map_filename, "r"), backreference_maps_sha1, "backreference map", same_permitted=True) ################################### Password Generation ################################### @@ -4058,7 +7823,7 @@ def load_backreference_maps_from_token(token): # (builds a cache in the first run to be memory efficient in future runs) class DuplicateChecker(object): - EXCLUDE = sys.maxint + EXCLUDE = sys.maxsize def __init__(self): self._seen_once = dict() # tracks potential duplicates in run 0 only @@ -4121,13 +7886,22 @@ def init_password_generator(): password_dups = token_combination_dups = None passwordlist_warnings = 0 # (re)set the min_typos argument default values to 0 - capslock_typos_generator.func_defaults = (0,) - swap_typos_generator .func_defaults = (0,) - simple_typos_generator .func_defaults = (0,) - insert_typos_generator .func_defaults = (0,) + capslock_typos_generator.__defaults__ = (0,) + swap_typos_generator .__defaults__ = (0,) + simple_typos_generator .__defaults__ = (0,) + insert_typos_generator .__defaults__ = (0,) # def password_generator(chunksize = 1, only_yield_count = False): assert chunksize > 0, "password_generator: chunksize > 0" + + generatingSeeds = False + try: + global loaded_wallet + if loaded_wallet._checksum_in_generator: + generatingSeeds = True + except: + pass + # Used to communicate between typo generators the number of typos that have been # created so far during each password generated so that later generators know how # many additional typos, at most, they are permitted to add, and also if it is @@ -4143,39 +7917,63 @@ def password_generator(chunksize = 1, only_yield_count = False): # Initialize this global if not already initialized but only # if they should be used; see its usage below for more details global password_dups - if password_dups is None and args.no_dupchecks < 1: + if password_dups is None and args.no_dupchecks < 1 and args.seedgenerator == False: password_dups = DuplicateChecker() + print("Duplicate Check Level:", args.no_dupchecks, ", Add --no-dupchecks up to 4 times fully disable duplicate checking") + print() + # Copy a few globals into local for a small speed boost l_generator_product = generator_product l_regex_only = regex_only l_regex_never = regex_never l_password_dups = password_dups l_args_worker = args.worker + l_seed_generator = args.seedgenerator + l_length_min = args.length_min + l_length_max = args.length_max + l_truncate_length = args.truncate_length + if l_args_worker: l_workers_total = workers_total l_worker_id = worker_id # Build up the modification_generators list; see the inner loop below for more details modification_generators = [] - if has_any_wildcards: modification_generators.append( expand_wildcards_generator ) - if args.typos_capslock: modification_generators.append( capslock_typos_generator ) - if args.typos_swap: modification_generators.append( swap_typos_generator ) - if enabled_simple_typos: modification_generators.append( simple_typos_generator ) - if args.typos_insert: modification_generators.append( insert_typos_generator ) - modification_generators_len = len(modification_generators) + + if has_any_wildcards: modification_generators.append( expand_wildcards_generator ) + + if l_seed_generator is False: + if args.password_repeats_pretypos: modification_generators.append( password_repeats_generator ) + if args.typos_capslock: modification_generators.append( capslock_typos_generator ) + if args.typos_swap: modification_generators.append( swap_typos_generator ) + if enabled_simple_typos: modification_generators.append( simple_typos_generator ) + if args.typos_insert: modification_generators.append( insert_typos_generator ) + if args.password_repeats_posttypos: modification_generators.append( password_repeats_generator ) # Only the last typo generator needs to enforce a min-typos requirement - if args.min_typos: - assert modification_generators[-1] != expand_wildcards_generator - # set the min_typos argument default value - modification_generators[-1].func_defaults = (args.min_typos,) + if args.min_typos and (l_seed_generator is False): + # Though this isn't applicable to the expand wildcards generator + if modification_generators[-1] != expand_wildcards_generator: + # set the min_typos argument default value + modification_generators[-1].__defaults__ = (args.min_typos,) + + # Modification generators for seed generation + if args.seed_transform_wordswaps: + modification_generators.append(swap_tokens_generator) + modification_generators[-1].__defaults__ = (args.seed_transform_wordswaps,) + if args.seed_transform_trezor_common_mistakes: + modification_generators.append(trezor_common_mistakes_generator) + modification_generators[-1].__defaults__ = ( + args.seed_transform_trezor_common_mistakes, + ) + + modification_generators_len = len(modification_generators) # The base password generator is set in parse_arguments(); it's either an iterable # or a generator function (which returns an iterator) that produces base passwords # usually based on either a tokenlist file (as parsed above) or a passwordlist file. for password_base in base_password_generator() if callable(base_password_generator) else base_password_generator: - # The for loop below takes the password_base and applies zero or more modifications # to it to produce a number of different possible variations of password_base (e.g. # different wildcard expansions, typos, etc.) @@ -4203,20 +8001,42 @@ def password_generator(chunksize = 1, only_yield_count = False): if l_regex_only and not l_regex_only .search(password): continue if l_regex_never and l_regex_never.search(password): continue + if l_length_min and (len(password)l_length_max): + #print("Skipping ",password," - too long \r", end="", flush=True) + continue + + # If it's a seed, split it up into a list + if l_seed_generator and not isinstance(password,list) and not isinstance(password,tuple): + password = password.split(" ") + # This is the check_only argument optionally passed # by external libraries to parse_arguments() if custom_final_checker and not custom_final_checker(password): continue + # Truncate password if required + password = password[0:l_truncate_length] + # This duplicate check can be disabled via --no-dupchecks # because it can take up a lot of memory, sometimes needlessly if l_password_dups and l_password_dups.is_duplicate(password): continue # Workers in a server pool ignore passwords not assigned to them if l_args_worker: - if worker_count % l_workers_total != l_worker_id: - worker_count += 1 - continue + skip_current_password = True + if (worker_count % l_workers_total) in l_worker_id: + skip_current_password = False + worker_count += 1 + if skip_current_password: + continue + + if generatingSeeds: #Skip seeds that don't have a valid BIP39 checksum + if not loaded_wallet._verify_checksum(password): + continue # Produce the password(s) or the count once enough of them have been accumulated passwords_count += 1 @@ -4270,14 +8090,70 @@ def generator_product(initial_value, generator, *other_generators): for final_value in generator_product(intermediate_value, *other_generators): yield final_value +# A recursive function that will swap one pair of words in a given mnemonic and then, +# if required, will call itself recursively to handle further possible swaps. + +# This implementation prioritises simplicity leaves it to other dup-check functions +# to handle duplicates created through repeated swaps. +# (Though isn't really an issue for small numbers of swaps either way) + +# Note: There is a bit of inconsistency in the data type of password_base depending on +# whether tokenlists/seedlists are being used. (Hence why there are a few casts between tuple and list) +def swap_tokens_generator(password_base, numSwaps = 0): + yield tuple(password_base) + password_base = list(password_base) + # If we have reached the end then simply return the base password + if numSwaps > 0: + for i, j in itertools.combinations(range(len(password_base)), 2): + swapped_seed = tuple(password_base[:i] + [password_base[j]] + password_base[i+1:j] + [password_base[i]] + password_base[j+1:]) + yield from swap_tokens_generator(swapped_seed, numSwaps - 1) + + +def trezor_common_mistakes_generator(password_base, max_mistakes = 0): + base_seed = tuple(password_base) + yield base_seed + + if max_mistakes <= 0: + return + + def recurse(current_seed, start_index, replacements_remaining): + if replacements_remaining == 0: + return + + for index in range(start_index, len(current_seed)): + word = current_seed[index] + lookup_word = word.lower() if isinstance(word, str) else word + alternatives = TREZOR_COMMON_MISTAKES.get(lookup_word, ()) + if not alternatives: + continue + + for alternative in alternatives: + if alternative == lookup_word: + continue + + updated_seed = list(current_seed) + updated_seed[index] = alternative + updated_tuple = tuple(updated_seed) + yield updated_tuple + + if replacements_remaining > 1: + yield from recurse( + updated_seed, + index + 1, + replacements_remaining - 1, + ) + + yield from recurse(list(base_seed), 0, max_mistakes) # The tokenlist generator function produces all possible password permutations from the # token_lists global as constructed by parse_tokenlist(). These passwords are then used # by password_generator() as base passwords that can undergo further modifications. def tokenlist_base_password_generator(): + # Initialize this global if not already initialized but only # if they should be used; see its usage below for more details global token_combination_dups + if token_combination_dups is None and args.no_dupchecks < 2 and has_any_duplicate_tokens: token_combination_dups = DuplicateChecker() @@ -4292,14 +8168,19 @@ def tokenlist_base_password_generator(): l_sorted = sorted l_list = list l_tstr = tstr + l_seed_generator = args.seedgenerator + l_mnemonic_length = args.mnemonic_length # Choose between the custom duplicate-checking and the standard itertools permutation # functions for the outer loop unless the custom one has been specifically disabled # with three (or more) --no-dupcheck options. - if args.no_dupchecks < 3 and has_any_duplicate_tokens: - permutations_function = permutations_nodups + if args.keep_tokens_order: + permutations_function = lambda x: [tuple(reversed(x))] else: - permutations_function = itertools.permutations + if args.no_dupchecks < 3 and has_any_duplicate_tokens: + permutations_function = permutations_nodups + else: + permutations_function = itertools.permutations # The outer loop iterates through all possible (unordered) combinations of tokens # taking into account the at-most-one-token-per-line rule. Note that lines which @@ -4312,18 +8193,20 @@ def tokenlist_base_password_generator(): # First choose which product generator to use: the custom product_limitedlen # might be faster (possibly a lot) if a large --min-tokens or any --max-tokens # is specified at the command line, otherwise use the standard itertools version. - using_product_limitedlen = l_args_min_tokens > 5 or l_args_max_tokens < sys.maxint + using_product_limitedlen = l_args_min_tokens > 5 or l_args_max_tokens < sys.maxsize if using_product_limitedlen: product_generator = product_limitedlen(*token_lists, minlen=l_args_min_tokens, maxlen=l_args_max_tokens) else: product_generator = itertools.product(*token_lists) - for tokens_combination in product_generator: + + for tokens_combination in product_generator: # Remove any None's, then check against token length constraints: # (product_limitedlen, if used, has already done all this) if not using_product_limitedlen: - tokens_combination = filter(lambda t: t is not None, tokens_combination) - if not l_args_min_tokens <= l_len(tokens_combination) <= l_args_max_tokens: continue + #tokens_combination = filter(lambda t: t is not None, tokens_combination) + tokens_combination = [x for x in tokens_combination if x is not None] + if not l_args_min_tokens <= l_len(list(tokens_combination)) <= l_args_max_tokens: continue # There are three types of anchors: positional, middle/range, & relative. Positionals # only have a single possible position; middle anchors have a range, but are never @@ -4337,7 +8220,7 @@ def tokenlist_base_password_generator(): has_any_mid_anchors = False rel_anchors_count = 0 if l_has_any_anchors: - tokens_combination_len = l_len(tokens_combination) + tokens_combination_len = l_len(list(tokens_combination)) tokens_combination_nopos = [] # all tokens except positional ones invalid_anchors = False for token in tokens_combination: @@ -4350,7 +8233,7 @@ def tokenlist_base_password_generator(): invalid_anchors = True # anchored past the end break if not positional_anchors: # initialize it to a list of None's - positional_anchors = [None for i in xrange(tokens_combination_len)] + positional_anchors = [None for i in range(tokens_combination_len)] elif positional_anchors[pos] is not None: invalid_anchors = True # two tokens anchored to the same place break @@ -4369,7 +8252,8 @@ def tokenlist_base_password_generator(): if invalid_anchors: continue # if tokens_combination_nopos == []: # if all tokens have positional anchors, - tokens_combination_nopos = ( l_tstr(""), ) # make this non-empty so a password can be created + if not args.seedgenerator: + tokens_combination_nopos = ( l_tstr(""), ) # make this non-empty so a password can be created else: tokens_combination_nopos = tokens_combination @@ -4381,7 +8265,26 @@ def tokenlist_base_password_generator(): # TODO: # Be smarter in deciding when to enable this? (currently on if has_any_duplicate_tokens) # Instead of dup checking, write a smarter product (seems hard)? - if l_token_combination_dups and \ + # TODO: + # Right now, trying to remove duplicates with this method if --keep-tokens-order is passed + # will cause some passwords to be skipped, check the following example: + # + # Token file: + # ----------- + # a b + # a b + # Passwords to try: + # ----------------- + # a + # b + # aa + # ba + # ab + # bb + # + # Trying to remove duplicates when --keep-tokens-order is passed in the above + # example will skip the 5th password "ab". Fix that. + if not args.keep_tokens_order and l_token_combination_dups and \ l_token_combination_dups.is_duplicate(l_tuple(l_sorted(tokens_combination, key=l_tstr))): continue # The inner loop iterates through all valid permutations (orderings) of one @@ -4389,7 +8292,6 @@ def tokenlist_base_password_generator(): # Because positionally anchored tokens can only appear in one position, they # are not passed to the permutations_function. for ordered_token_guess in permutations_function(tokens_combination_nopos): - # If multiple relative anchors are in a guess, they must appear in the correct # relative order. If any are out of place, we continue on to the next guess. # Otherwise, we remove the anchor information leaving only the string behind. @@ -4437,7 +8339,21 @@ def tokenlist_base_password_generator(): break if invalid_anchors: continue - yield l_tstr().join(ordered_token_guess) + if l_seed_generator: + expandedGuess = [] + for rawToken in ordered_token_guess: + expandedGuess.extend(rawToken.split(",")) + + if l_mnemonic_length is None: # If mnemonic_length hasn't been specified then skip this check + yield expandedGuess + else: + if len(expandedGuess) == l_mnemonic_length: #Only return mnemonic guesses of the expected length + yield expandedGuess + else: + break + + else: + yield l_tstr().join(ordered_token_guess) if l_token_combination_dups: l_token_combination_dups.run_finished() @@ -4453,10 +8369,10 @@ def tokenlist_base_password_generator(): # and then call do_product_limitedlen() to do the real work def product_limitedlen(*sequences, **kwds): minlen = max(kwds.get("minlen", 0), 0) # no less than 0 - maxlen = kwds.get("maxlen", sys.maxint) + maxlen = kwds.get("maxlen", sys.maxsize) if minlen > maxlen: # minlen is already >= 0 - return xrange(0).__iter__() # yields nothing at all + return range(0).__iter__() # yields nothing at all if maxlen == 0: # implies minlen == 0 because of the check above # Produce a length 0 tuple unless there's a seq which doesn't have a None @@ -4466,18 +8382,18 @@ def product_limitedlen(*sequences, **kwds): else: # if it didn't break, there was a None in every seq return itertools.repeat((), 1) # a single empty tuple # if it did break, there was a seq without a None - return xrange(0).__iter__() # yields nothing at all + return range(0).__iter__() # yields nothing at all sequences_len = len(sequences) if sequences_len == 0: if minlen == 0: # already true: minlen >= 0 and maxlen >= minlen return itertools.repeat((), 1) # a single empty tuple else: # else minlen > 0 - return xrange(0).__iter__() # yields nothing at all + return range(0).__iter__() # yields nothing at all # If there aren't enough sequences to satisfy minlen if minlen > sequences_len: - return xrange(0).__iter__() # yields nothing at all + return range(0).__iter__() # yields nothing at all # Unfortunately, do_product_limitedlen is recursive; the recursion limit # must be at least as high as sequences_len plus a small buffer @@ -4551,7 +8467,7 @@ def permutations_nodups(sequence): # Copy a global into local for a small speed boost l_len = len - sequence_len = l_len(sequence) + sequence_len = l_len(list(sequence)) # Special case for speed if sequence_len == 2: @@ -4588,8 +8504,8 @@ def passwordlist_warn(line_num, *args): if passwordlist_warnings is not None: passwordlist_warnings += 1 if passwordlist_warnings <= MAX_PASSWORDLIST_WARNINGS: - print(prog+": warning: ignoring", - "line "+unicode(line_num)+":" if line_num else "last line:", + print("Warning: ignoring", + "line "+str(line_num)+":" if line_num else "last line:", *args, file=sys.stderr) # # Produces whole passwords from a file, exactly one per line, or from the file's cache @@ -4597,48 +8513,88 @@ def passwordlist_warn(line_num, *args): # used by password_generator() as base passwords that can undergo further modifications. def passwordlist_base_password_generator(): global initial_passwordlist, passwordlist_warnings + global passwordlist_file, passwordlist_first_line_num + global loaded_wallet - line_num = 1 + line_num = passwordlist_first_line_num for password_base in initial_passwordlist: # note that these have already been syntax-checked if password_base is not None: # happens if there was a wildcard syntax error yield password_base line_num += 1 # count both valid lines and ones with syntax errors if not passwordlist_allcached: + + firstRun = True + try: + multiFile = loaded_wallet.load_multi_file_seedlist #There are some instances where this will run without a loaded wallet, in these instances, just set multi file to false + except AttributeError: + multiFile = False + file_suffix = "" + filename = args.passwordlist + file_suffix assert not passwordlist_file.closed - for line_num, password_base in enumerate(passwordlist_file, line_num): # not yet syntax-checked - password_base = password_base.rstrip(tstr("\r\n")) - try: - check_chars_range(password_base, "line", no_replacement_chars=True) - except SystemExit as e: - passwordlist_warn(line_num, e.code) - continue - if args.has_wildcards and b"%" in password_base: - count_or_error_msg = count_valid_wildcards(password_base, permit_contracting_wildcards=True) - if isinstance(count_or_error_msg, basestring): - passwordlist_warn(line_num, count_or_error_msg) + + for i in range(9999): + if multiFile: + file_suffix = "_" + '{:04d}'.format(i) + ".txt" + filename = args.passwordlist[:-9] + file_suffix + if firstRun: + firstRun = False + print("Notice: Loading File: ", filename) + else: + try: + passwordlist_file = open_or_use(filename, "r", decoding_errors="replace") + print("Notice: Loading File: ", filename) + except FileNotFoundError: continue + + for line_num, password_base in enumerate(passwordlist_file, line_num): # not yet syntax-checked + password_base = password_base.strip("\r\n") try: - load_backreference_maps_from_token(password_base) - except IOError as e: - passwordlist_warn(line_num, e) + check_chars_range(password_base, "line", no_replacement_chars=True) + except SystemExit as e: + passwordlist_warn(line_num, e.code) continue - yield password_base + if args.has_wildcards and "%" in password_base: + count_or_error_msg = count_valid_wildcards(password_base, permit_contracting_wildcards=True) + if isinstance(count_or_error_msg, str): + passwordlist_warn(line_num, count_or_error_msg) + continue + try: + load_backreference_maps_from_token(password_base) + except IOError as e: + passwordlist_warn(line_num, e) + continue + + if args.seedgenerator: + yield password_base.replace("'", "").replace(",","").strip('()[]').split(' ') # Gracefully handle seed lists files formatted as tuples, lists or just raw spaced words + else: + yield password_base + + print("Notice: Finished File: ", filename) + if not multiFile: + break + passwordlist_file.close() + + if passwordlist_warnings: if passwordlist_warnings > MAX_PASSWORDLIST_WARNINGS: - print("\n"+prog+": warning:", passwordlist_warnings-MAX_PASSWORDLIST_WARNINGS, + print("\n"+"Warning:", passwordlist_warnings-MAX_PASSWORDLIST_WARNINGS, "additional warnings were suppressed", file=sys.stderr) passwordlist_warnings = None # ignore warnings during future runs of the same passwordlist - # Prepare for a potential future run of the same passwordlist - if passwordlist_file != sys.stdin: - passwordlist_file.seek(0) + try: + # Prepare for a potential future run of the same passwordlist + if passwordlist_file != sys.stdin: + passwordlist_file.seek(0) + + # Data from stdin can't be reused if it hasn't been fully cached + elif not passwordlist_allcached: + initial_passwordlist = () + passwordlist_file.close() + except ValueError: #This exception will be thrown if we are reading a multi-file seedlist, as the file was closed earlier + pass - # Data from stdin can't be reused if it hasn't been fully cached - elif not passwordlist_allcached: - initial_passwordlist = () - passwordlist_file.close() # Produces an infinite number of base passwords for performance measurements. These passwords @@ -4655,6 +8611,9 @@ def default_performance_base_password_generator(): # prior_prefix + password_with_all_wildcards_expanded # TODO: implement without recursion? def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): + if isinstance(password_with_wildcards, list): + password_with_wildcards = " ".join(password_with_wildcards) + if prior_prefix is None: prior_prefix = tstr() # Quick check to see if any wildcards are present @@ -4663,8 +8622,23 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): yield prior_prefix + password_with_wildcards return + # %e and %f are special types of wildcards which can both be customised AND can occur multiple times, but always have the same value + if "%e" in password_with_wildcards: + for wildcard in wildcard_sets["e"]: + loop_password_with_wildcards = password_with_wildcards.replace("%e", wildcard) + for password_expanded in expand_wildcards_generator(loop_password_with_wildcards): + yield password_expanded + return + + if "%f" in password_with_wildcards: + for wildcard in wildcard_sets["f"]: + loop_password_with_wildcards = password_with_wildcards.replace("%f", wildcard) + for password_expanded in expand_wildcards_generator(loop_password_with_wildcards): + yield password_expanded + return + # Copy a few globals into local for a small speed boost - l_xrange = xrange + l_range = range l_len = len l_min = min l_max = max @@ -4676,7 +8650,7 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): global wildcard_re if not wildcard_re: wildcard_re = re.compile( - br"%(?:(?:(?P\d+),)?(?P\d+))?(?Pi)?(?:(?P[{}<>-])|\[(?P.+?)\]|(?:;(?:(?P.+?);)?(?P\d+)?)?(?Pb))" \ + r"%(?:(?:(?P\d+),)?(?P\d+))?(?Pi)?(?:(?P[{}<>-])|\[(?P.+?)\]|(?:;(?:(?P.+?);)?(?P\d+)?)?(?Pb))" \ .format(wildcard_keys)) match = wildcard_re.search(password_with_wildcards) assert match, "expand_wildcards_generator: parsed valid wildcard spec" @@ -4685,14 +8659,14 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): full_password_prefix = prior_prefix + password_prefix # nor here; password_postfix_with_wildcards = password_with_wildcards[match.end():] # might be other wildcards in here - m_bref = match.group(b"bref") + m_bref = match.group("bref") if m_bref: # a backreference wildcard, e.g. "%b" or "%;2b" or "%;map.txt;2b" - m_bfile, m_bpos = match.group(b"bfile", b"bpos") + m_bfile, m_bpos = match.group("bfile", "bpos") m_bpos = int(m_bpos) if m_bpos else 1 bmap = backreference_maps[m_bfile] if m_bfile else None else: # For positive (expanding) wildcards, build the set of possible characters based on the wildcard type and caseflag - m_custom, m_nocase = match.group(b"custom", b"nocase") + m_custom, m_nocase = match.group("custom", "nocase") if m_custom: # a custom set wildcard, e.g. %[abcdef0-9] is_expanding = True wildcard_set = custom_wildcard_cache.get((m_custom, m_nocase)) @@ -4705,8 +8679,8 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): wildcard_set = duplicates_removed(wildcard_set + wildcard_set_caseswapped) custom_wildcard_cache[(m_custom, m_nocase)] = wildcard_set else: # either a "normal" or a contracting wildcard - m_type = match.group(b"type") - is_expanding = m_type not in b"<>-" + m_type = match.group("type") + is_expanding = m_type not in "<>-" if is_expanding: if m_nocase and m_type in wildcard_nocase_sets: wildcard_set = wildcard_nocase_sets[m_type] @@ -4715,9 +8689,9 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): assert not is_expanding or wildcard_set, "expand_wildcards_generator: found expanding wildcard set" # Extract or default the wildcard min and max length - wildcard_maxlen = match.group(b"max") + wildcard_maxlen = match.group("max") wildcard_maxlen = int(wildcard_maxlen) if wildcard_maxlen else 1 - wildcard_minlen = match.group(b"min") + wildcard_minlen = match.group("min") wildcard_minlen = int(wildcard_minlen) if wildcard_minlen else wildcard_maxlen # If it's a backreference wildcard @@ -4754,7 +8728,7 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): else: # else it's a "normal" backreference wildcard (without a map file) # Construct the first password to be produced - for i in xrange(0, wildcard_minlen): + for i in range(0, wildcard_minlen): full_password_prefix += full_password_prefix[m_bpos] # Iterate over the [wildcard_minlen, wildcard_maxlen) range @@ -4778,11 +8752,10 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): # If it's an expanding wildcard elif is_expanding: # Iterate through specified wildcard lengths - for wildcard_len in l_xrange(wildcard_minlen, wildcard_maxlen+1): + for wildcard_len in l_range(wildcard_minlen, wildcard_maxlen+1): # Expand the wildcard into a length of characters according to the wildcard type/caseflag for wildcard_expanded_list in itertools.product(wildcard_set, repeat=wildcard_len): - # If the wildcard was at the end of the string, we're done if password_postfix_with_wildcards == "": yield full_password_prefix + tstr().join(wildcard_expanded_list) @@ -4795,19 +8768,19 @@ def expand_wildcards_generator(password_with_wildcards, prior_prefix = None): else: # Determine the max # of characters that can be removed from either the left # or the right of the wildcard, not yet taking wildcard_maxlen into account - max_from_left = l_len(password_prefix) if m_type in b"<-" else 0 - if m_type in b">-": + max_from_left = l_len(password_prefix) if m_type in "<-" else 0 + if m_type in ">-": max_from_right = password_postfix_with_wildcards.find("%") if max_from_right == -1: max_from_right = l_len(password_postfix_with_wildcards) else: max_from_right = 0 # Iterate over the total number of characters to remove - for remove_total in l_xrange(wildcard_minlen, l_min(wildcard_maxlen, max_from_left+max_from_right) + 1): + for remove_total in l_range(wildcard_minlen, l_min(wildcard_maxlen, max_from_left+max_from_right) + 1): # Iterate over the number of characters to remove from the right of the wildcard # (this loop runs just once for %#,#< or %#,#> ; or for %#,#- at the beginning or end) - for remove_right in l_xrange(l_max(0, remove_total-max_from_left), l_min(remove_total, max_from_right) + 1): + for remove_right in l_range(l_max(0, remove_total-max_from_left), l_min(remove_total, max_from_right) + 1): remove_left = remove_total-remove_right password_prefix_contracted = full_password_prefix[:-remove_left] if remove_left else full_password_prefix @@ -4842,7 +8815,6 @@ def expand_mapping_backreference_wildcard(password_prefix, minlen, maxlen, bpos, # to the password_base itself) def capslock_typos_generator(password_base, min_typos = 0): global typos_sofar - min_typos -= typos_sofar if min_typos > 1: return # this generator can't ever generate more than 1 typo @@ -4864,7 +8836,7 @@ def capslock_typos_generator(password_base, min_typos = 0): def swap_typos_generator(password_base, min_typos = 0): global typos_sofar # Copy a few globals into local for a small speed boost - l_xrange = xrange + l_range = range l_itertools_combinations = itertools.combinations l_args_nodupchecks = args.no_dupchecks @@ -4877,18 +8849,18 @@ def swap_typos_generator(password_base, min_typos = 0): # max number swappable is len // 2 because we never swap any single character twice. password_base_len = len(password_base) max_swaps = min(args.max_typos_swap, args.typos - typos_sofar, password_base_len // 2) - for swap_count in l_xrange(max(1, min_typos), max_swaps + 1): + for swap_count in l_range(max(1, min_typos), max_swaps + 1): typos_sofar += swap_count # Generate all possible combinations of swapping exactly swap_count characters; # swap_indexes is a list of indexes of characters that will be swapped in a # single guess (swapped with the character at the next position in the string) - for swap_indexes in l_itertools_combinations(l_xrange(password_base_len-1), swap_count): + for swap_indexes in l_itertools_combinations(l_range(password_base_len-1), swap_count): # Look for adjacent indexes in swap_indexes (which would cause a single # character to be swapped more than once in a single guess), and only # continue if no such adjacent indexes are found - for i in l_xrange(1, swap_count): + for i in l_range(1, swap_count): if swap_indexes[i] - swap_indexes[i-1] == 1: break else: # if we left the loop normally (didn't break) @@ -4934,7 +8906,7 @@ def case_id_changed(case_id1, case_id2): def simple_typos_generator(password_base, min_typos = 0): global typos_sofar # Copy a few globals into local for a small speed boost - l_xrange = xrange + l_range = range l_itertools_product = itertools.product l_product_max_elements = product_max_elements l_enabled_simple_typos = enabled_simple_typos @@ -4948,7 +8920,7 @@ def simple_typos_generator(password_base, min_typos = 0): # First change all single characters, then all combinations of 2 characters, then of 3, etc. password_base_len = len(password_base) max_typos = min(sum_max_simple_typos, args.typos - typos_sofar, password_base_len) - for typos_count in l_xrange(max(1, min_typos), max_typos + 1): + for typos_count in l_range(max(1, min_typos), max_typos + 1): typos_sofar += typos_count # Pre-calculate all possible permutations of the chosen simple_typos_choices @@ -4960,7 +8932,7 @@ def simple_typos_generator(password_base, min_typos = 0): # Select the indexes of exactly typos_count characters from the password_base # that will be the target of the typos (out of all possible combinations thereof) - for typo_indexes in itertools.combinations(l_xrange(password_base_len), typos_count): + for typo_indexes in itertools.combinations(l_range(password_base_len), typos_count): # typo_indexes_ has an added sentinel at the end; it's the index of # one-past-the-end of password_base. This is used in the inner loop. typo_indexes_ = typo_indexes + (password_base_len,) @@ -4968,14 +8940,14 @@ def simple_typos_generator(password_base, min_typos = 0): # Apply each possible permutation of simple typo generators to # the typo targets selected above (using the pre-calculated list) for typo_generators_per_target in simple_typo_permutations: - # For each of the selected typo target(s), call the generator(s) selected above # to get the replacement(s) of said to-be-replaced typo target(s). Each item in # typo_replacements is an iterable (tuple, list, generator, etc.) producing # zero or more replacements for a single target. If there are zero replacements # for any target, the for loop below intentionally produces no results at all. + typo_replacements = [ generator(password_base, index) for index, generator in - zip(typo_indexes, typo_generators_per_target) ] + list(zip(typo_indexes, typo_generators_per_target)) ] # one_replacement_set is a tuple of exactly typos_count length, with one # replacement per selected typo target. If all of the selected generators @@ -4985,12 +8957,12 @@ def simple_typos_generator(password_base, min_typos = 0): # combinations of those replacements. If any generator produces zero outputs # (therefore that the target has no typo), this loop iterates zero times. for one_replacement_set in l_itertools_product(*typo_replacements): - # Construct a new password, left-to-right, from password_base and the # one_replacement_set. (Note the use of typo_indexes_, not typo_indexes.) password = password_base[0:typo_indexes_[0]] for i, replacement in enumerate(one_replacement_set): password += replacement + password_base[typo_indexes_[i]+1:typo_indexes_[i+1]] + yield password typos_sofar -= typos_count @@ -5043,7 +9015,7 @@ def insert_typos_generator(password_base, min_typos = 0): global typos_sofar # Copy a few globals into local for a small speed boost l_max_adjacent_inserts = args.max_adjacent_inserts - l_xrange = xrange + l_range = range l_itertools_product = itertools.product # Start with the unmodified password itself @@ -5062,12 +9034,12 @@ def insert_typos_generator(password_base, min_typos = 0): max_inserts = min(args.max_typos_insert, args.typos - typos_sofar, password_base_len + 1) # First insert a single string, then all combinations of 2 strings, then of 3, etc. - for inserts_count in l_xrange(max(1, min_typos), max_inserts + 1): + for inserts_count in l_range(max(1, min_typos), max_inserts + 1): typos_sofar += inserts_count # Select the indexes (some possibly the same) of exactly inserts_count characters # from the password_base before which new string(s) will be inserted - for insert_indexes in combinations_function(l_xrange(password_base_len + 1), inserts_count): + for insert_indexes in combinations_function(l_range(password_base_len + 1), inserts_count): # If multiple inserts are permitted at a single location, make sure they're # limited to args.max_adjacent_inserts. (If multiple inserts are not permitted, @@ -5102,6 +9074,15 @@ def insert_typos_generator(password_base, min_typos = 0): typos_sofar -= inserts_count +# password_repeats_generator() is a generator function which creates repetitions of the base password +def password_repeats_generator(password_base, min_typos = 0): + global typos_sofar + + # Copy a few globals into local for a small speed boost + l_max_password_repeats = args.max_password_repeats + + for i in range(1,l_max_password_repeats+1): + yield password_base * (i) ################################### Main ################################### @@ -5116,18 +9097,42 @@ def return_verified_password_or_false(passwords): # tries to set the process priority to minimum, and # begins ignoring SIGINTs for a more graceful exit on Ctrl-C loaded_wallet = None # initialized once at global scope for Windows -def init_worker(wallet, char_mode): +def init_worker(wallet, char_mode, worker_out_queue = None): global loaded_wallet if not loaded_wallet: loaded_wallet = wallet if char_mode == str: - enable_ascii_mode() - elif char_mode == unicode: enable_unicode_mode() else: assert False + try: + loaded_wallet._load_wordlist() # Load the wordlist for each worker (Allows word ID lookups in the solver thread, required for Electrum1) + except: + pass #don't really care if it doesn't load in terms of performance, this is only called at worker thread creation + + if worker_out_queue: + loaded_wallet.worker_out_queue = worker_out_queue + + try: + # If GPU usage is enabled, create the openCL contexts for the workers + if loaded_wallet.opencl_algo == 0: + # Split up GPU's over available worker threads + worker_number = int(multiprocessing.current_process().name.split("-")[1]) - 1 + try: + openclDevice = loaded_wallet.opencl_devices[worker_number % len(loaded_wallet.opencl_devices)] + except Exception: + devices = pyopencl.get_platforms()[loaded_wallet.opencl_platform].get_devices() + openclDevice = worker_number % len(devices) + #print("Creating Context for Device :", openclDevice) + btcrecover.opencl_helpers.init_opencl_contexts(loaded_wallet, openclDevice = openclDevice) + + except Exception as errormessage: + print(errormessage) + pass + set_process_priority_idle() signal.signal(signal.SIGINT, signal.SIG_IGN) + # def set_process_priority_idle(): try: @@ -5142,7 +9147,7 @@ def set_process_priority_idle(): SetPriorityClass(GetCurrentProcess(), 0x00000040) # IDLE_PRIORITY_CLASS else: os.nice(19) - except StandardError: pass + except Exception: pass # If an out-of-memory error occurs which can be handled, free up some memory, display # an informative error message, and then return True, otherwise return False. @@ -5153,15 +9158,15 @@ def handle_oom(): del password_dups, token_combination_dups gc.collect() print() # move to the next line - print(prog+": error: out of memory", file=sys.stderr) - print(prog+": notice: the --no-dupchecks option will reduce memory usage at the possible expense of speed", file=sys.stderr) + print("Error: out of memory", file=sys.stderr) + print("Notice: the --no-dupchecks option will reduce memory usage at the possible expense of speed", file=sys.stderr) return True elif token_combination_dups and token_combination_dups._run_number == 0: del token_combination_dups gc.collect() print() # move to the next line - print(prog+": error: out of memory", file=sys.stderr) - print(prog+": notice: the --no-dupchecks option can be specified twice to further reduce memory usage", file=sys.stderr) + print("Error: out of memory", file=sys.stderr) + print("Notice: the --no-dupchecks option can be specified twice to further reduce memory usage", file=sys.stderr) return True return False @@ -5169,9 +9174,10 @@ def handle_oom(): # Saves progress by overwriting the older (of two) slots in the autosave file # (autosave_nextslot is initialized in load_savestate() or parse_arguments() ) def do_autosave(skip, inside_interrupt_handler = False): + print("SaveState: ", savestate, " Type:", type(savestate)) global autosave_nextslot assert autosave_file and not autosave_file.closed, "do_autosave: autosave_file is open" - assert isinstance(savestate, dict) and b"argv" in savestate, "do_autosave: savestate is initialized" + assert isinstance(savestate, dict) and "argv" in savestate, "do_autosave: savestate is initialized" if not inside_interrupt_handler: sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore Ctrl-C, sigterm_handler = signal.signal(signal.SIGTERM, signal.SIG_IGN) # SIGTERM, and @@ -5184,7 +9190,7 @@ def do_autosave(skip, inside_interrupt_handler = False): autosave_file.write(SAVESLOT_SIZE * b"\0") autosave_file.flush() try: os.fsync(autosave_file.fileno()) - except StandardError: pass + except Exception: pass autosave_file.seek(start_pos) else: assert autosave_nextslot == 1 @@ -5192,13 +9198,13 @@ def do_autosave(skip, inside_interrupt_handler = False): autosave_file.seek(start_pos) autosave_file.truncate() try: os.fsync(autosave_file.fileno()) - except StandardError: pass - savestate[b"skip"] = skip # overwrite the one item which changes for each autosave - cPickle.dump(savestate, autosave_file, cPickle.HIGHEST_PROTOCOL) - assert autosave_file.tell() <= start_pos + SAVESLOT_SIZE, "do_autosave: data <= "+unicode(SAVESLOT_SIZE)+" bytes long" + except Exception: pass + savestate["skip"] = skip # overwrite the one item which changes for each autosave + pickle.dump(savestate, autosave_file) + assert autosave_file.tell() <= start_pos + SAVESLOT_SIZE, "do_autosave: data <= "+str(SAVESLOT_SIZE)+" bytes long" autosave_file.flush() try: os.fsync(autosave_file.fileno()) - except StandardError: pass + except Exception: pass autosave_nextslot = 1 if autosave_nextslot==0 else 0 if not inside_interrupt_handler: signal.signal(signal.SIGINT, sigint_handler) @@ -5206,6 +9212,30 @@ def do_autosave(skip, inside_interrupt_handler = False): if sys.platform != "win32": signal.signal(signal.SIGHUP, sighup_handler) +def count_passwords_async(current_passwords_count): + try: + for passwords_count in passwords_count_generator: + current_passwords_count.value = passwords_count + except BaseException as e: + raise Exception(e.code) + +def count_and_check_eta_dynamic(est_secs_per_password): + assert est_secs_per_password > 0.0, "count_and_check_eta_dynamic: est_secs_per_password > 0.0" + assert args.skip >= 0 + max_seconds = args.max_eta * 3600 # max_eta is in hours + passwords_count_iterator = password_generator(PASSWORDS_BETWEEN_UPDATES, only_yield_count=True) + passwords_counted = 0 + # Iterate though the password counts in increments of size PASSWORDS_BETWEEN_UPDATES + for passwords_counted_last in passwords_count_iterator: + passwords_counted += passwords_counted_last + unskipped_passwords_counted = passwords_counted - args.skip + + # If the ETA is past its max permitted limit, exit + if unskipped_passwords_counted * est_secs_per_password > max_seconds: + error_exit("\rat least {:,} passwords to try, ETA > --max-eta option ({} hours), exiting" \ + .format(passwords_counted - args.skip, args.max_eta)) + + yield passwords_counted # Given an est_secs_per_password, counts the *total* number of passwords generated by password_generator() # (including those skipped by args.skip), and returns the result, checking the --max-eta constraint along @@ -5234,7 +9264,7 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): passwords_counted = 0 try: # Skip it all in a single iteration (or raise StopIteration if it's empty) - passwords_counted = passwords_count_iterator.next() + passwords_counted = passwords_count_iterator.__next__() passwords_count_iterator.send( (chunksize, False) ) # change it into a "normal" iterator except StopIteration: pass return passwords_count_iterator, passwords_counted @@ -5245,7 +9275,7 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): passwords_count_iterator = password_generator(PASSWORDS_BETWEEN_UPDATES, only_yield_count=True) passwords_counted = 0 is_displayed = False - start = time.clock() if sys_stderr_isatty else None + start = time.perf_counter() if sys_stderr_isatty else None try: # Iterate though the password counts in increments of size PASSWORDS_BETWEEN_UPDATES for passwords_counted_last in passwords_count_iterator: @@ -5254,7 +9284,7 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): # If it's taking a while, and if we're not almost done, display/update the on-screen message - if not is_displayed and sys_stderr_isatty and time.clock() - start > SECONDS_BEFORE_DISPLAY and ( + if not is_displayed and sys_stderr_isatty and time.perf_counter() - start > SECONDS_BEFORE_DISPLAY and ( est_secs_per_password or passwords_counted * 1.5 < args.skip): print("Counting passwords ..." if est_secs_per_password else "Skipping passwords ...", file=sys.stderr) is_displayed = True @@ -5265,11 +9295,11 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): # Only display an ETA once unskipped passwords are being counted if unskipped_passwords_counted > 0: eta = unskipped_passwords_counted * est_secs_per_password / 60 - if eta < 90: eta = unicode(int(eta)+1) + " minutes" # round up + if eta < 90: eta = str(int(eta)+1) + " minutes" # round up else: eta /= 60 - if eta < 48: eta = unicode(int(round(eta))) + " hours" - else: eta = unicode(round(eta / 24, 1)) + " days" + if eta < 48: eta = str(int(round(eta))) + " hours" + else: eta = str(round(eta / 24, 1)) + " days" msg = "\r {:,}".format(passwords_counted) if args.skip: msg += " (includes {:,} skipped)".format(args.skip) msg += " ETA: " + eta + " and counting " @@ -5305,7 +9335,7 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): else: try: passwords_count_iterator.send( (args.skip - passwords_counted, True) ) # the remaining count - passwords_counted += passwords_count_iterator.next() + passwords_counted += passwords_count_iterator.__next__() passwords_count_iterator.send( (chunksize, False) ) # change it into a "normal" iterator except StopIteration: pass return passwords_count_iterator, passwords_counted @@ -5323,6 +9353,26 @@ def password_generator_factory(chunksize = 1, est_secs_per_password = 0): if isinstance(e, KeyboardInterrupt): sys.exit(0) raise +# Writes the checksummed seed phrases out to the file specified in the listvalid argument +# This function runs in its own process and consumes the seeds which are placed in the queue by the worker threads +def write_checked_seeds(worker_out_queue,loaded_wallet): + current_file_valid_seed_count = 0 + seedfile_suffix = 0 + while worker_out_queue.qsize() < 10: #If the workers haven't started filling the queue yet, just sleep + time.sleep(10) + try: + with open(loaded_wallet._savevalidseeds + "_" + '{:04d}'.format(seedfile_suffix) + ".txt", mode='a', buffering = 10240) as listfile: + while True: + listfile.write(" ".join(worker_out_queue.get(timeout = 5)).strip('()[]') + "\n") + current_file_valid_seed_count += 1 + if current_file_valid_seed_count > loaded_wallet._seedfilecount: + listfile.close() + seedfile_suffix += 1 + current_file_valid_seed_count = 0 + listfile = open(loaded_wallet._savevalidseeds + "_" + '{:04d}'.format(seedfile_suffix) + ".txt", mode='a', buffering = 10240) + + except multiprocessing.queues.Empty: + print("Save List Writer Finished") # Should be called after calling parse_arguments() # Returns a two-element tuple: @@ -5353,23 +9403,16 @@ def windows_ctrl_handler(signal): # If --listpass was requested, just list out all the passwords and exit passwords_count = 0 if args.listpass: - if tstr == unicode: - stdout_encoding = sys.stdout.encoding if hasattr(sys.stdout, "encoding") else None # for unittest - if not stdout_encoding: - print(prog+": warning: output will be UTF-8 encoded", file=sys.stderr) - stdout_encoding = "utf_8" - elif "UTF" in stdout_encoding.upper(): - stdout_encoding = None # let the builtin print do the encoding automatically - else: - print(prog+": warning: stdout's encoding is not Unicode compatible; data loss may occur", file=sys.stderr) - else: - stdout_encoding = None password_iterator, skipped_count = password_generator_factory() - plus_skipped = " (plus " + unicode(skipped_count) + " skipped)" if skipped_count else "" + plus_skipped = " (plus " + str(skipped_count) + " skipped)" if skipped_count else "" try: for password in password_iterator: passwords_count += 1 - builtin_print(password[0] if stdout_encoding is None else password[0].encode(stdout_encoding, "replace")) + if type(password[0]) in (tuple, list): # If we are printing seed phrases + password = " ".join(password[0]) + print(password) + else: + print(password[0]) except BaseException as e: handled = handle_oom() if isinstance(e, MemoryError) and passwords_count > 0 else False if not handled: print() # move to the next line @@ -5377,9 +9420,10 @@ def windows_ctrl_handler(signal): if handled: sys.exit(1) if isinstance(e, KeyboardInterrupt): sys.exit(0) raise - return None, unicode(passwords_count) + " password combinations" + plus_skipped + return None, str(passwords_count) + " password combinations" + plus_skipped try: + print("Wallet Type:", str(type(loaded_wallet))[19:-2]) print("Wallet difficulty:", loaded_wallet.difficulty_info()) except AttributeError: pass @@ -5391,27 +9435,71 @@ def windows_ctrl_handler(signal): if args.enable_gpu: inner_iterations = sum(args.global_ws) outer_iterations = 1 + approx_passwords = loaded_wallet.passwords_per_seconds(0.5) else: # Passwords are verified in "chunks" to reduce call overhead. One chunk includes enough passwords to # last for about 1/100th of a second (determined experimentally to be about the best I could do, YMMV) CHUNKSIZE_SECONDS = 1.0 / 100.0 measure_performance_iterations = loaded_wallet.passwords_per_seconds(0.5) - inner_iterations = int(round(2*measure_performance_iterations * CHUNKSIZE_SECONDS)) or 1 # the "2*" is due to the 0.5 seconds above - outer_iterations = int(round(measure_performance_iterations / inner_iterations)) - assert outer_iterations > 0 - # - performance_generator = performance_base_password_generator() # generates dummy passwords - start = timeit.default_timer() - # Emulate calling the verification function with lists of size inner_iterations - for o in xrange(outer_iterations): - loaded_wallet.return_verified_password_or_false(list( - itertools.islice(itertools.ifilter(custom_final_checker, performance_generator), inner_iterations))) - est_secs_per_password = (timeit.default_timer() - start) / (outer_iterations * inner_iterations) - del performance_generator + inner_iterations = int(round(2 * measure_performance_iterations * CHUNKSIZE_SECONDS)) or 1 # the "2*" is due to the 0.5 seconds above + outer_iterations = max(1, int(round(measure_performance_iterations / inner_iterations))) + approx_passwords = measure_performance_iterations + + approx_passwords = max(approx_passwords, 1) + fallback_estimate = 0.5 / float(approx_passwords) + + skip_pre_start = getattr(args, "skip_pre_start", False) + pre_start_limit = getattr(args, "pre_start_seconds", 30.0) + + if pre_start_limit is not None: + if pre_start_limit < 0: + print("Warning: --pre-start-seconds must be >= 0, skipping benchmark", file=sys.stderr) + skip_pre_start = True + pre_start_limit = None + elif pre_start_limit == 0: + skip_pre_start = True + pre_start_limit = None + + if skip_pre_start: + print("Skipping pre-start benchmark; progress estimates may be less accurate.") + est_secs_per_password = fallback_estimate + else: + message = "Pre-start benchmark: measuring verification speed" + if pre_start_limit is not None: + message += " (limit {:.3g} seconds)".format(pre_start_limit) + message += ". Use --skip-pre-start to skip or --pre-start-seconds to limit this step." + print(message) + + performance_generator = performance_base_password_generator() # generates dummy passwords + start = timeit.default_timer() + iterations_done = 0 + + loaded_wallet.pre_start_benchmark = True + try: + for o in range(outer_iterations): + loaded_wallet.return_verified_password_or_false(list( + itertools.islice(filter(custom_final_checker, performance_generator), inner_iterations))) + iterations_done += inner_iterations + if pre_start_limit is not None and timeit.default_timer() - start >= pre_start_limit: + break + finally: + loaded_wallet.pre_start_benchmark = False + del performance_generator + + elapsed = timeit.default_timer() - start + if iterations_done <= 0 or elapsed <= 0: + est_secs_per_password = fallback_estimate + else: + est_secs_per_password = elapsed / iterations_done + rate = iterations_done / elapsed + print("Pre-start benchmark completed in {:.2f}s ({:.2f} passwords/s).".format(elapsed, rate)) + assert isinstance(est_secs_per_password, float) and est_secs_per_password > 0.0 if args.enable_gpu: chunksize = sum(args.global_ws) + elif args.enable_opencl: + chunksize = loaded_wallet.chunksize else: # (see CHUNKSIZE_SECONDS above) chunksize = int(round(CHUNKSIZE_SECONDS / est_secs_per_password)) or 1 @@ -5428,34 +9516,39 @@ def windows_ctrl_handler(signal): verifying_threads = args.threads # Adjust estimate for the number of verifying threads (final estimate is probably an underestimate) - est_secs_per_password /= min(verifying_threads, cpus) + est_secs_per_password /= min(verifying_threads, logical_cpu_cores) # Count how many passwords there are (excluding skipped ones) so we can display and conform to ETAs if not args.no_eta: assert args.skip >= 0 - if l_savestate and b"total_passwords" in l_savestate and args.no_dupchecks: - passwords_count = l_savestate[b"total_passwords"] # we don't need to do a recount + if args.dynamic_passwords_count: + # this is global because it's used in count_passwords_async + global passwords_count_generator + passwords_count_generator = count_and_check_eta_dynamic(est_secs_per_password) + elif l_savestate and "total_passwords" in l_savestate and args.no_dupchecks: + passwords_count = l_savestate["total_passwords"] # we don't need to do a recount iterate_time = 0 else: - start = time.clock() + start = time.perf_counter() passwords_count = count_and_check_eta(est_secs_per_password) - iterate_time = time.clock() - start + iterate_time = time.perf_counter() - start if l_savestate: - if b"total_passwords" in l_savestate: - assert l_savestate[b"total_passwords"] == passwords_count, "main: saved password count matches actual count" + if "total_passwords" in l_savestate: + assert l_savestate["total_passwords"] == passwords_count, "main: saved password count matches actual count" else: - l_savestate[b"total_passwords"] = passwords_count + l_savestate["total_passwords"] = passwords_count - passwords_count -= args.skip - if passwords_count <= 0: - return False, "Skipped all "+unicode(passwords_count + args.skip)+" passwords, exiting" + if not args.dynamic_passwords_count: + passwords_count -= args.skip + if passwords_count <= 0: + return False, "Skipped all "+str(passwords_count + args.skip)+" passwords, exiting" # If additional ETA calculations are required - if l_savestate or not have_progress: + if not args.dynamic_passwords_count and (l_savestate or not have_progress): eta_seconds = passwords_count * est_secs_per_password # if the main thread is sharing CPU time with a verifying thread - if spawned_threads == 0 and not args.enable_gpu or spawned_threads >= cpus: + if spawned_threads == 0 and not args.enable_gpu or spawned_threads >= logical_cpu_cores: eta_seconds += iterate_time if l_savestate: est_passwords_per_5min = int(round(passwords_count / eta_seconds * 300.0)) @@ -5469,7 +9562,8 @@ def windows_ctrl_handler(signal): # If there aren't many passwords, give each of the N workers 1/Nth of the passwords # (rounding up) and also don't bother spawning more threads than there are passwords - if not args.no_eta and spawned_threads * chunksize > passwords_count: + # note that if the passwords are counted dynamically, we can't tell that there aren't many passwords + if not args.dynamic_passwords_count and not args.no_eta and spawned_threads * chunksize > passwords_count: if spawned_threads > passwords_count: spawned_threads = passwords_count chunksize = (passwords_count-1) // spawned_threads + 1 @@ -5480,9 +9574,12 @@ def windows_ctrl_handler(signal): password_iterator, skipped_count = password_generator_factory(chunksize) if skipped_count < args.skip: assert args.no_eta, "discovering all passwords have been skipped this late only happens if --no-eta" - return False, "Skipped all "+unicode(skipped_count)+" passwords, exiting" + return False, "Skipped all "+str(skipped_count)+" passwords, exiting" assert skipped_count == args.skip + # Print Timestamp that this step occured + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ", end="") + if args.enable_gpu: cl_devices = loaded_wallet._cl_devices if len(cl_devices) == 1: @@ -5498,20 +9595,30 @@ def windows_ctrl_handler(signal): if args.no_eta: progress = progressbar.ProgressBar(maxval=progressbar.UnknownLength, poll=0.1, widgets=[ progressbar.AnimatedMarker(), - progressbar.FormatLabel(b" %(value)d elapsed: %(elapsed)s rate: "), - progressbar.FileTransferSpeed(unit=b"P") + progressbar.FormatLabel(" %(value)d elapsed: %(elapsed)s rate: "), + progressbar.FileTransferSpeed(unit="P") ]) - progress.update_interval = sys.maxint # work around performance bug in ProgressBar + progress.update_interval = sys.maxsize # work around performance bug in ProgressBar else: + if args.dynamic_passwords_count: + try: + passwords_count = passwords_count_generator.__next__() + + except StopIteration: + passwords_count = 0 progress = progressbar.ProgressBar(maxval=passwords_count, poll=0.1, widgets=[ - progressbar.SimpleProgress(), b" ", - progressbar.Bar(left=b"[", fill=b"-", right=b"]"), - progressbar.FormatLabel(b" %(elapsed)s, "), + progressbar.SimpleProgress(), " ", + progressbar.Bar(left="[", fill="-", right="]"), + progressbar.FormatLabel(" %(elapsed)s, "), progressbar.ETA() ]) else: progress = None - if args.no_eta: + if args.dynamic_passwords_count: + # TODO: print timeout estimate if not args.no_eta even + # if --dynamic-passwords-count is used + print("Passwords will be counted dynamically") + elif args.no_eta: print("Searching for password ...") else: # If progressbar is unavailable, print out a time estimate instead @@ -5532,17 +9639,32 @@ def windows_ctrl_handler(signal): # (the initial counting process can be memory intensive) gc.collect() + worker_out_queue = multiprocessing.Queue() + # Create an iterator which actually checks the (remaining) passwords produced by the password_iterator # by executing the return_verified_password_or_false worker function in possibly multiple threads if spawned_threads == 0: pool = None - password_found_iterator = itertools.imap(return_verified_password_or_false, password_iterator) + if loaded_wallet.opencl_algo == 0: + btcrecover.opencl_helpers.init_opencl_contexts(loaded_wallet) + password_found_iterator = map(return_verified_password_or_false, password_iterator) set_process_priority_idle() # this, the only thread, should be nice else: - pool = multiprocessing.Pool(spawned_threads, init_worker, (loaded_wallet, tstr)) + pool = multiprocessing.Pool(spawned_threads, init_worker, (loaded_wallet, tstr, worker_out_queue)) + if args.dynamic_passwords_count: + current_passwords_count = multiprocessing.Manager().Value('current_passwords_count', progress.maxval if progress else 0) + passwords_counting_result = pool.apply_async(count_passwords_async, args = (current_passwords_count,)) password_found_iterator = pool.imap(return_verified_password_or_false, password_iterator) if main_thread_is_worker: set_process_priority_idle() # if this thread is cpu-intensive, be nice + # If we are writing out the checksummed seed files, spawn a process that will handle taking the seeds produced by the workers and writing them out to a file + try: + if loaded_wallet._savevalidseeds: + write_checked_seeds_worker = multiprocessing.Process(target = write_checked_seeds, args = (worker_out_queue,loaded_wallet)) + write_checked_seeds_worker.start() + except AttributeError: # Not all loaded wallets will have this attribute + pass + # Try to catch all types of intentional program shutdowns so we can # display password progress information and do a final autosave windows_handler_routine = None @@ -5559,7 +9681,7 @@ def windows_ctrl_handler(signal): SetConsoleCtrlHandler.restype = ctypes.wintypes.BOOL windows_handler_routine = HandlerRoutine(windows_ctrl_handler) # creates a C callback from the Python function SetConsoleCtrlHandler(windows_handler_routine, True) - except StandardError: pass + except Exception: pass # Make est_passwords_per_5min evenly divisible by chunksize # (so that passwords_tried % est_passwords_per_5min will eventually == 0) @@ -5590,7 +9712,12 @@ def windows_ctrl_handler(signal): print() # move down to the line below the progress bar break passwords_tried += passwords_tried_last - if progress: progress.update(passwords_tried) + if progress: + if args.dynamic_passwords_count: + progress.maxval = current_passwords_count.value + if passwords_counting_result.ready() and not passwords_counting_result.successful(): + passwords_counting_result.get() + progress.update(passwords_tried) if l_savestate and passwords_tried % est_passwords_per_5min == 0: do_autosave(args.skip + passwords_tried) else: # if the for loop exits normally (without breaking) @@ -5628,4 +9755,7 @@ def windows_ctrl_handler(signal): do_autosave(args.skip + passwords_tried) autosave_file.close() - return (password_found, "Password search exhausted" if password_found is False else None) + worker_out_queue.close() + + global searchfailedtext + return (password_found, searchfailedtext if password_found is False else None) diff --git a/btcrecover/btcrseed.py b/btcrecover/btcrseed.py index 33fb8d0cb..fa3d212d7 100644 --- a/btcrecover/btcrseed.py +++ b/btcrecover/btcrseed.py @@ -1,1889 +1,5340 @@ -# btcrseed.py -- btcrecover mnemonic sentence library -# Copyright (C) 2014-2017 Christopher Gurnee -# -# This file is part of btcrecover. -# -# btcrecover is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version -# 2 of the License, or (at your option) any later version. -# -# btcrecover is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ - -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! - -# TODO: finish pythonizing comments/documentation - -# (all optional futures for 2.7 except unicode_literals) -from __future__ import print_function, absolute_import, division - -__version__ = "0.7.3" - -from . import btcrpass -from .addressset import AddressSet -import sys, os, io, base64, hashlib, hmac, difflib, coincurve, itertools, \ - unicodedata, collections, struct, glob, atexit, re, random, multiprocessing - - -# Order of the base point generator, from SEC 2 -GENERATOR_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141L - -ADDRESSDB_DEF_FILENAME = "addresses.db" - -def full_version(): - return "seedrecover {}, {}".format( - __version__, - btcrpass.full_version() - ) - - -################################### Utility Functions ################################### - - -def bytes_to_int(bytes_rep): - """convert a string of bytes (in big-endian order) to a long integer - - :param bytes_rep: the raw bytes - :type bytes_rep: str - :return: the unsigned integer - :rtype: long - """ - return long(base64.b16encode(bytes_rep), 16) - -def int_to_bytes(int_rep, min_length): - """convert an unsigned integer to a string of bytes (in big-endian order) - - :param int_rep: a non-negative integer - :type int_rep: long or int - :param min_length: the minimum output length - :type min_length: int - :return: the raw bytes, zero-padded (at the beginning) if necessary - :rtype: str - """ - assert int_rep >= 0 - hex_rep = "{:X}".format(int_rep) - if len(hex_rep) % 2 == 1: # The hex decoder below requires - hex_rep = "0" + hex_rep # exactly 2 chars per byte. - return base64.b16decode(hex_rep).rjust(min_length, "\0") - - -dec_digit_to_base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -base58_digit_to_dec = { b58:dec for dec,b58 in enumerate(dec_digit_to_base58) } - - -def base58check_to_bytes(base58_rep, expected_size): - """decode a base58check string to its raw bytes - - :param base58_rep: check-code appended base58-encoded string - :type base58_rep: str - :param expected_size: the expected number of decoded bytes (excluding the check code) - :type expected_size: int - :return: the base58-decoded bytes - :rtype: str - """ - base58_stripped = base58_rep.lstrip("1") - - int_rep = 0 - for base58_digit in base58_stripped: - int_rep *= 58 - int_rep += base58_digit_to_dec[base58_digit] - - # Convert int to raw bytes - all_bytes = int_to_bytes(int_rep, expected_size + 4) - - zero_count = next(zeros for zeros,byte in enumerate(all_bytes) if byte != "\0") - if len(base58_rep) - len(base58_stripped) != zero_count: - raise ValueError("prepended zeros mismatch") - - if hashlib.sha256(hashlib.sha256(all_bytes[:-4]).digest()).digest()[:4] != all_bytes[-4:]: - raise ValueError("base58 check code mismatch") - - return all_bytes[:-4] - -def base58check_to_hash160(base58_rep): - """convert from a base58check address to its hash160 form - - :param base58_rep: check-code appended base58-encoded address - :type base58_rep: str - :return: the hash of the pubkey/redeemScript, then the version byte - :rtype: (str, str) - """ - decoded_bytes = base58check_to_bytes(base58_rep, 1 + 20) - return decoded_bytes[1:], decoded_bytes[0] - -BIP32ExtendedKey = collections.namedtuple("BIP32ExtendedKey", - "version depth fingerprint child_number chaincode key") -# -def base58check_to_bip32(base58_rep): - """decode a bip32-serialized extended key from its base58check form - - :param base58_rep: check-code appended base58-encoded bip32 extended key - :type base58_rep: str - :return: a namedtuple containing: version depth fingerprint child_number chaincode key - :rtype: BIP32ExtendedKey - """ - decoded_bytes = base58check_to_bytes(base58_rep, 4 + 1 + 4 + 4 + 32 + 33) - return BIP32ExtendedKey(decoded_bytes[0:4], ord(decoded_bytes[ 4:5]), decoded_bytes[ 5:9], - struct.unpack(">I", decoded_bytes[9:13])[0], decoded_bytes[13:45], decoded_bytes[45:]) - -def compress_pubkey(uncompressed_pubkey): - """convert an uncompressed public key into a compressed public key - - :param uncompressed_pubkey: the uncompressed public key - :type uncompressed_pubkey: str - :return: the compressed public key - :rtype: str - """ - assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == "\x04" - return chr((ord(uncompressed_pubkey[-1]) & 1) + 2) + uncompressed_pubkey[1:33] - - -print = btcrpass.safe_print # use btcrpass's print which never dies from printing Unicode - - -################################### Wallets ################################### - -# A class decorator which adds a wallet class to a registered -# list that can later be selected by a user in GUI mode -selectable_wallet_classes = [] -def register_selectable_wallet_class(description): - def _register_selectable_wallet_class(cls): - selectable_wallet_classes.append((cls, description)) - return cls - return _register_selectable_wallet_class - - -# Loads a wordlist from a file into a list of Python unicodes. Note that the -# unicodes are normalized in NFC format, which is not what BIP39 requires (NFKD). -wordlists_dir = os.path.join(os.path.dirname(__file__), "wordlists") -def load_wordlist(name, lang): - filename = os.path.join(wordlists_dir, "{}-{}.txt".format(name, lang)) - with io.open(filename, encoding="utf_8_sig") as wordlist_file: - wordlist = [] - for word in wordlist_file: - word = word.strip() - if word and not word.startswith(u"#"): - wordlist.append(unicodedata.normalize("NFC", word)) - return wordlist - - -def calc_passwords_per_second(checksum_ratio, kdf_overhead, scalar_multiplies): - """estimate the number of mnemonics that can be checked per second (per CPU core) - - :param checksum_ratio: chances that a random mnemonic has the correct checksum [0.0 - 1.0] - :type checksum_ratio: float - :param kdf_overhead: overhead in seconds imposed by the kdf per each guess - :type kdf_overhead: float - :param scalar_multiplies: count of EC scalar multiplications required per each guess - :type scalar_multiplies: int - :return: estimated mnemonic check rate in hertz (per CPU core) - :rtype: float - """ - return 1.0 / (checksum_ratio * (kdf_overhead + scalar_multiplies*0.0001) + 0.00001) - - -############### WalletBase ############### - -# Methods common to most wallets, but overridden by WalletEthereum -class WalletBase(object): - - def __init__(self, loading = False): - assert loading, "use load_from_filename or create_from_params to create a " + self.__class__.__name__ - - @staticmethod - def _addresses_to_hash160s(addresses): - hash160s = set() - for address in addresses: - hash160, version_byte = base58check_to_hash160(address) - if ord(version_byte) != 0: - raise ValueError("not a Bitcoin P2PKH address; version byte is {:#04x}".format(ord(version_byte))) - hash160s.add(hash160) - return hash160s - - @staticmethod - def pubkey_to_hash160(uncompressed_pubkey): - """convert from an uncompressed public key to its Bitcoin compressed-pubkey hash160 form - - :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString - :type uncompressed_pubkey: str - :return: ripemd160(sha256(compressed_pubkey)) - :rtype: str - """ - return hashlib.new("ripemd160", hashlib.sha256(compress_pubkey(uncompressed_pubkey)).digest()).digest() - - -############### Electrum1 ############### - -@register_selectable_wallet_class("Electrum 1.x (including wallets later upgraded to 2.x)") -class WalletElectrum1(WalletBase): - - _words = None - @classmethod - def _load_wordlist(cls): - if not cls._words: - cls._words = tuple(map(str, load_wordlist("electrum1", "en"))) # also converts to ASCII - cls._word_to_id = { word:id for id,word in enumerate(cls._words) } - - @property - def word_ids(self): return xrange(len(self._words)) - @classmethod - def id_to_word(cls, id): return cls._words[id] - - @staticmethod - def is_wallet_file(wallet_file): - wallet_file.seek(0) - # returns "maybe yes" or "definitely no" - return None if wallet_file.read(2) == b"{'" else False - - def __init__(self, loading = False): - super(WalletElectrum1, self).__init__(loading) - self._master_pubkey = None - self._passwords_per_second = None - - self._load_wordlist() - self._num_words = len(self._words) # needs to be an instance variable so it can be pickled - - def passwords_per_seconds(self, seconds): - if not self._passwords_per_second: - self._passwords_per_second = \ - calc_passwords_per_second(1, 0.12, 1 if self._master_pubkey else self._addrs_to_generate + 1) - return max(int(round(self._passwords_per_second * seconds)), 1) - - # Load an Electrum1 wallet file (the part of it we need, just the master public key) - @classmethod - def load_from_filename(cls, wallet_filename): - from ast import literal_eval - with open(wallet_filename) as wallet_file: - wallet = literal_eval(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE)) # up to 64M, typical size is a few k - return cls._load_from_dict(wallet) - - @classmethod - def _load_from_dict(cls, wallet): - seed_version = wallet.get("seed_version") - if seed_version is None: raise ValueError("Unrecognized wallet format (Electrum1 seed_version not found)") - if seed_version != 4: raise NotImplementedError("Unsupported Electrum1 seed version " + seed_version) - if not wallet.get("use_encryption"): raise ValueError("Electrum1 wallet is not encrypted") - master_pubkey = base64.b16decode(wallet["master_public_key"], casefold=True) - if len(master_pubkey) != 64: raise ValueError("Electrum1 master public key is not 64 bytes long") - self = cls(loading=True) - self._master_pubkey = "\x04" + master_pubkey # prepend the uncompressed tag - return self - - # Creates a wallet instance from either an mpk, an addresses container and address_limit, - # or a hash160s container. If none of these were supplied, prompts the user for each. - @classmethod - def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, is_performance = False): - self = cls(loading=True) - - # Process the mpk (master public key) argument - if mpk: - if len(mpk) != 128: - raise ValueError("an Electrum 1.x master public key must be exactly 128 hex digits long") - try: - mpk = base64.b16decode(mpk, casefold=True) - # (it's assigned to the self._master_pubkey later) - except TypeError as e: - raise ValueError(e) # consistently raise ValueError for any bad inputs - - # Process the hash160s argument - if hash160s: - if mpk: - print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr) - hash160s = None - else: - self._known_hash160s = hash160s - - # Process the addresses argument - if addresses: - if mpk or hash160s: - print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr) - addresses = None - else: - self._known_hash160s = self._addresses_to_hash160s(addresses) - - # Process the address_limit argument - if address_limit: - if mpk: - print("warning: address limit is ignored when an mpk is provided", file=sys.stderr) - address_limit = None - else: - address_limit = int(address_limit) - if address_limit <= 0: - raise ValueError("the address limit must be > 0") - # (it's assigned to self._addrs_to_generate later) - - # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first - if not mpk and not addresses and not hash160s: - init_gui() - while True: - mpk = tkSimpleDialog.askstring("Electrum 1.x master public key", - "Please enter your master public key if you have it, or click Cancel to search by an address instead:", - initialvalue="c79b02697b32d9af63f7d2bd882f4c8198d04f0e4dfc5c232ca0c18a87ccc64ae8829404fdc48eec7111b99bda72a7196f9eb8eb42e92514a758f5122b6b5fea" - if is_performance else None) - if not mpk: - break # if they pressed Cancel, stop prompting for an mpk - mpk = mpk.strip() - try: - if len(mpk) != 128: - raise TypeError() - mpk = base64.b16decode(mpk, casefold=True) # raises TypeError() on failure - break - except TypeError: - tkMessageBox.showerror("Master public key", "The entered Electrum 1.x key is not exactly 128 hex digits long") - - # If an mpk has been provided (in the function call or from a user), convert it to the needed format - if mpk: - assert len(mpk) == 64, "mpk is 64 bytes long (after decoding from hex)" - self._master_pubkey = "\x04" + mpk # prepend the uncompressed tag - - # If an mpk wasn't provided (at all), and addresses and hash160s arguments also - # weren't provided (in the original function call), prompt the user for addresses. - else: - if not addresses and not hash160s: - # init_gui() was already called above - self._known_hash160s = None - while True: - addresses = tkSimpleDialog.askstring("Addresses", - "Please enter at least one address from your wallet, " - "preferably some created early in your wallet's lifetime:", - initialvalue="17LGpN2z62zp7RS825jXwYtE7zZ19Mxxu8" if is_performance else None) - if not addresses: break - addresses.replace(",", " ") - addresses.replace(";", " ") - addresses = addresses.split() - if not addresses: break - try: - # (raises ValueError or TypeError on failure): - self._known_hash160s = self._addresses_to_hash160s(addresses) - break - except (ValueError, TypeError) as e: - tkMessageBox.showerror("Addresses", "An entered address is invalid ({})".format(e)) - - # If there are still no hash160s available (and no mpk), check for an address database before giving up - if not self._known_hash160s: - if os.path.isfile(ADDRESSDB_DEF_FILENAME): - print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' in the current directory.") - else: - print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr) - sys.exit("canceled") - - if not address_limit: - init_gui() # might not have been called yet - before_the = "one(s) you just entered" if addresses else "first one in actual use" - address_limit = tkSimpleDialog.askinteger("Address limit", - "Please enter the address generation limit. Smaller will\n" - "be faster, but it must be equal to at least the number\n" - "of addresses created before the "+before_the+":", minvalue=1) - if not address_limit: - sys.exit("canceled") - self._addrs_to_generate = address_limit - - if not self._known_hash160s: - print("Loading address database ...") - self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb")) - - return self - - # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped - @staticmethod - def verify_mnemonic_syntax(mnemonic_ids): - return len(mnemonic_ids) == 12 and None not in mnemonic_ids - - # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic - # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 - def return_verified_password_or_false(self, mnemonic_ids_list): - # Copy some vars into local for a small speed boost - l_sha256 = hashlib.sha256 - hashlib_new = hashlib.new - num_words = self._num_words - num_words2 = num_words * num_words - - for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): - # Compute the binary seed from the word list the Electrum1 way - seed = "" - for i in xrange(0, 12, 3): - seed += "{:08x}".format( mnemonic_ids[i ] - + num_words * ( (mnemonic_ids[i + 1] - mnemonic_ids[i ]) % num_words ) - + num_words2 * ( (mnemonic_ids[i + 2] - mnemonic_ids[i + 1]) % num_words )) - # - unstretched_seed = seed - for i in xrange(100000): # Electrum1's seed stretching - seed = l_sha256(seed + unstretched_seed).digest() - - # If a master public key was provided, check the pubkey derived from the seed against it - if self._master_pubkey: - try: - if coincurve.PublicKey.from_valid_secret(seed).format(compressed=False) == self._master_pubkey: - return mnemonic_ids, count # found it - except ValueError: continue - - # Else derive addrs_to_generate addresses from the seed, searching for a match with known_hash160s - else: - master_privkey = bytes_to_int(seed) - - try: master_pubkey_bytes = coincurve.PublicKey.from_valid_secret(seed).format(compressed=False)[1:] - except ValueError: continue - - for seq_num in xrange(self._addrs_to_generate): - # Compute the next deterministic private/public key pair the Electrum1 way. - # FYI we derive a privkey first, and then a pubkey from that because it's - # likely faster than deriving a pubkey directly from the base point and - # seed -- it means doing a simple modular addition instead of a point - # addition (plus a scalar point multiplication which is needed for both). - d_offset = bytes_to_int( l_sha256(l_sha256( - "{}:0:{}".format(seq_num, master_pubkey_bytes) # 0 means: not a change address - ).digest()).digest() ) - d_privkey = int_to_bytes((master_privkey + d_offset) % GENERATOR_ORDER, 32) - - d_pubkey = coincurve.PublicKey.from_valid_secret(d_privkey).format(compressed=False) - # Compute the hash160 of the *uncompressed* public key, and check for a match - if hashlib_new("ripemd160", l_sha256(d_pubkey).digest()).digest() in self._known_hash160s: - return mnemonic_ids, count # found it - - return False, count - - # Configures the values of four globals used later in config_btcrecover(): - # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes - @classmethod - def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65): - # If a mnemonic guess wasn't provided, prompt the user for one - if not mnemonic_guess: - init_gui() - mnemonic_guess = tkSimpleDialog.askstring("Electrum seed", - "Please enter your best guess for your Electrum seed:") - if not mnemonic_guess: - sys.exit("canceled") - - mnemonic_guess = str(mnemonic_guess) # ensures it's ASCII - - # Convert the mnemonic words into numeric ids and pre-calculate similar mnemonic words - global mnemonic_ids_guess, close_mnemonic_ids - mnemonic_ids_guess = () - # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (int), and each - # dict value is a tuple containing length 1 tuples, and finally each of the - # length 1 tuples contains a single mnemonic_id which is similar to the dict's key - close_mnemonic_ids = {} - for word in mnemonic_guess.lower().split(): - close_words = difflib.get_close_matches(word, cls._words, sys.maxint, closematch_cutoff) - if close_words: - if close_words[0] != word: - print("'{}' was in your guess, but it's not a valid Electrum seed word;\n" - " trying '{}' instead.".format(word, close_words[0])) - mnemonic_ids_guess += cls._word_to_id[close_words[0]], - close_mnemonic_ids[mnemonic_ids_guess[-1]] = tuple( (cls._word_to_id[w],) for w in close_words[1:] ) - else: - print("'{}' was in your guess, but there is no similar Electrum seed word;\n" - " trying all possible seed words here instead.".format(word)) - mnemonic_ids_guess += None, - - global num_inserts, num_deletes - num_inserts = max(12 - len(mnemonic_ids_guess), 0) - num_deletes = max(len(mnemonic_ids_guess) - 12, 0) - if num_inserts: - print("Seed sentence was too short, inserting {} word{} into each guess." - .format(num_inserts, "s" if num_inserts > 1 else "")) - if num_deletes: - print("Seed sentence was too long, deleting {} word{} from each guess." - .format(num_deletes, "s" if num_deletes > 1 else "")) - - # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing) - @staticmethod - def performance_iterator(): - # See WalletBIP39.performance_iterator() for details - prefix = tuple(random.randrange(len(WalletElectrum1._words)) for i in xrange(8)) - for guess in itertools.product(xrange(len(WalletElectrum1._words)), repeat = 4): - yield prefix + guess - - -############### BIP32 ############### - -class WalletBIP32(WalletBase): - - def __init__(self, path = None, loading = False): - super(WalletBIP32, self).__init__(loading) - self._chaincode = None - self._passwords_per_second = None - - # Split the BIP32 key derivation path into its constituent indexes - if not path: # Defaults to BIP44 - path = "m/44'/0'/0'/" - # Append the internal/external (change) index to the path in create_from_params() - self._append_last_index = True - else: - self._append_last_index = False - path_indexes = path.split("/") - if path_indexes[0] == "m" or path_indexes[0] == "": - del path_indexes[0] # the optional leading "m/" - if path_indexes[-1] == "": - del path_indexes[-1] # the optional trailing "/" - self._path_indexes = () - for path_index in path_indexes: - if path_index.endswith("'"): - self._path_indexes += int(path_index[:-1]) + 2**31, - else: - self._path_indexes += int(path_index), - - def passwords_per_seconds(self, seconds): - if not self._passwords_per_second: - scalar_multiplies = 0 - for i in self._path_indexes: - if i < 2147483648: # if it's a normal child key - scalar_multiplies += 1 # then it requires a scalar multiply - if not self._chaincode: - scalar_multiplies += self._addrs_to_generate + 1 # each addr. to generate req. a scalar multiply - self._passwords_per_second = \ - calc_passwords_per_second(self._checksum_ratio, self._kdf_overhead, scalar_multiplies) - return max(int(round(self._passwords_per_second * seconds)), 1) - - # Creates a wallet instance from either an mpk, an addresses container and address_limit, - # or a hash160s container. If none of these were supplied, prompts the user for each. - # (the BIP32 key derivation path is by default BIP44's account 0) - @classmethod - def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, path = None, is_performance = False): - self = cls(path, loading=True) - - # Process the mpk (master public key) argument - if mpk: - if not mpk.startswith("xpub"): - raise ValueError("the BIP32 extended public key must begin with 'xpub'") - mpk = base58check_to_bip32(mpk) - # (it's processed more later) - - # Process the hash160s argument - if hash160s: - if mpk: - print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr) - hash160s = None - else: - self._known_hash160s = hash160s - - # Process the addresses argument - if addresses: - if mpk or hash160s: - print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr) - addresses = None - else: - self._known_hash160s = self._addresses_to_hash160s(addresses) - - # Process the address_limit argument - if address_limit: - if mpk: - print("warning: address limit is ignored when an mpk is provided", file=sys.stderr) - address_limit = None - else: - address_limit = int(address_limit) - if address_limit <= 0: - raise ValueError("the address limit must be > 0") - # (it's assigned to self._addrs_to_generate later) - - # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first - if not mpk and not addresses and not hash160s: - init_gui() - while True: - mpk = tkSimpleDialog.askstring("Master extended public key", - "Please enter your master extended public key (xpub) if you " - "have it, or click Cancel to search by an address instead:", - initialvalue=self._performance_xpub() if is_performance else None) - if not mpk: - break # if they pressed Cancel, stop prompting for an mpk - mpk = mpk.strip() - try: - if not mpk.startswith("xpub"): - raise ValueError("not a BIP32 extended public key (doesn't start with 'xpub')") - mpk = base58check_to_bip32(mpk) - break - except ValueError as e: - tkMessageBox.showerror("Master extended public key", "The entered key is invalid ({})".format(e)) - - # If an mpk has been provided (in the function call or from a user), extract the - # required chaincode and adjust the path to match the mpk's depth and child number - if mpk: - if mpk.depth == 0: - print("xpub depth: 0") - assert mpk.child_number == 0, "child_number == 0 when depth == 0" - else: - if mpk.child_number < 2**31: - child_num = mpk.child_number - else: - child_num = str(mpk.child_number - 2**31) + "'" - print("xpub depth: {}\n" - "xpub fingerprint: {}\n" - "xpub child #: {}" - .format(mpk.depth, base64.b16encode(mpk.fingerprint), child_num)) - self._chaincode = mpk.chaincode - if mpk.depth <= len(self._path_indexes): # if this, ensure the path - self._path_indexes = self._path_indexes[:mpk.depth] # length matches the depth - if self._path_indexes and self._path_indexes[-1] != mpk.child_number: - raise ValueError("the extended public key's child # doesn't match " - "the corresponding index of this wallet's path") - elif mpk.depth == 1 + len(self._path_indexes) and self._append_last_index: - self._path_indexes += mpk.child_number, - else: - raise ValueError( - "the extended public key's depth exceeds the length of this wallet's path ({})" - .format(len(self._path_indexes))) - - else: # else if not mpk - - # If we don't have an mpk but need to append the last - # index, assume it's the external (non-change) chain - if self._append_last_index: - self._path_indexes += 0, - - # If an mpk wasn't provided (at all), and addresses and hash160s arguments also - # weren't provided (in the original function call), prompt the user for addresses. - if not addresses and not hash160s: - # init_gui() was already called above - self._known_hash160s = None - while True: - addresses = tkSimpleDialog.askstring("Addresses", - "Please enter at least one address from the first account in your wallet, " - "preferably some created early in the account's lifetime:", - initialvalue="17LGpN2z62zp7RS825jXwYtE7zZ19Mxxu8" if is_performance else None) - if not addresses: break - addresses.replace(",", " ") - addresses.replace(";", " ") - addresses = addresses.split() - if not addresses: break - try: - # (raises ValueError or TypeError on failure): - self._known_hash160s = self._addresses_to_hash160s(addresses) - break - except (ValueError, TypeError) as e: - tkMessageBox.showerror("Addresses", "An entered address is invalid ({})".format(e)) - - # If there are still no hash160s available (and no mpk), check for an address database before giving up - if not self._known_hash160s: - if os.path.isfile(ADDRESSDB_DEF_FILENAME): - print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' the in current directory.") - else: - print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr) - sys.exit("canceled") - - if not address_limit: - init_gui() # might not have been called yet - before_the = "one(s) you just entered" if addresses else "first one in actual use" - address_limit = tkSimpleDialog.askinteger("Address limit", - "Please enter the address generation limit. Smaller will\n" - "be faster, but it must be equal to at least the number\n" - "of addresses created before the "+before_the+":", minvalue=1) - if not address_limit: - sys.exit("canceled") - self._addrs_to_generate = address_limit - - if not self._known_hash160s: - print("Loading address database ...") - self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb")) - - return self - - # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped - @staticmethod - def verify_mnemonic_syntax(mnemonic_ids): - # Length must be divisible by 3 and all ids must be present - return len(mnemonic_ids) % 3 == 0 and None not in mnemonic_ids - - # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic - # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 - def return_verified_password_or_false(self, mnemonic_ids_list): - for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): - - # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test - if not self._verify_checksum(mnemonic_ids): - continue - - # Convert the mnemonic sentence to seed bytes (according to BIP39 or Electrum2) - seed_bytes = hmac.new("Bitcoin seed", self._derive_seed(mnemonic_ids), hashlib.sha512).digest() - - if self._verify_seed(seed_bytes): - return mnemonic_ids, count # found it - - return False, count - - def _verify_seed(self, seed_bytes): - # Derive the chain of private keys for the specified path as per BIP32 - privkey_bytes = seed_bytes[:32] - chaincode_bytes = seed_bytes[32:] - for i in self._path_indexes: - - if i < 2147483648: # if it's a normal child key, derive the compressed public key - try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format() - except ValueError: return False - else: # else it's a hardened child key - data_to_hmac = "\0" + privkey_bytes # prepended "\0" as per BIP32 - data_to_hmac += struct.pack(">I", i) # append the index (big-endian) as per BIP32 - - seed_bytes = hmac.new(chaincode_bytes, data_to_hmac, hashlib.sha512).digest() - - # The child private key is the parent one + the first half of the seed_bytes (mod n) - privkey_bytes = int_to_bytes((bytes_to_int(seed_bytes[:32]) + - bytes_to_int(privkey_bytes)) % GENERATOR_ORDER, 32) - chaincode_bytes = seed_bytes[32:] - - # If an extended public key was provided, check the derived chain code against it - if self._chaincode: - if chaincode_bytes == self._chaincode: - return True # found it - - else: - # (note: the rest assumes the address index isn't hardened) - - # Derive the final public keys, searching for a match with known_hash160s - # (these first steps below are loop invariants) - try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format() - except ValueError: return False - privkey_int = bytes_to_int(privkey_bytes) - # - for i in xrange(self._addrs_to_generate): - seed_bytes = hmac.new(chaincode_bytes, - data_to_hmac + struct.pack(">I", i), hashlib.sha512).digest() - - # The final derived private key is the parent one + the first half of the seed_bytes - d_privkey_bytes = int_to_bytes((bytes_to_int(seed_bytes[:32]) + - privkey_int) % GENERATOR_ORDER, 32) - - d_pubkey = coincurve.PublicKey.from_valid_secret(d_privkey_bytes).format(compressed=False) - if self.pubkey_to_hash160(d_pubkey) in self._known_hash160s: - return True - - return False - - # Returns a dummy xpub for performance testing purposes - @staticmethod - def _performance_xpub(): - # an xpub at path m/44'/0'/0', as Mycelium for Android would export - return "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx" - - -############### BIP39 ############### - -@register_selectable_wallet_class("Standard BIP39/BIP44 (Mycelium, TREZOR, Ledger, Bither, Blockchain.info, Jaxx)") -class WalletBIP39(WalletBIP32): - FIRSTFOUR_TAG = "-firstfour" - - # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() ) - _language_words = {} - @classmethod - def _load_wordlists(cls): - assert not cls._language_words, "_load_wordlists() should only be called once from the first init()" - cls._do_load_wordlists("bip39") - for wordlist_lang in cls._language_words.keys(): # takes a copy of the keys so the dict can be safely changed - wordlist = cls._language_words[wordlist_lang] - assert len(wordlist) == 2048, "BIP39 wordlist has 2048 words" - # Special case for the four languages whose words may be truncated to the first four letters - if wordlist_lang in ("en", "es", "fr", "it"): - cls._language_words[wordlist_lang + cls.FIRSTFOUR_TAG] = [ w[:4] for w in wordlist ] - # - @classmethod - def _do_load_wordlists(cls, name, wordlist_langs = None): - if not wordlist_langs: - wordlist_langs = [] - for filename in glob.iglob(os.path.join(wordlists_dir, name + "-??*.txt")): - wordlist_langs.append(os.path.basename(filename)[len(name)+1:-4]) # e.g. "en", or "zh-hant" - for lang in wordlist_langs: - assert lang not in cls._language_words, "wordlist not already loaded" - cls._language_words[lang] = load_wordlist(name, lang) - - @property - def word_ids(self): return self._words - @staticmethod - def id_to_word(id): return id # returns a UTF-8 encoded bytestring - - def __init__(self, path = None, loading = False): - super(WalletBIP39, self).__init__(path, loading) - if not self._language_words: - self._load_wordlists() - pbkdf2_library_name = btcrpass.load_pbkdf2_library().__name__ # btcrpass's pbkdf2 library is used in _derive_seed() - self._kdf_overhead = 0.0026 if pbkdf2_library_name == "hashlib" else 0.013 - - def __setstate__(self, state): - # (Re)load the pbkdf2 library if necessary - btcrpass.load_pbkdf2_library() - self.__dict__ = state - - # Converts a mnemonic word from a Python unicode (as produced by load_wordlist()) - # into a bytestring (of type str) in the format required by BIP39 - @staticmethod - def _unicode_to_bytes(word): - assert isinstance(word, unicode) - return intern(unicodedata.normalize("NFKD", word).encode("utf_8")) - - # Configures the values of four globals used later in config_btcrecover(): - # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes; - # also selects the appropriate wordlist language to use - def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrase = u"", expected_len = None, closematch_cutoff = 0.65): - if expected_len: - if expected_len < 12: - raise ValueError("minimum BIP39 sentence length is 12 words") - if expected_len > 24: - raise ValueError("maximum BIP39 sentence length is 24 words") - if expected_len % 3 != 0: - raise ValueError("BIP39 sentence length must be evenly divisible by 3") - - # Do most of the work in this function: - passphrase = self._config_mnemonic(mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff) - - # The pbkdf2-derived salt, based on the passphrase, as per BIP39 (needed by _derive_seed()); - # first ensure that this version of Python supports the characters present in the passphrase - if sys.maxunicode < 65536: # if this Python is a "narrow" Unicode build - for c in passphrase: - c = ord(c) - if 0xD800 <= c <= 0xDBFF or 0xDC00 <= c <= 0xDFFF: - raise ValueError("this version of Python doesn't support passphrases with Unicode code points > "+str(sys.maxunicode)) - self._derivation_salt = "mnemonic" + self._unicode_to_bytes(passphrase) - - # Special case for wallets which tell users to record only the first four letters of each word; - # convert all short words into long ones (intentionally done *after* the finding of close words). - # Specifically, update self._words and the globals mnemonic_ids_guess and close_mnemonic_ids. - if self._lang.endswith(self.FIRSTFOUR_TAG): - long_lang_words = self._language_words[self._lang[:-len(self.FIRSTFOUR_TAG)]] - assert isinstance(long_lang_words[0], unicode), "long words haven't yet been converted into bytes" - assert isinstance(self._words[0], bytes), "short words have already been converted into bytes" - assert len(long_lang_words) == len(self._words), "long and short word lists have the same length" - long_lang_words = [ self._unicode_to_bytes(l) for l in long_lang_words ] - short_to_long = { s:l for s,l in zip(self._words, long_lang_words) } - self._words = long_lang_words - # - global mnemonic_ids_guess # the to-be-replaced short-words guess - long_ids_guess = () # the new long-words guess - for short_id in mnemonic_ids_guess: - long_ids_guess += None if short_id is None else short_to_long[short_id], - mnemonic_ids_guess = long_ids_guess - # - global close_mnemonic_ids - if close_mnemonic_ids: - assert isinstance(close_mnemonic_ids.iterkeys() .next(), bytes), "close word keys have already been converted into bytes" - assert isinstance(close_mnemonic_ids.itervalues().next()[0][0], bytes), "close word values have already been converted into bytes" - for key in close_mnemonic_ids.keys(): # takes a copy of the keys so the dict can be safely changed - vals = close_mnemonic_ids.pop(key) - # vals is a tuple containing length-1 tuples which in turn each contain one word in bytes-format - close_mnemonic_ids[short_to_long[key]] = tuple( (short_to_long[v[0]],) for v in vals ) - - # Calculate each word's index in binary (needed by _verify_checksum()) - self._word_to_binary = { word : "{:011b}".format(i) for i,word in enumerate(self._words) } - - # Chances a checksum is valid, e.g. 1/16 for 12 words, 1/256 for 24 words - self._checksum_ratio = 2.0**( -( len(mnemonic_ids_guess) + num_inserts - num_deletes )//3 ) - # - def _config_mnemonic(self, mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff): - - # If a mnemonic guess wasn't provided, prompt the user for one - if not mnemonic_guess: - init_gui() - mnemonic_guess = tkSimpleDialog.askstring("Seed", - "Please enter your best guess for your seed (mnemonic):") - if not mnemonic_guess: - sys.exit("canceled") - - # Note: this is not in BIP39's preferred encoded form yet, instead it's - # in the same format as load_wordlist creates (NFC normalized Unicode) - mnemonic_guess = unicodedata.normalize("NFC", unicode(mnemonic_guess).lower()).split() - if len(mnemonic_guess) == 1: # assume it's a logographic script (no spaces, e.g. Chinese) - mnemonic_guess = tuple(mnemonic_guess) - - # Select the appropriate wordlist language to use - if not lang: - language_word_hits = {} # maps a language id to the # of words found in that language - for word in mnemonic_guess: - for lang, one_languages_words in self._language_words.iteritems(): - if word in one_languages_words: - language_word_hits.setdefault(lang, 0) - language_word_hits[lang] += 1 - if len(language_word_hits) == 0: - raise ValueError("can't guess wordlist language: 0 valid words") - if len(language_word_hits) == 1: - best_guess = language_word_hits.popitem() - else: - sorted_hits = language_word_hits.items() - sorted_hits.sort(key=lambda x: x[1]) # sort based on hit count - best_guess = sorted_hits[-1] - second_guess = sorted_hits[-2] - # at least 20% must be exclusive to the best_guess language - if best_guess[1] - second_guess[1] < 0.2 * len(mnemonic_guess): - raise ValueError("can't guess wordlist language: top best guesses ({}, {}) are too close ({}, {})" - .format(best_guess[0], second_guess[0], best_guess[1], second_guess[1])) - # at least half must be valid words - if best_guess[1] < 0.5 * len(mnemonic_guess): - raise ValueError("can't guess wordlist language: best guess ({}) has only {} valid word(s)" - .format(best_guess[0], best_guess[1])) - lang = best_guess[0] - # - try: - words = self._language_words[lang] - except KeyError: # consistently raise ValueError for any bad inputs - raise ValueError("can't find wordlist for language code '{}'".format(lang)) - self._lang = lang - print("Using the '{}' wordlist.".format(lang)) - - # Build the mnemonic_ids_guess and pre-calculate similar mnemonic words - global mnemonic_ids_guess, close_mnemonic_ids - self._initial_words_valid = True # start off by assuming they're all valid - mnemonic_ids_guess = () # the best initial guess (w/no invalid words) - # - # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (a string), and - # each dict value is a tuple containing length 1 tuples, and finally each of the - # length 1 tuples contains a single mnemonic_id which is similar to the dict's key; - # e.g.: { "a-word" : ( ("a-ward", ), ("a-work",) ), "other-word" : ... } - close_mnemonic_ids = {} - for word in mnemonic_guess: - close_words = difflib.get_close_matches(word, words, sys.maxint, closematch_cutoff) - if close_words: - if close_words[0] != word: - print(u"'{}' was in your guess, but it's not a valid seed word;\n" - u" trying '{}' instead.".format(word, close_words[0])) - self._initial_words_valid = False - mnemonic_ids_guess += self._unicode_to_bytes(close_words[0]), # *now* convert to BIP39's format - close_mnemonic_ids[mnemonic_ids_guess[-1]] = \ - tuple( (self._unicode_to_bytes(w),) for w in close_words[1:] ) - else: - if __name__ == b"__main__": - print(u"'{}' was in your guess, but there is no similar seed word;\n" - u" trying all possible seed words here instead.".format(word)) - else: - print(u"'{}' was in your seed, but there is no similar seed word.".format(word)) - self._initial_words_valid = False - mnemonic_ids_guess += None, - - guess_len = len(mnemonic_ids_guess) - if not expected_len: # (this is always explicitly specified for Electrum 2 seeds) - assert not isinstance(self, WalletElectrum2), "WalletBIP39._config_mnemonic: expected_len is specified for WalletElectrum2 objects" - if guess_len < 12: - expected_len = 12 - elif guess_len > 24: - expected_len = 24 - else: - off_by = guess_len % 3 - if off_by == 1: - expected_len = guess_len - 1 - elif off_by == 2: - expected_len = guess_len + 1 - else: - expected_len = guess_len - - global num_inserts, num_deletes - num_inserts = max(expected_len - guess_len, 0) - num_deletes = max(guess_len - expected_len, 0) - if num_inserts and not isinstance(self, WalletElectrum2): - print("Seed sentence was too short, inserting {} word{} into each guess." - .format(num_inserts, "s" if num_inserts > 1 else "")) - if num_deletes: - print("Seed sentence was too long, deleting {} word{} from each guess." - .format(num_deletes, "s" if num_deletes > 1 else "")) - - # Now that we're done with the words in Unicode format, - # convert them to BIP39's encoding and save for future reference - self._words = tuple(map(self._unicode_to_bytes, words)) - - if passphrase is True: - init_gui() - while True: - passphrase = tkSimpleDialog.askstring("Passphrase", - "Please enter the passphrase you added when the seed was first created:", show="*") - if not passphrase: - sys.exit("canceled") - if passphrase == tkSimpleDialog.askstring("Passphrase", "Please re-enter the passphrase:", show="*"): - break - tkMessageBox.showerror("Passphrase", "The passphrases did not match, try again.") - return passphrase - - # Called by WalletBIP32.return_verified_password_or_false() to verify a BIP39 checksum - def _verify_checksum(self, mnemonic_words): - # Convert from the mnemonic_words (ids) back to the entropy bytes + checksum - bit_string = "".join(self._word_to_binary[w] for w in mnemonic_words) - cksum_len_in_bits = len(mnemonic_words) // 3 # as per BIP39 - entropy_bytes = bytearray() - for i in xrange(0, len(bit_string) - cksum_len_in_bits, 8): - entropy_bytes.append(int(bit_string[i:i+8], 2)) - cksum_int = int(bit_string[-cksum_len_in_bits:], 2) - # - # Calculate and verify the checksum - return ord(hashlib.sha256(entropy_bytes).digest()[:1]) >> 8-cksum_len_in_bits \ - == cksum_int - - # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed - def _derive_seed(self, mnemonic_words): - # Note: the words are already in BIP39's normalized form - return btcrpass.pbkdf2_hmac("sha512", b" ".join(mnemonic_words), self._derivation_salt, 2048) - - # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing) - # (uses mnemonic_ids_guess, num_inserts, and num_deletes globals as set by config_mnemonic()) - def performance_iterator(self): - # This used to just itereate through the entire space, starting at "abandon abandon abandon ..." - # and "counting" up from there. However "abandon abandon ... abandon about" is actually in use - # in the blockchain (buggy software or people just messing around), and in address-database - # mode this creates an unwanted positive hit, so now we have to start with a random prefix. - length = len(mnemonic_ids_guess) + num_inserts - num_deletes - assert length >= 12 - prefix = tuple(random.choice(self._words) for i in xrange(length-4)) - for guess in itertools.product(self._words, repeat=4): - yield prefix + guess - - -############### bitcoinj ############### - -@register_selectable_wallet_class("Bitcoinj compatible (MultiBit HD (Beta 8+), Bitcoin Wallet for Android/BlackBerry, Hive, breadwallet)") -class WalletBitcoinj(WalletBIP39): - - def __init__(self, path = None, loading = False): - # Just calls WalletBIP39.__init__() with a hardcoded path - if path: raise ValueError("can't specify a BIP32 path with Bitcoinj wallets") - super(WalletBitcoinj, self).__init__("m/0'/0/", loading) - - @staticmethod - def is_wallet_file(wallet_file): - wallet_file.seek(0) - if wallet_file.read(1) == b"\x0a": # protobuf field number 1 of type length-delimited - network_identifier_len = ord(wallet_file.read(1)) - if 1 <= network_identifier_len < 128: - wallet_file.seek(2 + network_identifier_len) - if wallet_file.read(1) in b"\x12\x1a": # field number 2 or 3 of type length-delimited - return True - return False - - # Load a bitcoinj wallet file (the part of it we need, just the chaincode) - @classmethod - def load_from_filename(cls, wallet_filename): - from . import wallet_pb2 - pb_wallet = wallet_pb2.Wallet() - with open(wallet_filename, "rb") as wallet_file: - pb_wallet.ParseFromString(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE)) # up to 64M, typical size is a few k - if pb_wallet.encryption_type == wallet_pb2.Wallet.UNENCRYPTED: - raise ValueError("this bitcoinj wallet is not encrypted") - - # Search for the (one and only) master public extended key (whose path length is 0) - self = None - for key in pb_wallet.key: - if key.HasField("deterministic_key") and len(key.deterministic_key.path) == 0: - assert not self, "only one master public extended key is in the wallet file" - assert len(key.deterministic_key.chain_code) == 32, "chaincode length is 32 bytes" - self = cls(loading=True) - self._chaincode = key.deterministic_key.chain_code - # Because it's the *master* xpub, it has an empty path - self._path_indexes = () - - if not self: - raise ValueError("No master public extended key was found in this bitcoinj wallet file") - return self - - # Returns a dummy xpub for performance testing purposes - @staticmethod - def _performance_xpub(): - # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export - return "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4QLCpPu" - - -############### Electrum2 ############### - -@register_selectable_wallet_class('Electrum 2.x ("standard" wallets initially created with 2.x)') -class WalletElectrum2(WalletBIP39): - - # From Electrum 2.x's mnemonic.py (coalesced) - CJK_INTERVALS = ( - ( 0x1100, 0x11ff), - ( 0x2e80, 0x2fdf), - ( 0x2ff0, 0x2fff), - ( 0x3040, 0x31ff), - ( 0x3400, 0x4dbf), - ( 0x4e00, 0xa4ff), - ( 0xa960, 0xa97f), - ( 0xac00, 0xd7ff), - ( 0xf900, 0xfaff), - ( 0xff00, 0xffef), - (0x16f00, 0x16f9f), - (0x1b000, 0x1b0ff), - (0x20000, 0x2a6df), - (0x2a700, 0x2b81f), - (0x2f800, 0x2fa1d), - (0xe0100, 0xe01ef)) - - # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() ) - @classmethod - def _load_wordlists(cls): - assert not cls._language_words, "_load_wordlists() should only be called once from the first init()" - cls._do_load_wordlists("electrum2") - cls._do_load_wordlists("bip39", ("en", "es", "ja", "zh-hans")) # only the four bip39 ones used by Electrum2 - assert all(len(w) >= 1411 for w in cls._language_words.values()), \ - "Electrum2 wordlists are at least 1411 words long" # because we assume a max mnemonic length of 13 - - def __init__(self, path = None, loading = False): - # Just calls WalletBIP39.__init__() with a hardcoded path - if path: raise ValueError("can't specify a BIP32 path with Electrum 2.x wallets") - super(WalletElectrum2, self).__init__("m/0/", loading) - self._checksum_ratio = 1.0 / 256.0 # 1 in 256 checksums are valid on average - self._needs_passphrase = None - - @staticmethod - def is_wallet_file(wallet_file): - wallet_file.seek(0) - data = wallet_file.read(8) - if data[0] == b"{": - return None # "maybe yes" - try: data = base64.b64decode(data) - except TypeError: return False # "definitely no" - if data.startswith(b"BIE1"): - sys.exit("error: Electrum 2.8+ fully-encrypted wallet files cannot be read,\n" - "try to recover from your master extended public key or an address instead") - return False # "definitely no" - - # Load an Electrum2 wallet file (the part of it we need, just the master public key) - @classmethod - def load_from_filename(cls, wallet_filename): - import json - - with open(wallet_filename) as wallet_file: - wallet = json.load(wallet_file) - wallet_type = wallet.get("wallet_type") - if not wallet_type: - raise ValueError("Unrecognized wallet format (Electrum2 wallet_type not found)") - if wallet_type == "old": # if it's been converted from 1.x to 2.y (y<7), return a WalletElectrum1 object - return WalletElectrum1._load_from_dict(wallet) - if not wallet.get("use_encryption"): - raise ValueError("Electrum2 wallet is not encrypted") - seed_version = wallet.get("seed_version", "(not found)") - if wallet.get("seed_version") not in (11, 12, 13): # all 2.x versions as of Oct 2016 - raise NotImplementedError("Unsupported Electrum2 seed version " + unicode(seed_version)) - if wallet_type != "standard": - raise NotImplementedError("Unsupported Electrum2 wallet type: " + wallet_type) - - mpk = needs_passphrase = None - while True: # "loops" exactly once; only here so we've something to break out of - - # Electrum 2.7+ standard wallets have a keystore - keystore = wallet.get("keystore") - if keystore: - keystore_type = keystore.get("type", "(not found)") - - # Wallets originally created by an Electrum 2.x version - if keystore_type == "bip32": - mpk = keystore["xpub"] - if keystore.get("passphrase"): - needs_passphrase = True - break - - # Former Electrum 1.x wallet after conversion to Electrum 2.7+ standard-wallet format - elif keystore_type == "old": - # Construct and return a WalletElectrum1 object - mpk = base64.b16decode(keystore["mpk"], casefold=True) - if len(mpk) != 64: - raise ValueError("Electrum1 master public key is not 64 bytes long") - self = WalletElectrum1(loading=True) - self._master_pubkey = "\x04" + mpk # prepend the uncompressed tag - return self - - else: - print("warning: found unsupported keystore type " + keystore_type, file=sys.stderr) - - # Electrum 2.0 - 2.6.4 wallet (of any wallet type) - mpks = wallet.get("master_public_keys") - if mpks: - mpk = mpks.values()[0] - break - - raise RuntimeError("No master public keys found in Electrum2 wallet") - - assert mpk - wallet = cls.create_from_params(mpk) - wallet._needs_passphrase = needs_passphrase - return wallet - - # Converts a mnemonic word from a Python unicode (as produced by load_wordlist()) - # into a bytestring (of type str) via the same method as Electrum 2.x - @staticmethod - def _unicode_to_bytes(word): - assert isinstance(word, unicode) - word = unicodedata.normalize("NFKD", word) - word = filter(lambda c: not unicodedata.combining(c), word) # Electrum 2.x removes combining marks - return intern(word.encode("utf_8")) - - def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrase = u"", expected_len = None, closematch_cutoff = 0.65): - if expected_len is None: - expected_len_specified = False - if self._needs_passphrase: - expected_len = 12 - print("notice: presence of a mnemonic passphrase implies a 12-word long Electrum 2.7+ mnemonic", - file=sys.stderr) - else: - init_gui() - if tkMessageBox.askyesno("Electrum 2.x version", - "Did you CREATE your wallet with Electrum version 2.7 (released Oct 2 2016) or later?" - "\n\nPlease choose No if you're unsure.", - default=tkMessageBox.NO): - expected_len = 12 - else: - expected_len = 13 - else: - expected_len_specified = True - if expected_len > 13: - raise ValueError("maximum mnemonic length for Electrum2 is 13 words") - - if self._needs_passphrase and not passphrase: - passphrase = True # tells self._config_mnemonic() to prompt for a passphrase below - init_gui() - tkMessageBox.showwarning("Passphrase", - 'This Electrum seed was extended with "custom words" (a seed passphrase) when it ' - "was first created. You will need to enter it to continue.\n\nNote that this seed " - "passphrase is NOT the same as the wallet password that's entered to spend funds.") - # Calls WalletBIP39's generic version (note the leading _) with the mnemonic - # length (which for Electrum2 wallets alone is treated only as a maximum length) - passphrase = self._config_mnemonic(mnemonic_guess, lang, passphrase, expected_len, closematch_cutoff) - - # Python 2.x running Electrum 2.x has a Unicode bug where if there are any code points > 65535, - # they might be normalized differently between different Python 2 builds (narrow vs. wide Unicode) - assert isinstance(passphrase, unicode) - if sys.maxunicode < 65536: # the check for narrow Unicode builds looks for UTF-16 surrogate pairs: - maybe_buggy = any(0xD800 <= ord(c) <= 0xDBFF or 0xDC00 <= ord(c) <= 0xDFFF for c in passphrase) - else: # the check for wide Unicode builds: - maybe_buggy = any(ord(c) > 65535 for c in passphrase) - if maybe_buggy: - print("warning: due to Unicode incompatibilities, it's strongly recommended\n" - " that you run seedrecover.py on the same computer (or at least\n" - " the same OS) where you created your wallet", file=sys.stderr) - - if expected_len_specified and num_inserts: - print("notice: for Electrum 2.x, --mnemonic-length is the max length tried, but not necessarily the min", - file=sys.stderr) - - # The pbkdf2-derived salt (needed by _derive_seed()); Electrum 2.x is similar to BIP39, - # however it differs in the iffy(?) normalization procedure and the prepended string - import string - passphrase = unicodedata.normalize("NFKD", passphrase) # problematic w/Python narrow Unicode builds, same as Electrum - passphrase = passphrase.lower() # (?) - passphrase = filter(lambda c: not unicodedata.combining(c), passphrase) # remove combining marks - passphrase = u" ".join(passphrase.split()) # replace whitespace sequences with a single ASCII space - # remove ASCII whitespace between CJK characters (?) - passphrase = u"".join(c for i,c in enumerate(passphrase) if not ( - c in string.whitespace - and any(intvl[0] <= ord(passphrase[i-1]) <= intvl[1] for intvl in self.CJK_INTERVALS) - and any(intvl[0] <= ord(passphrase[i+1]) <= intvl[1] for intvl in self.CJK_INTERVALS))) - self._derivation_salt = "electrum" + passphrase.encode("utf_8") - - # Electrum 2.x doesn't separate mnemonic words with spaces in sentences for any CJK - # scripts when calculating the checksum or deriving a binary seed (even though this - # seem inappropriate for some CJK scripts such as Hiragana as used by the ja wordlist) - self._space = "" if self._lang in ("ja", "zh-hans", "zh-hant") else " " - - # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped - @staticmethod - def verify_mnemonic_syntax(mnemonic_ids): - # As long as each wordlist has at least 1411 words (checked by _load_wordlists()), - # a valid mnemonic is at most 13 words long (and all ids must be present) - return len(mnemonic_ids) <= 13 and None not in mnemonic_ids - - # Called by WalletBIP32.return_verified_password_or_false() to verify an Electrum2 checksum - def _verify_checksum(self, mnemonic_words): - return hmac.new("Seed version", self._space.join(mnemonic_words), hashlib.sha512) \ - .digest()[0] == "\x01" - - # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed - def _derive_seed(self, mnemonic_words): - # Note: the words are already in Electrum2's normalized form - return btcrpass.pbkdf2_hmac("sha512", self._space.join(mnemonic_words), self._derivation_salt, 2048) - - # Returns a dummy xpub for performance testing purposes - @staticmethod - def _performance_xpub(): - # an xpub at path m, as Electrum would export - return "xpub661MyMwAqRbcGsUXkGBkytQkYZ6M16bFWwTocQDdPSm6eJ1wUsxG5qty1kTCUq7EztwMscUstHVo1XCJMxWyLn4PP1asLjt4gPt3HkA81qe" - - -############### Ethereum ############### - -@register_selectable_wallet_class('Ethereum Standard BIP39/BIP44 (Jaxx, MetaMask, MyEtherWallet, TREZOR, Exodus, NOT Ledger)') -class WalletEthereum(WalletBIP39): - - def __init__(self, path = None, loading = False): - if not path: path = "m/44'/60'/0'/0/" - super(WalletEthereum, self).__init__(path, loading) - global sha3 - import sha3 - - def __setstate__(self, state): - super(WalletEthereum, self).__setstate__(state) - # (re-)load the required libraries after being unpickled - global sha3 - import sha3 - - @classmethod - def create_from_params(cls, *args, **kwargs): - if isinstance(kwargs.get("hash160s"), AddressSet): - raise ValueError("can't use an address database with Ethereum wallets") - self = super(WalletEthereum, cls).create_from_params(*args, **kwargs) - if hasattr(self, "_known_hash160s") and isinstance(self._known_hash160s, AddressSet): - raise ValueError("can't use an address database with Ethereum wallets") - return self - - @staticmethod - def _addresses_to_hash160s(addresses): - hash160s = set() - for address in addresses: - if address[:2].lower() == "0x": - address = address[2:] - if len(address) != 40: - raise ValueError("length (excluding any '0x' prefix) of Ethereum addresses must be 40") - cur_hash160 = base64.b16decode(address, casefold=True) - if not address.islower(): # verify the EIP55 checksum unless all letters are lowercase - checksum = sha3.keccak_256(base64.b16encode(cur_hash160).lower()).digest() - for nibble, c in enumerate(address, 0): - if c.isalpha() and \ - c.isupper() != bool(ord(checksum[nibble // 2]) & (0b1000 if nibble&1 else 0b10000000)): - raise ValueError("invalid EIP55 checksum") - hash160s.add(cur_hash160) - return hash160s - - @staticmethod - def pubkey_to_hash160(uncompressed_pubkey): - """convert from an uncompressed public key to its Ethereum hash160 form - - :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString - :type uncompressed_pubkey: str - :return: last 20 bytes of keccak256(raw_64_byte_pubkey) - :rtype: str - """ - assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == "\x04" - return sha3.keccak_256(uncompressed_pubkey[1:]).digest()[-20:] - - -################################### Main ################################### - - -tk_root = None -def init_gui(): - global tk_root, tk, tkFileDialog, tkSimpleDialog, tkMessageBox - if not tk_root: - - if sys.platform == "win32": - # Some py2exe .dll's, when registered as Windows shell extensions (e.g. SpiderOak), can interfere - # with Python scripts which spawn a shell (e.g. a file selection dialog). The code below blocks - # required modules from loading and prevents any such py2exe .dlls from causing too much trouble. - sys.modules["win32api"] = None - sys.modules["win32com"] = None - - import Tkinter as tk - import tkFileDialog, tkSimpleDialog, tkMessageBox - tk_root = tk.Tk(className="seedrecover.py") # initialize library - tk_root.withdraw() # but don't display a window (yet) - - -# seed.py uses routines from password.py to generate guesses, however instead -# of dealing with passwords (immutable sequences of characters), it deals with -# seeds (represented as immutable sequences of mnemonic_ids). More specifically, -# seeds are tuples of mnemonic_ids, and a mnemonic_id is just an int for Electrum1, -# or a UTF-8 bytestring id for most other wallet types. - -# These are simple typo generators; see btcrpass.py for additional information. -# Instead of returning iterables of sequences of characters (iterables of strings), -# these return iterables of sequences of mnemonic_ids (iterables of partial seeds). -# -@btcrpass.register_simple_typo("deleteword") -def delete_word(mnemonic_ids, i): - return (), -# -@btcrpass.register_simple_typo("replaceword") -def replace_word(mnemonic_ids, i): - if mnemonic_ids[i] is None: return (), # don't touch invalid words - return ((new_id,) for new_id in loaded_wallet.word_ids if new_id != mnemonic_ids[i]) -# -@btcrpass.register_simple_typo("replacecloseword") -def replace_close_word(mnemonic_ids, i): - if mnemonic_ids[i] is None: return (), # don't touch invalid words - return close_mnemonic_ids[mnemonic_ids[i]] # the pre-calculated similar words -# -@btcrpass.register_simple_typo("replacewrongword") -def replace_wrong_word(mnemonic_ids, i): - if mnemonic_ids[i] is not None: return (), # only replace invalid words - return ((new_id,) for new_id in loaded_wallet.word_ids) - - -# Builds a command line and then runs btcrecover with it. -# typos - max number of mistakes to apply to each guess -# big_typos - max number of "big" mistakes to apply to each guess; -# a big mistake involves replacing or inserting a word using the -# full word list, and significantly increases the search time -# min_typos - min number of mistakes to apply to each guess -num_inserts = num_deletes = 0 -def run_btcrecover(typos, big_typos = 0, min_typos = 0, is_performance = False, extra_args = []): - if typos < 0: # typos == 0 is silly, but causes no harm - raise ValueError("typos must be >= 0") - if big_typos < 0: - raise ValueError("big-typos must be >= 0") - if big_typos > typos: - raise ValueError("typos includes big_typos, therefore it must be >= big_typos") - # min_typos < 0 is silly, but causes no harm - # typos < min_typos is an error; it's checked in btcrpass.parse_arguments() - - # Local copies of globals whose changes should only be visible locally - l_num_inserts = num_inserts - l_num_deletes = num_deletes - - # Number of words that were definitely wrong in the guess - num_wrong = sum(map(lambda id: id is None, mnemonic_ids_guess)) - - # Start building the command-line arguments - btcr_args = "--typos " + str(typos) - - if is_performance: - btcr_args += " --performance" - # These typos are not supported by seedrecover with --performance testing: - l_num_inserts = l_num_deletes = num_wrong = 0 - - # First, check if there are any required typos (if there are missing or extra - # words in the guess) and adjust the max number of other typos to later apply - - any_typos = typos # the max number of typos left after removing required typos - #big_typos = # the max number of "big" typos after removing required typos (an arg from above) - - if l_num_deletes: # if the guess is too long (extra words need to be deleted) - any_typos -= l_num_deletes - btcr_args += " --typos-deleteword" - if l_num_deletes < typos: - btcr_args += " --max-typos-deleteword " + str(l_num_deletes) - - if num_wrong: # if any of the words were invalid (and need to be replaced) - any_typos -= num_wrong - big_typos -= num_wrong - btcr_args += " --typos-replacewrongword" - if num_wrong < typos: - btcr_args += " --max-typos-replacewrongword " + str(num_wrong) - - # For (only) Electrum2, num_inserts are not required, so we try several sub-phases with a - # different number of inserts each time; for all others the total num_inserts are required - if isinstance(loaded_wallet, WalletElectrum2): - num_inserts_to_try = xrange(l_num_inserts + 1) # try a range - else: - num_inserts_to_try = l_num_inserts, # only try the required max - for subphase_num, cur_num_inserts in enumerate(num_inserts_to_try, 1): - - # Create local copies of these which are reset at the beginning of each loop - l_any_typos = any_typos - l_big_typos = big_typos - l_btcr_args = btcr_args - - ids_to_try_inserting = None - if cur_num_inserts: # if the guess is too short (words need to be inserted) - l_any_typos -= cur_num_inserts - l_big_typos -= cur_num_inserts - # (instead of --typos-insert we'll set inserted_items=ids_to_try_inserting below) - ids_to_try_inserting = ((id,) for id in loaded_wallet.word_ids) - l_btcr_args += " --max-adjacent-inserts " + str(cur_num_inserts) - if cur_num_inserts < typos: - l_btcr_args += " --max-typos-insert " + str(cur_num_inserts) - - # For >1 subphases, print this out now or just after the skip-this-phase check below - if len(num_inserts_to_try) > 1: - subphase_msg = " - subphase {}/{}: with {} inserted seed word{}".format( - subphase_num, len(num_inserts_to_try), - cur_num_inserts, "" if cur_num_inserts == 1 else "s") - if subphase_num > 1: - print(subphase_msg) - maybe_skipping = "the remainder of this phase." - else: - maybe_skipping = "this phase." - - if l_any_typos < 0: # if too many typos are required to generate valid mnemonics - print("Not enough mistakes permitted to produce a valid seed; skipping", maybe_skipping) - return False - if l_big_typos < 0: # if too many big typos are required to generate valid mnemonics - print("Not enough entirely different seed words permitted; skipping", maybe_skipping) - return False - assert typos >= cur_num_inserts + l_num_deletes + num_wrong - - if subphase_num == 1 and len(num_inserts_to_try) > 1: - print(subphase_msg) - - # Because btcrecover doesn't support --min-typos-* on a per-typo basis, it ends - # up generating some invalid guesses. We can use --min-typos to filter out some - # of them (the remainder is later filtered out by verify_mnemonic_syntax()). - min_typos = max(min_typos, cur_num_inserts + l_num_deletes + num_wrong) - if min_typos: - l_btcr_args += " --min-typos " + str(min_typos) - - # Next, if the required typos above haven't consumed all available typos - # (as specified by the function's args), add some "optional" typos - - if l_any_typos: - l_btcr_args += " --typos-swap" - if l_any_typos < typos: - l_btcr_args += " --max-typos-swap " + str(l_any_typos) - - if l_big_typos: # if there are any big typos left, add the replaceword typo - l_btcr_args += " --typos-replaceword" - if l_big_typos < typos: - l_btcr_args += " --max-typos-replaceword " + str(l_big_typos) - - # only add replacecloseword typos if they're not already covered by the - # replaceword typos added above and there exists at least one close word - num_replacecloseword = l_any_typos - l_big_typos - if num_replacecloseword > 0 and any(len(ids) > 0 for ids in close_mnemonic_ids.itervalues()): - l_btcr_args += " --typos-replacecloseword" - if num_replacecloseword < typos: - l_btcr_args += " --max-typos-replacecloseword " + str(num_replacecloseword) - - btcrpass.parse_arguments( - l_btcr_args.split() + extra_args, - inserted_items= ids_to_try_inserting, - wallet= loaded_wallet, - base_iterator= (mnemonic_ids_guess,) if not is_performance else None, # the one guess to modify - perf_iterator= lambda: loaded_wallet.performance_iterator(), - check_only= loaded_wallet.verify_mnemonic_syntax - ) - (mnemonic_found, not_found_msg) = btcrpass.main() - - if mnemonic_found: - return mnemonic_found - elif not_found_msg is None: - return None # An error occurred or Ctrl-C was pressed inside btcrpass.main() - - return False # No error occurred; the mnemonic wasn't found - - -def register_autodetecting_wallets(): - """Registers wallets which can do file auto-detection with btcrecover's auto-detect mechanism - - :rtype: None - """ - btcrpass.clear_registered_wallets() - for wallet_cls, description in selectable_wallet_classes: - if hasattr(wallet_cls, "is_wallet_file"): - btcrpass.register_wallet_class(wallet_cls) - - -def main(argv): - global loaded_wallet - loaded_wallet = wallet_type = None - create_from_params = {} # additional args to pass to wallet_type.create_from_params() - config_mnemonic_params = {} # additional args to pass to wallet.config_mnemonic() - phase = {} # if only one phase is requested, the args to pass to run_btcrecover() - extra_args = [] # additional args to pass to btcrpass.parse_arguments() (in run_btcrecover()) - - if argv or "_ARGCOMPLETE" in os.environ: - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--wallet", metavar="FILE", help="the wallet file") - parser.add_argument("--wallet-type", metavar="TYPE", help="if not using a wallet file, the wallet type") - parser.add_argument("--mpk", metavar="XPUB-OR-HEX", help="if not using a wallet file, the master public key") - parser.add_argument("--addrs", metavar="ADDRESS", nargs="+", help="if not using an mpk, address(es) in the wallet") - parser.add_argument("--addressdb", metavar="FILE", nargs="?", help="if not using addrs, use a full address database (default: %(const)s)", const=ADDRESSDB_DEF_FILENAME) - parser.add_argument("--addr-limit", type=int, metavar="COUNT", help="if using addrs or addressdb, the generation limit") - parser.add_argument("--typos", type=int, metavar="COUNT", help="the max number of mistakes to try (default: auto)") - parser.add_argument("--big-typos", type=int, metavar="COUNT", help="the max number of big (entirely different word) mistakes to try (default: auto or 0)") - parser.add_argument("--min-typos", type=int, metavar="COUNT", help="enforce a min # of mistakes per guess") - parser.add_argument("--close-match",type=float,metavar="CUTOFF",help="try words which are less/more similar for each mistake (0.0 to 1.0, default: 0.65)") - parser.add_argument("--passphrase", action="store_true", help="the mnemonic is augmented with a known passphrase (BIP39 or Electrum 2.x only)") - parser.add_argument("--passphrase-prompt", action="store_true", help="prompt for the mnemonic passphrase via the terminal (default: via the GUI)") - parser.add_argument("--mnemonic-prompt", action="store_true", help="prompt for the mnemonic guess via the terminal (default: via the GUI)") - parser.add_argument("--mnemonic-length", type=int, metavar="WORD-COUNT", help="the length of the correct mnemonic (default: auto)") - parser.add_argument("--language", metavar="LANG-CODE", help="the wordlist language to use (see wordlists/README.md, default: auto)") - parser.add_argument("--bip32-path", metavar="PATH", help="path (e.g. m/0'/0/) excluding the final index (default: BIP44 account 0)") - parser.add_argument("--skip", type=int, metavar="COUNT", help="skip this many initial passwords for continuing an interrupted search") - parser.add_argument("--threads", type=int, metavar="COUNT", help="number of worker threads (default: number of CPUs, {})".format(btcrpass.cpus)) - parser.add_argument("--worker", metavar="ID#/TOTAL#", help="divide the workload between TOTAL# servers, where each has a different ID# between 1 and TOTAL#") - parser.add_argument("--max-eta", type=int, help="max estimated runtime before refusing to even start (default: 168 hours, i.e. 1 week)") - parser.add_argument("--no-eta", action="store_true", help="disable calculating the estimated time to completion") - parser.add_argument("--no-dupchecks",action="store_true", help="disable duplicate guess checking to save memory") - parser.add_argument("--no-progress", action="store_true", help="disable the progress bar") - parser.add_argument("--no-pause", action="store_true", help="never pause before exiting (default: auto)") - parser.add_argument("--performance", action="store_true", help="run a continuous performance test (Ctrl-C to exit)") - parser.add_argument("--btcr-args", action="store_true", help=argparse.SUPPRESS) - parser.add_argument("--version","-v",action="store_true", help="show full version information and exit") - - # Optional bash tab completion support - try: - import argcomplete - argcomplete.autocomplete(parser) - except ImportError: - pass - assert argv - - # Parse the args; unknown args will be passed to btcrpass.parse_arguments() iff --btcr-args is specified - args, extra_args = parser.parse_known_args(argv) - if extra_args and not args.btcr_args: - parser.parse_args(argv) # re-parse them just to generate an error for the unknown args - assert False - - # Version information is always printed by seedrecover.py, so just exit - if args.version: sys.exit(0) - - if args.wallet: - loaded_wallet = btcrpass.load_wallet(args.wallet) - - # Look up the --wallet-type arg in the list of selectable_wallet_classes - if args.wallet_type: - if args.wallet: - print("warning: --wallet-type is ignored when a wallet is provided", file=sys.stderr) - else: - args.wallet_type = args.wallet_type.lower() - wallet_type_names = [] - for cls, desc in selectable_wallet_classes: - wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower()) - if wallet_type_names[-1] == args.wallet_type: - wallet_type = cls - break - else: - wallet_type_names.sort() - sys.exit("--wallet-type must be one of: " + ", ".join(wallet_type_names)) - - if args.mpk: - if args.wallet: - print("warning: --mpk is ignored when a wallet is provided", file=sys.stderr) - else: - create_from_params["mpk"] = args.mpk - - if args.addrs: - if args.wallet: - print("warning: --addrs is ignored when a wallet is provided", file=sys.stderr) - else: - create_from_params["addresses"] = args.addrs - - if args.addr_limit is not None: - if args.wallet: - print("warning: --addr-limit is ignored when a wallet is provided", file=sys.stderr) - else: - create_from_params["address_limit"] = args.addr_limit - - if args.addressdb and not os.path.isfile(args.addressdb): - sys.exit("file '{}' does not exist".format(args.addressdb)) - - if args.typos is not None: - phase["typos"] = args.typos - - if args.big_typos is not None: - phase["big_typos"] = args.big_typos - if not args.typos: - phase["typos"] = args.big_typos - - if args.min_typos is not None: - if not phase.get("typos"): - sys.exit("--typos must be specified when using --min_typos") - phase["min_typos"] = args.min_typos - - if args.close_match is not None: - config_mnemonic_params["closematch_cutoff"] = args.close_match - - if args.mnemonic_prompt: - encoding = sys.stdin.encoding or "ASCII" - if "utf" not in encoding.lower(): - print("terminal does not support UTF; mnemonics with non-ASCII chars might not work", file=sys.stderr) - mnemonic_guess = raw_input("Please enter your best guess for your mnemonic (seed)\n> ") - if not mnemonic_guess: - sys.exit("canceled") - if isinstance(mnemonic_guess, str): - mnemonic_guess = mnemonic_guess.decode(encoding) # convert from terminal's encoding to unicode - config_mnemonic_params["mnemonic_guess"] = mnemonic_guess - - if args.passphrase_prompt: - import getpass - encoding = sys.stdin.encoding or "ASCII" - if "utf" not in encoding.lower(): - print("warning: terminal does not support UTF; passwords with non-ASCII chars might not work", file=sys.stderr) - print("(note your passphrase will not be displayed as you type)") - while True: - passphrase = getpass.getpass("Please enter the passphrase you added when the seed was first created: ") - if not passphrase: - sys.exit("canceled") - if passphrase == getpass.getpass("Please re-enter the passphrase: "): - break - print("The passphrases did not match, try again.") - if isinstance(passphrase, str): - passphrase = passphrase.decode(encoding) # convert from terminal's encoding to unicode - config_mnemonic_params["passphrase"] = passphrase - elif args.passphrase: - config_mnemonic_params["passphrase"] = True # config_mnemonic() will prompt for one - - if args.language: - config_mnemonic_params["lang"] = args.language.lower() - - if args.mnemonic_length is not None: - config_mnemonic_params["expected_len"] = args.mnemonic_length - - if args.bip32_path: - if args.wallet: - print("warning: --bip32-path is ignored when a wallet is provided", file=sys.stderr) - else: - create_from_params["path"] = args.bip32_path - - # These arguments and their values are passed on to btcrpass.parse_arguments() - for argkey in "skip", "threads", "worker", "max_eta": - if args.__dict__[argkey] is not None: - extra_args.extend(("--"+argkey.replace("_", "-"), str(args.__dict__[argkey]))) - - # These arguments (which have no values) are passed on to btcrpass.parse_arguments() - for argkey in "no_eta", "no_dupchecks", "no_progress": - if args.__dict__[argkey]: - extra_args.append("--"+argkey.replace("_", "-")) - - if args.performance: - create_from_params["is_performance"] = phase["is_performance"] = True - phase.setdefault("typos", 0) - if not args.mnemonic_prompt: - # Create a dummy mnemonic; only its language and length are used for anything - config_mnemonic_params["mnemonic_guess"] = " ".join("act" for i in xrange(args.mnemonic_length or 12)) - - if args.addressdb: - print("Loading address database ...") - create_from_params["hash160s"] = AddressSet.fromfile(open(args.addressdb, "rb")) - - else: # else if no command-line args are present - global pause_at_exit - pause_at_exit = True - atexit.register(lambda: pause_at_exit and - not multiprocessing.current_process().name.startswith("PoolWorker-") and - raw_input("Press Enter to exit ...")) - - if not loaded_wallet and not wallet_type: # neither --wallet nor --wallet-type were specified - - # Ask for a wallet file - init_gui() - wallet_filename = tkFileDialog.askopenfilename(title="Please select your wallet file if you have one") - if wallet_filename: - loaded_wallet = btcrpass.load_wallet(wallet_filename) # raises on failure; no second chance - - if not loaded_wallet: # if no wallet file was chosen - - if not wallet_type: # if --wallet-type wasn't specified - - # Without a wallet file, we can't automatically determine the wallet type, so prompt the - # user to select a wallet that's been registered with @register_selectable_wallet_class - selectable_wallet_classes.sort(key=lambda x: x[1]) # sort by description - class WalletTypeDialog(tkSimpleDialog.Dialog): - def body(self, master): - self.wallet_type = None - self._index_to_cls = [] - self._selected_index = tk.IntVar(value= -1) - for i, (cls, desc) in enumerate(selectable_wallet_classes): - self._index_to_cls.append(cls) - tk.Radiobutton(master, variable=self._selected_index, value=i, text=desc) \ - .pack(anchor=tk.W) - def validate(self): - if self._selected_index.get() < 0: - tkMessageBox.showwarning("Wallet Type", "Please select a wallet type") - return False - return True - def apply(self): - self.wallet_type = self._index_to_cls[self._selected_index.get()] - # - wallet_type_dialog = WalletTypeDialog(tk_root, "Please select your wallet type") - wallet_type = wallet_type_dialog.wallet_type - if not wallet_type: - sys.exit("canceled") - - try: - loaded_wallet = wallet_type.create_from_params(**create_from_params) - except TypeError as e: - matched = re.match("create_from_params\(\) got an unexpected keyword argument '(.*)'", str(e)) - if matched: - sys.exit("{} does not support the {} option".format(wallet_type.__name__, matched.group(1))) - raise - except ValueError as e: - sys.exit(e) - - try: - loaded_wallet.config_mnemonic(**config_mnemonic_params) - except TypeError as e: - matched = re.match("config_mnemonic\(\) got an unexpected keyword argument '(.*)'", str(e)) - if matched: - sys.exit("{} does not support the {} option".format(loaded_wallet.__class__.__name__, matched.group(1))) - raise - except ValueError as e: - sys.exit(e) - - # Seeds for some wallet types have a checksum which is unlikely to be correct - # for the initial provided seed guess; if it is correct, let the user know - try: - if ( loaded_wallet._initial_words_valid - and loaded_wallet.verify_mnemonic_syntax(mnemonic_ids_guess) - and loaded_wallet._verify_checksum(mnemonic_ids_guess) ): - print(u"Initial seed guess has a valid checksum ({:.2g}% chance).".format(loaded_wallet._checksum_ratio * 100.0)) - except AttributeError: pass - - # Now that most of the GUI code is done, undo any Windows shell extension workarounds from init_gui() - if sys.platform == "win32" and tk_root: - del sys.modules["win32api"] - del sys.modules["win32com"] - # Some py2exe-compiled .dll shell extensions set sys.frozen, which should only be set - # for "frozen" py2exe .exe's; this causes problems with multiprocessing, so delete it - try: - del sys.frozen - except AttributeError: pass - - if phase: - phases = (phase,) - # Set reasonable defaults for the search phases - else: - # If each guess is very slow, separate out the first two phases - passwords_per_seconds = loaded_wallet.passwords_per_seconds(1) - if passwords_per_seconds < 25: - phases = [ dict(typos=1), dict(typos=2, min_typos=2) ] - else: - phases = [ dict(typos=2) ] - # - # These two phases are added to all searches - phases.extend(( dict(typos=1, big_typos=1), dict(typos=2, big_typos=1, min_typos=2) )) - # - # Add a final more thorough phase if it's not likely to take more than a few hours - if len(mnemonic_ids_guess) <= 13 and passwords_per_seconds >= 750 or \ - len(mnemonic_ids_guess) <= 19 and passwords_per_seconds >= 2500: - phases.append(dict(typos=3, big_typos=1, min_typos=3, extra_args=["--no-dupchecks"])) - - for phase_num, phase_params in enumerate(phases, 1): - - # Print a friendly message describing this phase's search settings - print("Phase {}/{}: ".format(phase_num, len(phases)), end="") - if phase_params["typos"] == 1: - print("1 mistake", end="") - else: - print("up to {} mistakes".format(phase_params["typos"]), end="") - if phase_params.get("big_typos"): - if phase_params["big_typos"] == phase_params["typos"] == 1: - print(" which can be an entirely different seed word.") - else: - print(", {} of which can be an entirely different seed word.".format(phase_params["big_typos"])) - else: - print(", excluding entirely different seed words.") - - # Perform this phase's search - phase_params.setdefault("extra_args", []).extend(extra_args) - mnemonic_found = run_btcrecover(**phase_params) - - if mnemonic_found: - return " ".join(loaded_wallet.id_to_word(i) for i in mnemonic_found).decode("utf_8") - elif mnemonic_found is None: - return None # An error occurred or Ctrl-C was pressed inside btcrpass.main() - else: - print("Seed not found" + ( ", sorry..." if phase_num==len(phases) else "" )) - - return False # No error occurred; the mnemonic wasn't found - -def show_mnemonic_gui(mnemonic_sentence): - """may be called *after* main() to display the successful result iff the GUI is in use - - :param mnemonic_sentence: the mnemonic sentence that was found - :type mnemonic_sentence: unicode - :rtype: None - """ - assert tk_root - global pause_at_exit - padding = 6 - tk.Label(text="WARNING: seed information is sensitive, carefully protect it and do not share", fg="red") \ - .pack(padx=padding, pady=padding) - tk.Label(text="Seed found:").pack(side=tk.LEFT, padx=padding, pady=padding) - entry = tk.Entry(width=80, readonlybackground="white") - entry.insert(0, mnemonic_sentence) - entry.config(state="readonly") - entry.select_range(0, tk.END) - entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=padding, pady=padding) - tk_root.deiconify() - tk_root.lift() - entry.focus_set() - tk_root.mainloop() # blocks until the user closes the window - pause_at_exit = False +# btcrseed.py -- btcrecover mnemonic sentence library +# Copyright (C) 2014-2017 Christopher Gurnee +# 2019-2021 Stephen Rothery +# +# This file is part of btcrecover. +# +# btcrecover is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# btcrecover is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +# TODO: finish pythonizing comments/documentation + +__version__ = "1.13.0-CryptoGuide" + +disable_security_warnings = True + +# Import modules included in standard libraries +import sys, os, io, base64, hashlib, hmac, difflib, itertools, \ + unicodedata, collections, struct, glob, atexit, re, random, multiprocessing, binascii, copy, datetime +import bisect +from typing import AnyStr, List, Optional, Sequence, Tuple, TypeVar, Union + +# Import modules bundled with BTCRecover +from . import aezeed, btcrpass +from . import success_alert +from .addressset import AddressSet +from lib.bitcoinlib import encoding +from lib.cashaddress import convert, base58 +from lib.base58_tools import base58_tools +from lib.eth_hash.auto import keccak +import btcrecover.opencl_helpers +from lib.pyzil.account import Account as zilliqa_account +import lib.bech32 as bech32 +import lib.cardano.cardano_utils as cardano +import lib.stacks.c32 as c32 +from lib.p2tr_helper import P2TR_tools + +# import bundled modules that won't work in some environments +bundled_bitcoinlib_mod_available = False +try: + from lib.bitcoinlib_mod import encoding as encoding_mod + + bundled_bitcoinlib_mod_available = True +except: + pass + +# Enable functions that may not work for some standard libraries in some environments +hashlib_ripemd160_available = False + +try: + # this will work with micropython and python < 3.10 + # but will raise and exception if ripemd is not supported (python3.10, openssl 3) + hashlib.new('ripemd160') + hashlib_ripemd160_available = True + def ripemd160(msg): + return hashlib.new('ripemd160', msg).digest() +except: + # otherwise use pure python implementation + from lib.embit.py_ripemd160 import ripemd160 + +# Import modules from requirements.txt +try: + import coincurve +except ModuleNotFoundError: + exit("\nERROR: Cannot load coincurve module... Be sure to install all requirements with the command 'pip3 install -r requirements.txt', see https://btcrecover.readthedocs.io/en/latest/INSTALL/") + +# Import optional modules +module_opencl_available = False +try: + from lib.opencl_brute import opencl + from lib.opencl_brute.opencl_information import opencl_information + import pyopencl + module_opencl_available = True +except: + pass + +py_crypto_hd_wallet_available = False +try: + import py_crypto_hd_wallet + + py_crypto_hd_wallet_available = True +except: + pass + +nacl_available = False +try: + import nacl.bindings + + nacl_available = True +except: + pass + +bitstring_available = False +try: + from bitstring import BitArray + + bitstring_available = True +except: + pass + +bip_utils_available = False +try: + from bip_utils import Bip32Slip10Ed25519, Bip39SeedGenerator + + bip_utils_available = True +except Exception: + pass + +eth2_staking_deposit_available = False +try: + from staking_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key + from py_ecc.bls import G2ProofOfPossession as bls + + eth2_staking_deposit_available = True +except: + pass + +shamir_mnemonic_available = False +slip39_min_words = 20 +try: + import shamir_mnemonic + from shamir_mnemonic.constants import MIN_MNEMONIC_LENGTH_WORDS as slip39_min_words + shamir_mnemonic_available = True +except Exception: + pass + + +_T = TypeVar("_T") + +# Pulled from https://github.com/trezor/python-mnemonic and modified to fix bug in Trezor derivation +# From +def binary_search( + a: Sequence[_T], + x: _T, + lo: int = 0, + hi: Optional[int] = None, # can't use a to specify default for hi +) -> int: + hi = hi if hi is not None else len(a) # hi defaults to len(a) + pos = bisect.bisect_left(a, x, lo, hi) # find insertion position + return pos if pos != hi and a[pos] == x else -1 # don't walk off the end + + +# Order of the base point generator, from SEC 2 +GENERATOR_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + +ADDRESSDB_DEF_FILENAME = "addresses.db" + +no_gui = False + +def full_version(): + return "seedrecover {}, {}".format( + __version__, + btcrpass.full_version() + ) + + +################################### Utility Functions ################################### + + +def bytes_to_int(bytes_rep): + """convert a string of bytes (in big-endian order) to a long integer + + :param bytes_rep: the raw bytes + :type bytes_rep: str + :return: the unsigned integer + :rtype: long + """ + return int(base64.b16encode(bytes_rep), 16) + +def int_to_bytes(int_rep, min_length): + """convert an unsigned integer to a string of bytes (in big-endian order) + + :param int_rep: a non-negative integer + :type int_rep: long or int + :param min_length: the minimum output length + :type min_length: int + :return: the raw bytes, zero-padded (at the beginning) if necessary + :rtype: str + """ + assert int_rep >= 0 + hex_rep = "{:X}".format(int_rep) + if len(hex_rep) % 2 == 1: # The hex decoder below requires + hex_rep = "0" + hex_rep # exactly 2 chars per byte. + return base64.b16decode(hex_rep).rjust(min_length, "\0".encode("utf-8")) + + +dec_digit_to_base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +base58_digit_to_dec = { b58:dec for dec,b58 in enumerate(dec_digit_to_base58) } + + +def base58check_to_bytes(base58_rep, expected_size): + """decode a base58check string to its raw bytes + + :param base58_rep: check-code appended base58-encoded string + :type base58_rep: str + :param expected_size: the expected number of decoded bytes (excluding the check code) + :type expected_size: int + :return: the base58-decoded bytes + :rtype: str + """ + base58_stripped = base58_rep.lstrip("1") + + int_rep = 0 + for base58_digit in base58_stripped: + int_rep *= 58 + int_rep += base58_digit_to_dec[base58_digit] + + # Convert int to raw bytes + all_bytes = int_to_bytes(int_rep, expected_size + 4) + + zero_count = next(zeros for zeros,byte in enumerate(all_bytes) if byte != "\0") + if len(base58_rep) - len(base58_stripped) != zero_count: + raise ValueError("prepended zeros mismatch") + + + if hashlib.sha256(hashlib.sha256(all_bytes[:-4]).digest()).digest()[:4] != all_bytes[-4:]: + global groestlcoin_hash + if groestlcoin_hash.getHash(all_bytes[:-4], len(all_bytes[:-4]))[:4] != all_bytes[-4:]: + raise ValueError("base58 check code mismatch") + + return all_bytes[:-4] + +BIP32ExtendedKey = collections.namedtuple("BIP32ExtendedKey", + "version depth fingerprint child_number chaincode key") +# +def base58check_to_bip32(base58_rep): + """decode a bip32-serialized extended key from its base58check form + + :param base58_rep: check-code appended base58-encoded bip32 extended key + :type base58_rep: str + :return: a namedtuple containing: version depth fingerprint child_number chaincode key + :rtype: BIP32ExtendedKey + """ + decoded_bytes = base58check_to_bytes(base58_rep, 4 + 1 + 4 + 4 + 32 + 33) + return BIP32ExtendedKey(decoded_bytes[0:4], ord(decoded_bytes[ 4:5]), decoded_bytes[ 5:9], + struct.unpack(">I", decoded_bytes[9:13])[0], decoded_bytes[13:45], decoded_bytes[45:]) + +def compress_pubkey(uncompressed_pubkey): + """convert an uncompressed public key into a compressed public key + + :param uncompressed_pubkey: the uncompressed public key + :type uncompressed_pubkey: str + :return: the compressed public key + :rtype: str + """ + assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == 4 + return chr((uncompressed_pubkey[-1] & 1) + 2).encode() + uncompressed_pubkey[1:33] + + +def load_pathlist(pathlistFile): + pathlist_file = open(pathlistFile, "r") + pathlist_lines = pathlist_file.readlines() + pathlist = [] + for path in pathlist_lines: + if path[0] == '#' or len(path.strip()) == 0: + continue + pathlist.append(path.split("#")[0].strip()) + pathlist_file.close() + return pathlist + +def load_passphraselist(passphraselistFile): + passphraselist_file = open(passphraselistFile, "r") + passphraselist = passphraselist_file.read().splitlines() + passphraselist_file.close() + return passphraselist + +import hmac +import hashlib +from struct import pack + +def get_master_key_and_chain_code(seed): + key = b"ed25519 seed" + I = hmac.new(key, seed, hashlib.sha512).digest() + return I[:32], I[32:] + +def derive_child_key(parent_key, parent_chain_code, index): + # Hardened index: index >= 0x80000000 + assert index >= 0x80000000 + + data = b'\x00' + parent_key + pack(">L", index) + I = hmac.new(parent_chain_code, data, hashlib.sha512).digest() + return I[:32], I[32:] + +def derive_path(master_key, master_chain_code, path): + keys = (master_key, master_chain_code) + for index_str in path.lstrip("m/").split("/"): + hardened = index_str.endswith("'") + index = int(index_str.rstrip("'")) + if hardened: + index += 0x80000000 + keys = derive_child_key(keys[0], keys[1], index) + return keys + +################################### Wallets ################################### + +# A class decorator which adds a wallet class to a registered +# list that can later be selected by a user in GUI mode +selectable_wallet_classes = [] +def register_selectable_wallet_class(description): + def _register_selectable_wallet_class(cls): + selectable_wallet_classes.append((cls, description)) + return cls + return _register_selectable_wallet_class + + +# Loads a wordlist from a file into a list of Python unicodes. Note that the +# unicodes are normalized in NFC format, which is not what BIP39 requires (NFKD). +wordlists_dir = os.path.join(os.path.dirname(__file__), "wordlists") +def load_wordlist(name, lang): + filename = os.path.join(wordlists_dir, "{}-{}.txt".format(name, lang)) + with io.open(filename, encoding="utf_8_sig") as wordlist_file: + wordlist = [] + for word in wordlist_file: + word = word.strip() + if word and not word.startswith(u"#"): + wordlist.append(unicodedata.normalize("NFC", word)) + return wordlist + + +def calc_passwords_per_second(checksum_ratio, kdf_overhead, scalar_multiplies): + """estimate the number of mnemonics that can be checked per second (per CPU core) + + :param checksum_ratio: chances that a random mnemonic has the correct checksum [0.0 - 1.0] + :type checksum_ratio: float + :param kdf_overhead: overhead in seconds imposed by the kdf per each guess + :type kdf_overhead: float + :param scalar_multiplies: count of EC scalar multiplications required per each guess + :type scalar_multiplies: int + :return: estimated mnemonic check rate in hertz (per CPU core) + :rtype: float + """ + return 1.0 / (checksum_ratio * (kdf_overhead + scalar_multiplies*0.0001) + 0.00001) + + +# Convert any ypub, zpub, etc into an xpub +# This script uses version bytes as described in SLIP-132 +# https://github.com/satoshilabs/slips/blob/master/slip-0132.md +# Doesn't attempt any error checking, as this is handled by the callers. Will simply return the input MPK +# Given that derivation paths are specified elsewhere it's enough to just convert all Master Public Keys to an xpub... + +def convert_to_xpub(input_mpk): + output_mpk = input_mpk + + try: + input_mpk_b58 = base58.b58decode_check(input_mpk) + output_mpk_b58 = b'\x04\x88\xb2\x1e' + input_mpk_b58[4:] + output_mpk = base58.b58encode_check(output_mpk_b58) + except: + try: + input_mpk_b58 = base58.b58grsdecode_check(input_mpk) + output_mpk_b58 = b'\x04\x88\xb2\x1e' + input_mpk_b58[4:] + output_mpk = base58.b58grsencode_check(output_mpk_b58) + except: + pass + + return output_mpk + +############### WalletBase ############### + +# Methods common to most wallets, but overridden by WalletEthereum +class WalletBase(object): + opencl = False + opencl_algo = -1 + opencl_context_pbkdf2_sha512 = -1 + pre_start_benchmark = False + _skip_worker_checksum = False + _savevalidseeds = False + + def __init__(self, loading = False): + if not hashlib_ripemd160_available: + print("Warning: Native RIPEMD160 not available via Hashlib, using Pure-Python (This will significantly reduce performance)") + assert loading, "use load_from_filename or create_from_params to create a " + self.__class__.__name__ + + @staticmethod + def set_securityWarningsFlag(setflag): + global disable_security_warnings + disable_security_warnings = setflag + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + for address in addresses: + if address[:1] == "r": # Convert XRP addresses to standard Bitcoin Legacy Addresses + address = base58_tools.b58encode( + base58_tools.b58decode(address, alphabet=base58_tools.XRP_ALPHABET)).decode() + try: + # Check if we are getting BCH Cashaddresses and if so, convert them to standard legacy addresses + if address[:12].lower() == "bitcoincash:": + address = convert.to_legacy_address(address) + else: + try: + address = convert.to_legacy_address("bitcoincash:" + address) + except convert.InvalidAddress: + pass + hash160 = binascii.unhexlify(encoding.addr_base58_to_pubkeyhash(address, True)) #assume we have a P2PKH (Legacy) or Segwit (P2SH) so try a Base58 conversion + except (encoding.EncodingError, AssertionError) as e: + try: + hash160 = binascii.unhexlify(encoding.grs_addr_base58_to_pubkeyhash(address, True)) #assume we have a P2PKH (Legacy) or Segwit (P2SH) so try a Base58 conversion + except Exception as e: + try: + hash160 = binascii.unhexlify(encoding.addr_bech32_to_pubkeyhash(address, prefix=None, include_witver=False, as_hex=True)) #Base58 conversion above will give a keyError if attempted with a Bech32 address for things like BTC + except Exception as e: + if bundled_bitcoinlib_mod_available: + # Try for some obscure altcoins which require modified versions of Bitcoinlib + try: + hash160 = binascii.unhexlify(encoding_mod.grs_addr_base58_to_pubkeyhash(address, True)) + except Exception as e: + hash160 = binascii.unhexlify(encoding_mod.addr_bech32_to_pubkeyhash(address, prefix=None, include_witver=False, as_hex=True)) + else: + print("Address not valid and unable to load modified bitcoinlib on this platform...") + + hash160s.add(hash160) + return hash160s + + @staticmethod + def pubkey_to_hash160(uncompressed_pubkey): + """convert from an uncompressed public key to its Bitcoin compressed-pubkey hash160 form + + :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString + :type uncompressed_pubkey: str + :return: ripemd160(sha256(compressed_pubkey)) + :rtype: str + """ + return ripemd160(hashlib.sha256(compress_pubkey(uncompressed_pubkey)).digest()) + + # Simple accessor to be able to identify the BIP44 coin number of the wallet + def get_path_coin(self): + coin = 0 # Just assume bitcoin by default + try: + coin = self._path_indexes[0][1] - 2 ** 31 + except: + pass + + return coin + +############### Electrum1 ############### + +@register_selectable_wallet_class("Electrum 1.x (including wallets later upgraded to 2.x)") +class WalletElectrum1(WalletBase): + + _words = None + @classmethod + def _load_wordlist(cls): + if not cls._words: + cls._words = tuple(map(str, load_wordlist("electrum1", "en"))) # also converts to ASCII + cls._word_to_id = { word:id for id,word in enumerate(cls._words) } + + @property + def word_ids(self): return range(len(self._words)) + @classmethod + def id_to_word(cls, id): return cls._words[id] + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + # returns "maybe yes" or "definitely no" + return None if wallet_file.read(2) == b"{'" else False + + def __init__(self, loading = False): + super(WalletElectrum1, self).__init__(loading) + self._master_pubkey = None + self._passwords_per_second = None + + self._load_wordlist() + self._num_words = len(self._words) # needs to be an instance variable so it can be pickled + + def passwords_per_seconds(self, seconds): + if not self._passwords_per_second: + self._passwords_per_second = \ + calc_passwords_per_second(1, 0.12, 1 if self._master_pubkey else self._addrs_to_generate + 1) + return max(int(round(self._passwords_per_second * seconds)), 1) + + # Load an Electrum1 wallet file (the part of it we need, just the master public key) + @classmethod + def load_from_filename(cls, wallet_filename): + from ast import literal_eval + with open(wallet_filename) as wallet_file: + wallet = literal_eval(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE)) # up to 64M, typical size is a few k + return cls._load_from_dict(wallet) + + @classmethod + def _load_from_dict(cls, wallet): + seed_version = wallet.get("seed_version") + if seed_version is None: raise ValueError("Unrecognized wallet format (Electrum1 seed_version not found)") + if seed_version != 4: raise NotImplementedError("Unsupported Electrum1 seed version " + seed_version) + if not wallet.get("use_encryption"): raise ValueError("Electrum1 wallet is not encrypted") + master_pubkey = base64.b16decode(wallet["master_public_key"], casefold=True) + if len(master_pubkey) != 64: raise ValueError("Electrum1 master public key is not 64 bytes long") + self = cls(loading=True) + self._master_pubkey = "\x04".encode() + master_pubkey # prepend the uncompressed tag + return self + + # Creates a wallet instance from either an mpk, an addresses container and address_limit, + # or a hash160s container. If none of these were supplied, prompts the user for each. + @classmethod + def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, is_performance = False, address_start_index = None, force_p2sh = False, checksinglexpubaddress = False, force_p2tr = False, force_bip44 = False, force_bip84 = False, disable_p2sh = False, disable_p2tr = False, disable_bip44 = False, disable_bip84 = False): + self = cls(loading=True) + + # Process the mpk (master public key) argument + if mpk: + if len(mpk) != 128: + raise ValueError("an Electrum 1.x master public key must be exactly 128 hex digits long") + try: + mpk = base64.b16decode(mpk, casefold=True) + # (it's assigned to the self._master_pubkey later) + except TypeError as e: + raise ValueError(e) # consistently raise ValueError for any bad inputs + + # Process the hash160s argument + if hash160s: + if mpk: + print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr) + hash160s = None + else: + self._known_hash160s = hash160s + + # Process the addresses argument + if addresses: + if mpk or hash160s: + print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr) + addresses = None + else: + self._known_hash160s = self._addresses_to_hash160s(addresses) + + # Process the address_limit argument + if address_limit: + if mpk: + print("warning: address limit is ignored when an mpk is provided", file=sys.stderr) + address_limit = None + else: + address_limit = int(address_limit) + if address_limit <= 0: + raise ValueError("the address limit must be > 0") + # (it's assigned to self._addrs_to_generate later) + + if address_start_index: + self._address_start_index = address_start_index + if address_limit < 0: + raise ValueError("the address limit must zero or positive") + else: + self._address_start_index = 0 + + # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first + if not mpk and not addresses and not hash160s: + init_gui() + while True: + if tk_root: # Skip if TK is not available... + mpk = tk.simpledialog.askstring("Electrum 1.x master public key", + "Please enter your master public key if you have it, or click Cancel to search by an address instead:", + initialvalue="c79b02697b32d9af63f7d2bd882f4c8198d04f0e4dfc5c232ca0c18a87ccc64ae8829404fdc48eec7111b99bda72a7196f9eb8eb42e92514a758f5122b6b5fea" + if is_performance else None) + else: + print("Error: No MPK or addresses specified... Exiting...") + exit() + + if not mpk: + break # if they pressed Cancel, stop prompting for an mpk + mpk = mpk.strip() + try: + if len(mpk) != 128: + raise TypeError() + mpk = base64.b16decode(mpk, casefold=True) # raises TypeError() on failure + break + except TypeError: + tk.messagebox.showerror("Master public key", "The entered Electrum 1.x key is not exactly 128 hex digits long") + + # If an mpk has been provided (in the function call or from a user), convert it to the needed format + if mpk: + assert len(mpk) == 64, "mpk is 64 bytes long (after decoding from hex)" + self._master_pubkey = "\x04".encode() + mpk # prepend the uncompressed tag + + # If an mpk wasn't provided (at all), and addresses and hash160s arguments also + # weren't provided (in the original function call), prompt the user for addresses. + else: + if not addresses and not hash160s: + # init_gui() was already called above + self._known_hash160s = None + while True: + addresses = tk.simpledialog.askstring("Addresses", + "Please enter at least one address from your wallet, " + "preferably some created early in your wallet's lifetime:", + initialvalue="1Hp6UXuJjzt9eSBa9LhtW97KPb44bq4CAQ" if is_performance else None) + if not addresses: break + addresses.replace(",", " ") + addresses.replace(";", " ") + addresses = addresses.split() + if not addresses: break + try: + # (raises ValueError or TypeError on failure): + self._known_hash160s = self._addresses_to_hash160s(addresses) + break + except (ValueError, TypeError) as e: + tk.messagebox.showerror("Addresses", "An entered address is invalid ({})".format(e)) + + # If there are still no hash160s available (and no mpk), check for an address database before giving up + if not self._known_hash160s: + if os.path.isfile(ADDRESSDB_DEF_FILENAME): + print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' in the current directory.") + else: + print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr) + sys.exit("canceled") + + if not address_limit: + init_gui() # might not have been called yet + before_the = "one(s) you just entered" if addresses else "first one in actual use" + if tk_root: # Skip if TK is not available... + address_limit = tk.simpledialog.askinteger("Address limit", + "Please enter the address generation limit. Smaller will\n" + "be faster, but it must be equal to at least the number\n" + "of addresses created before the "+before_the+":\n" + "(If unsure, 10 is a sensible default...)", minvalue=1, initialvalue=10) + else: + print("No address generation limit specified... Exiting...") + exit() + + if not address_limit: + sys.exit("canceled") + self._addrs_to_generate = address_limit + + if not self._known_hash160s: + print("Loading address database ...") + self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb")) + print("Loaded", len(self._known_hash160s), "addresses from database ...") + + return self + + # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped + @staticmethod + def verify_mnemonic_syntax(mnemonic_ids): + return len(mnemonic_ids) in [12,24] and None not in mnemonic_ids + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def return_verified_password_or_false(self, mnemonic_ids_list): + # Copy some vars into local for a small speed boost + l_sha256 = hashlib.sha256 + hashlib_new = hashlib.new + num_words = self._num_words + num_words2 = num_words * num_words + + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + # In the event that a tokenlist based recovery is happening, convert the list from string sback to ints + if (type(mnemonic_ids[0]) == str): + new_mnemonic_ids = [] + for word in mnemonic_ids: + new_mnemonic_ids.append(self._words.index(word)) + mnemonic_ids = new_mnemonic_ids + + # Compute the binary seed from the word list the Electrum1 way + seed = "" + for i in range(0, len(mnemonic_ids), 3): + seed += "{:08x}".format( mnemonic_ids[i ] + + num_words * ( (mnemonic_ids[i + 1] - mnemonic_ids[i ]) % num_words ) + + num_words2 * ( (mnemonic_ids[i + 2] - mnemonic_ids[i + 1]) % num_words )) + # + + unstretched_seed = seed + for i in range(100000): # Electrum1's seed stretching + + #Check the types of the seed and stretched_seed variables and force back to bytes (Allows most code to stay as-is for Py3) + if type(seed) is str: + seed = seed.encode() + if type(unstretched_seed) is str: + unstretched_seed = unstretched_seed.encode() + + seed = l_sha256(seed + unstretched_seed).digest() + + # If a master public key was provided, check the pubkey derived from the seed against it + if self._master_pubkey: + try: + if coincurve.PublicKey.from_valid_secret(seed).format(compressed=False) == self._master_pubkey: + return mnemonic_ids, count # found it + except ValueError: continue + + # Else derive addrs_to_generate addresses from the seed, searching for a match with known_hash160s + else: + master_privkey = bytes_to_int(seed) + + try: master_pubkey_bytes = coincurve.PublicKey.from_valid_secret(seed).format(compressed=False)[1:] + except ValueError: continue + + for seq_num in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + # Compute the next deterministic private/public key pair the Electrum1 way. + # FYI we derive a privkey first, and then a pubkey from that because it's + # likely faster than deriving a pubkey directly from the base point and + # seed -- it means doing a simple modular addition instead of a point + # addition (plus a scalar point multiplication which is needed for both). + derivation = b"".join([str(seq_num).encode(), b":0:", master_pubkey_bytes]) + d_offset = bytes_to_int( l_sha256(l_sha256( + derivation # 0 means: not a change address + ).digest()).digest() ) + d_privkey = int_to_bytes((master_privkey + d_offset) % GENERATOR_ORDER, 32) + + d_pubkey = coincurve.PublicKey.from_valid_secret(d_privkey).format(compressed=False) + + # Compute the hash160 of the *uncompressed* public key, and check for a match + + if ripemd160(l_sha256(d_pubkey).digest()) in self._known_hash160s: + return mnemonic_ids, count # found it + + return False, count + + # Configures the values of four globals used later in config_btcrecover(): + # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes + @classmethod + def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65, expected_len = None): + if expected_len: + if expected_len not in [12,24]: + raise ValueError("Electrum1 mnemoincs can only be 12 or 24 words lon") + + # If a mnemonic guess wasn't provided, prompt the user for one + if not mnemonic_guess: + init_gui() + if tk_root: # Skip if TK is not available... + mnemonic_guess = tk.simpledialog.askstring("Electrum seed", + "Please enter your best guess for your Electrum seed:") + else: + print("No mnemonic guess specified... Exiting...") + exit() + + if not mnemonic_guess: + sys.exit("canceled") + + mnemonic_guess = str(mnemonic_guess) # ensures it's ASCII + + # Convert the mnemonic words into numeric ids and pre-calculate similar mnemonic words + global mnemonic_ids_guess, close_mnemonic_ids + mnemonic_ids_guess = () + # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (int), and each + # dict value is a tuple containing length 1 tuples, and finally each of the + # length 1 tuples contains a single mnemonic_id which is similar to the dict's key + close_mnemonic_ids = {} + for word in mnemonic_guess.lower().split(): + close_words = difflib.get_close_matches(word, cls._words, sys.maxsize, closematch_cutoff) + if close_words: + if close_words[0] != word: + print("'{}' was in your guess, but it's not a valid Electrum seed word;\n" + " trying '{}' instead.".format(word, close_words[0])) + mnemonic_ids_guess += cls._word_to_id[close_words[0]], + close_mnemonic_ids[mnemonic_ids_guess[-1]] = tuple( (cls._word_to_id[w],) for w in close_words[1:] ) + else: + if word != 'seed_token_placeholder': + print("'{}' was in your guess, but there is no similar Electrum seed word;\n" + " trying all possible seed words here instead.".format(word)) + mnemonic_ids_guess += None, + + guess_len = len(mnemonic_ids_guess) + if not expected_len: + if guess_len < 12: + expected_len = 12 + elif guess_len > 24: + expected_len = 24 + else: + off_by = guess_len % 3 + if off_by == 0: # If the supplied guess is a valid length, assume that all words have been supplied + expected_len = guess_len + else: # If less words have been supplied, round up to the nearest valid seed length (Assume words are missing by default) + expected_len = guess_len + 3 - off_by + + print("Assuming a", expected_len, "word mnemonic. (This can be overridden with --mnemonic-length)") + + global num_inserts, num_deletes + num_inserts = max(expected_len - len(mnemonic_ids_guess), 0) + num_deletes = max(len(mnemonic_ids_guess) - expected_len, 0) + if num_inserts: + print( + "Seed sentence was too short, inserting {} word{} into each guess.".format( + num_inserts, "s" if num_inserts > 1 else "" + ) + ) + if num_deletes: + print( + "Seed sentence was too long, deleting {} word{} from each guess.".format( + num_deletes, "s" if num_deletes > 1 else "" + ) + ) + if num_inserts: + print("Seed sentence was too short, inserting {} word{} into each guess." + .format(num_inserts, "s" if num_inserts > 1 else "")) + if num_deletes: + print("Seed sentence was too long, deleting {} word{} from each guess." + .format(num_deletes, "s" if num_deletes > 1 else "")) + + # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing) + @staticmethod + def performance_iterator(): + # See WalletBIP39.performance_iterator() for details + prefix = tuple(random.randrange(len(WalletElectrum1._words)) for i in range(8)) + for guess in itertools.product(range(len(WalletElectrum1._words)), repeat = 4): + yield prefix + guess + +### Blockchain.info password seed ### + +class BlockChainPassword(WalletBase): + _words = None + _v2words = None + + @property + def word_ids(self): return range(self._num_words) + + @classmethod + def id_to_word(self, id): return self._words[id] + + @classmethod + def _load_wordlist(self, wordlist): + self._words = tuple(map(str, load_wordlist(wordlist, "en"))) + self._num_words = len(self._words) + self._word_to_id = { word:id for id,word in enumerate(self._words) } + return self._words, self._word_to_id + + def __init__(self, loading = False): + super(BlockChainPassword, self).__init__(loading) + self._passwords_per_second = None + # v2 words are used by v3+ also + self._v2words = tuple(map(str, load_wordlist("blockchainpassword_words_v2", "en"))) + self._v2word_to_id = { word:id for id,word in enumerate(self._v2words) } + + def passwords_per_seconds(self, seconds): + if not self._passwords_per_second: + self._passwords_per_second = \ + calc_passwords_per_second(0.1, 0.1, 1) + return max(int(round(self._passwords_per_second * seconds)), 1) + + # Creates a wallet instance + @classmethod + def create_from_params(self, is_performance = False, force_p2tr = False, force_bip44 = False, force_bip84 = False, force_p2sh = False, disable_p2tr = False, disable_bip44 = False, disable_bip84 = False, disable_p2sh = False): + self = self(loading=True) + return self + + # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped + @staticmethod + def verify_mnemonic_syntax(mnemonic_ids): + return len(mnemonic_ids) == len(mnemonic_ids_guess) + num_inserts - num_deletes + + # Configures the values of four globals used later in config_btcrecover(): + # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes + def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65, expected_len = None): + # If a mnemonic guess wasn't provided, prompt the user for one + if not mnemonic_guess: + init_gui() + if tk_root: + mnemonic_guess = tk.simpledialog.askstring("Blockchain Legacy Wallet Recovery Mnemonic", + "Please enter your best guess for your Blockchain Legacy Wallet Recovery Mnemonic:") + else: + print("No mnemonic guess specified... Exiting...") + exit() + + if not mnemonic_guess: + sys.exit("canceled") + if not expected_len: + init_gui() + if tk_root: + expected_len = tk.simpledialog.askinteger("Blockchain Legacy Wallet Recovery Mnemonic number of words", + "Please enter your best guess for number of words in your BitcoinPassword seed " + "\n(Defaults to the number of words you entered on the previous step):", + minvalue=1, + initialvalue=len(mnemonic_guess.split(" "))) + else: + print("No number of words specified... Exiting...") + exit() + + if not expected_len: + sys.exit("canceled") + cls.expected_len = expected_len + cls._initial_words_valid = False + mnemonic_guess = str(mnemonic_guess) # ensures it's ASCII + + # Convert the mnemonic words into numeric ids and pre-calculate similar mnemonic words + global mnemonic_ids_guess, close_mnemonic_ids + mnemonic_ids_guess = () + # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (int), and each + # dict value is a tuple containing length 1 tuples, and finally each of the + # length 1 tuples contains a single mnemonic_id which is similar to the dict's key + close_mnemonic_ids = {} + for word in mnemonic_guess.lower().split(): + close_words = difflib.get_close_matches(word, cls._words, sys.maxsize, closematch_cutoff) + if close_words: + if close_words[0] != word: + print("'{}' was in your guess, but it's not a valid BlockchainPassword seed word;\n" + " trying '{}' instead.".format(word, close_words[0])) + mnemonic_ids_guess += cls._word_to_id[close_words[0]], + close_mnemonic_ids[mnemonic_ids_guess[-1]] = tuple( (cls._word_to_id[w],) for w in close_words[1:] ) + else: + if word != 'seed_token_placeholder': + print("'{}' was in your guess, but there is no similar BlockchainPassword seed word;\n" + " trying all possible seed words here instead.".format(word)) + mnemonic_ids_guess += None, + + global num_inserts, num_deletes + num_inserts = max(expected_len - len(mnemonic_ids_guess), 0) + num_deletes = max(len(mnemonic_ids_guess) - expected_len, 0) + if num_inserts: + print("Seed sentence was too short, inserting {} word{} into each guess." + .format(num_inserts, "s" if num_inserts > 1 else "")) + if num_deletes: + print("Seed sentence was too long, deleting {} word{} from each guess." + .format(num_deletes, "s" if num_deletes > 1 else "")) + + # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing) + def performance_iterator(self): + # See WalletBIP39.performance_iterator(self) for details + length = len(mnemonic_ids_guess) + num_inserts - num_deletes + for guess in itertools.product(self.word_ids, repeat=length): + yield guess + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def return_verified_password_or_false(self, mnemonic_ids_list): + # Copy some vars into local for a small speed boost + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + result = False + try: + result = self._verify_checksum(mnemonic_ids) + if result == False: continue + except ValueError: continue + except Exception as e: + print(e) + return mnemonic_ids, count + return False, count + + def _verify_checksum(self, words): + raise ValueError + + @staticmethod + def mn_mod(a, b): + return b + a if a < 0 else a % b + + @staticmethod + def words_to_bytes(words): + byte_array = [] + for word in words: + byte_array.extend(word.to_bytes(4, byteorder='big', signed=False)) + return byte_array + + @staticmethod + def bytes_to_words(byte_array): + words = [] + for i in range(0, len(byte_array), 4): + word = int.from_bytes(byte_array[i:i+4], byteorder='big', signed=False) + words.append(word) + return words + + @staticmethod + def bytes_to_string(byte_array): + return bytes(byte_array).decode('utf-8', errors='ignore') + + def decode_v2(self, word1, word2, word3): + if not word2 or not word3: + raise ValueError("seeds are not a multiple of 3") + n = len(self._v2words) + w1 = word1 + w2 = word2 % n + w3 = word3 % n + return w1 + n * self.mn_mod(w2 - w1, n) + n * n * self.mn_mod(w3 - w2, n) + + @staticmethod + def safe_get(lst, index): + try: + return lst[index] + except IndexError: + return None + + +@register_selectable_wallet_class("Blockchain.info Legacy Wallet Recovery Mnemonic v3") +class BlockChainPasswordV3(BlockChainPassword): + + def __init__(self, loading = False): + self._words, self._word_to_id = self._load_wordlist("blockchainpassword_words_v3") + self._num_words = len(self._words) + super(BlockChainPasswordV3, self).__init__(loading) + + def _verify_checksum(self, words): + if len(words) < 3: + raise ValueError('Mnemonic must have at least 2 words to do checksum') + + try: + # get the v2 ids of the first 3 words to calculate the checksum + seedwords = [self._words[seedid] for seedid in words[:3]] + try: + v2seedids = [self._v2word_to_id[seedword] for seedword in seedwords] + except KeyError: + raise ValueError + except Exception as e: + print(e) + checksum = self.decode_v2(v2seedids[0], v2seedids[1], v2seedids[2]) + + version = int_to_bytes(checksum, 4)[0] + if version not in (3, 4, 5, 6): return False + + obj = self.decode_v3456_word_list(words[3:], version, checksum) + print('\nPassword found: ' + obj['password'] + '\n') + if obj['guid']: + print('\nAccount ID found: ' + obj['guid'] + '\n') + + return True + except ValueError: + return False + except Exception as e: + print(e) + return False + + def decode_v3(self, word1, word2): + val1 = word1 + if word2 is None: + if val1 == -1: + raise ValueError('Unknown Word ' + self._words[word1]) + b1 = self.words_to_bytes([val1]) + return self.bytes_to_words([b1[2], b1[3], 0, 0]) + else: + val2 = word2 + if val1 == -1 or val2 == -1: + raise ValueError('Unknown Word ' + self._words[word1] + ' or ' + self._words[word2]) + b1 = self.words_to_bytes([val1]) + b2 = self.words_to_bytes([val2]) + try: + return self.bytes_to_words([b1[2], b1[3], b2[2], b2[3]]) + except: + raise ValueError + + def decode_v3456_word_list(self, wlist, version, checksum): + words = [self.decode_v3(wlist[i], self.safe_get(wlist, i + 1))[0] for i in range(0, len(wlist), 2)] + str_bytes = self.words_to_bytes(words) + str_bytes = bytearray([byte for byte in str_bytes if byte != 0]) + + restored_checksum_bytes = hashlib.sha256(str_bytes).digest()[:3] + version_byte = version.to_bytes(1, byteorder='big') + restored_checksum_bytes = version_byte + restored_checksum_bytes + restored_checksum = int.from_bytes(restored_checksum_bytes, byteorder='big') + + if restored_checksum < 0: + restored_checksum = -restored_checksum + if checksum != restored_checksum: + raise ValueError('Invalid Mnemonic Checksum. Please enter it carefully.') + + obj = {} + password_bytes = str_bytes + if version == 4: + guid_part = str_bytes[:16] + obj['guid'] = ( + ''.join('{:02x}'.format(b) for b in guid_part[:4]) + '-' + + ''.join('{:02x}'.format(b) for b in guid_part[4:6]) + '-' + + ''.join('{:02x}'.format(b) for b in guid_part[6:8]) + '-' + + ''.join('{:02x}'.format(b) for b in guid_part[8:10]) + '-' + + ''.join('{:02x}'.format(b) for b in guid_part[10:]) + ) + password_bytes = str_bytes[16:] + elif version == 5: + obj['time'] = bytes_to_int(str_bytes[:4], 4) + password_bytes = str_bytes[4:] + obj['password'] = self.bytes_to_string(password_bytes) + return obj + +@register_selectable_wallet_class("Blockchain.info Legacy Wallet Recovery Mnemonic v2") +class BlockChainPasswordV2(BlockChainPassword): + + def __init__(self, loading = False): + self._words, self._word_to_id = self._load_wordlist("blockchainpassword_words_v2") + self._v2words = self._words + self._v2word_to_id = self._word_to_id + super(BlockChainPasswordV2, self).__init__(loading) + + def config_mnemonic(self, mnemonic_guess = None, closematch_cutoff = 0.65, expected_len = None): + super(BlockChainPasswordV2, self).config_mnemonic(mnemonic_guess, closematch_cutoff, expected_len) + length = len(mnemonic_ids_guess) + num_inserts - num_deletes + if length % 3 != 0: + exit("BlockChain Password V2 seeds should be a length divisible by 3") + + def _verify_checksum(self, words): + if len(words) < 3: + raise ValueError('Mnemonic must have at least 3 words do checksum') + + try: + # Try decoding the first two words using version 3 logic + checksum = self.decode_v2(words[0], words[1], words[2]) + version = int_to_bytes(checksum, 1)[0] + if version != 2: return False + + obj = self.decode_v2_word_list(words, checksum) + print(obj.password) + return True + except ValueError: + return False + except Exception as e: + print(e) + return False + + def decode_v2_word_list(self, wlist, checksum): + try: + words = [self.decode_v2(wlist[i], self.safe_get(wlist, i + 1), self.safe_get(wlist, i + 2)) for i in range(0, len(wlist), 3)] + str_bytes = self.words_to_bytes(words) + str_bytes = bytearray([byte for byte in str_bytes if byte != 0]) + + restored_checksum = bytes_to_int(hashlib.sha256(str_bytes).digest()[:3]) + if restored_checksum < 0: + restored_checksum = -restored_checksum + if checksum != restored_checksum: + raise ValueError('Invalid Mnemonic Checksum. Please enter it carefully.') + else: + return {'password': self.bytes_to_string(str_bytes)} + except ValueError: + raise ValueError() + except Exception as e: + print(e) + return False + +############### BIP32 ############### + +class WalletBIP32(WalletBase): + + def __init__(self, arg_derivationpath = None, loading = False): + super(WalletBIP32, self).__init__(loading) + self._chaincode = None + self._passwords_per_second = None + + derivation_paths = [] + self._append_last_index = False + # Split the BIP32 key derivation path into its constituent indexes + if not arg_derivationpath: # Defaults to testing BIP44, BIP49 and BIP84 for Bitcoin + derivation_paths = ["m/44'/0'/0'/", "m/49'/0'/0'/", "m/84'/0'/0'/"] + # Append the internal/external (change) index to the path in create_from_params() + self._append_last_index = True + else: + for arg_path in arg_derivationpath: + derivation_paths.append(arg_path) + + self._path_indexes = [] + self._path_script_types = [] + self._path_strings = [] + self._script_type_labels = { + "p2pkh": "BIP44 (P2PKH)", + "p2wpkh": "BIP84 (P2WPKH)", + "p2sh": "BIP49 (P2SH wrapped Segwit)", + "p2tr": "BIP86 (P2TR Taproot)", + } + for path in derivation_paths: + path_indexes = path.split("/") + if path_indexes[0] == "m" or path_indexes[0] == "": + del path_indexes[0] # the optional leading "m/" + if path_indexes[-1] == "": + del path_indexes[-1] # the optional trailing "/" + current_path_indexes = [] + for path_index in path_indexes: + if path_index.endswith("'"): + current_path_indexes += int(path_index[:-1]) + 2**31, + else: + current_path_indexes += int(path_index), + + self._path_indexes.append(current_path_indexes) + purpose_index = current_path_indexes[0] if current_path_indexes else None + if purpose_index is not None and purpose_index >= 2 ** 31: + purpose_index -= 2 ** 31 + script_type_map = {0: "p2pkh", 44: "p2pkh", 49: "p2sh", 84: "p2wpkh", 86: "p2tr"} + self._path_script_types.append(script_type_map.get(purpose_index)) + self._path_strings.append(self._format_path(current_path_indexes)) + + def passwords_per_seconds(self, seconds): + if not self._passwords_per_second: + scalar_multiplies = 0 + for i in self._path_indexes[0]: # Just use the first derivation path for this... + if i < 2147483648: # if it's a normal child key + scalar_multiplies += 1 # then it requires a scalar multiply + if not self._chaincode: + scalar_multiplies += self._addrs_to_generate + 1 # each addr. to generate req. a scalar multiply + self._passwords_per_second = \ + calc_passwords_per_second(self._checksum_ratio, self._kdf_overhead, scalar_multiplies) + passwords_per_second = max(int(round(self._passwords_per_second * seconds)), 1) + # Divide the speed by however many passphrases we are testing for each seed (Otherwise the benchmarking step takes ages) + return passwords_per_second / len(self._derivation_salts) + + + @staticmethod + def _format_path(path_indexes): + if not path_indexes: + return "m" + path_parts = [] + for index in path_indexes: + if index >= 2 ** 31: + path_parts.append(f"{index - 2 ** 31}'") + else: + path_parts.append(str(index)) + return "m/" + "/".join(path_parts) + + def _script_type_label(self, script_type): + if not script_type: + return "" + return self._script_type_labels.get(script_type, script_type) + + def _classify_address_script_type(self, address): + try: + if isinstance(address, bytes): + address = address.decode() + address = address.strip() + except Exception: + return None + if not address: + return None + try: + base58_bytes = encoding.change_base(address, 58, 256, 25) + if len(base58_bytes) == 25: + version = base58_bytes[0] + if version in (0x00, 0x6f): # Bitcoin mainnet/testnet P2PKH + return "p2pkh" + if version in (0x05, 0xc4): # Bitcoin mainnet/testnet P2SH + return "p2sh" + except Exception: + pass + try: + decoded = encoding.addr_bech32_to_pubkeyhash(address, include_witver=True) + witness_tag = decoded[0] + data_length = decoded[1] + witness_version = 0 if witness_tag == 0 else witness_tag - 0x50 + if witness_version == 0 and data_length == 20: + return "p2wpkh" + if witness_version == 1 and data_length == 32: + return "p2tr" + except Exception: + pass + return None + + def _detect_address_types(self, addresses): + detected_types = set() + unknown_found = False + for address in addresses: + script_type = self._classify_address_script_type(address) + if script_type: + detected_types.add(script_type) + else: + unknown_found = True + if unknown_found or not detected_types: + return None + return detected_types + + def _detect_mpk_script_types(self, mpk): + if not mpk: + return None + if isinstance(mpk, bytes): + try: + mpk = mpk.decode() + except Exception: + return None + mpk = mpk.strip() + if not mpk: + return None + + prefix = mpk[:4] + prefix_map = { + "xpub": "p2pkh", + "xprv": "p2pkh", + "tpub": "p2pkh", + "tprv": "p2pkh", + "ypub": "p2sh", + "yprv": "p2sh", + "upub": "p2sh", + "uprv": "p2sh", + "zpub": "p2wpkh", + "zprv": "p2wpkh", + "vpub": "p2wpkh", + "vprv": "p2wpkh", + } + script_type = prefix_map.get(prefix) + if script_type: + return {script_type} + + version_bytes = None + for decoder in (getattr(base58, "b58decode_check", None), getattr(base58, "b58grsdecode_check", None)): + if not decoder: + continue + try: + decoded = decoder(mpk) + if len(decoded) >= 4: + version_bytes = decoded[:4] + break + except Exception: + continue + + if not version_bytes: + return None + + version_map = { + b"\x04\x88\xb2\x1e": "p2pkh", # xpub + b"\x04\x35\x87\xcf": "p2pkh", # tpub + b"\x04\x9d\x7c\xb2": "p2sh", # ypub + b"\x04\x4a\x52\x62": "p2sh", # upub + b"\x04\xb2\x47\x46": "p2wpkh", # zpub + b"\x04\x5f\x1c\xf6": "p2wpkh", # vpub + } + + script_type = version_map.get(version_bytes) + if script_type: + return {script_type} + return None + + def _apply_script_type_filters(self): + detected_types = getattr(self, "_auto_detected_script_types", None) + if getattr(self, "checksinglexpubaddress", False): + detected_types = None + detection_used = detected_types is not None + + forced_types = set() + if getattr(self, "force_bip44", False): + forced_types.add("p2pkh") + if getattr(self, "force_bip84", False): + forced_types.add("p2wpkh") + if getattr(self, "force_p2sh", False): + forced_types.update({"p2sh", "p2pkh", "p2wpkh"}) + if getattr(self, "force_p2tr", False): + forced_types.update({"p2tr", "p2pkh", "p2wpkh", "p2sh"}) + + disabled_types = set() + if getattr(self, "disable_bip44", False): + disabled_types.add("p2pkh") + if getattr(self, "disable_bip84", False): + disabled_types.add("p2wpkh") + if getattr(self, "disable_p2sh", False): + disabled_types.add("p2sh") + if getattr(self, "disable_p2tr", False): + disabled_types.add("p2tr") + + if detection_used: + allowed_types = set(detected_types) + else: + allowed_types = {"p2pkh", "p2wpkh", "p2sh", "p2tr"} + + allowed_types |= forced_types + allowed_types -= disabled_types + self._enabled_script_types = allowed_types + + filtered_indexes = [] + filtered_scripts = [] + filtered_strings = [] + skipped_paths = [] + + for idx, current_path in enumerate(self._path_indexes): + script_type = self._path_script_types[idx] if idx < len(self._path_script_types) else None + display_path = self._path_strings[idx] if idx < len(self._path_strings) else self._format_path(current_path) + skip_reason = None + + if script_type in disabled_types: + skip_reason = "disabled via command line" + elif detection_used and script_type and script_type not in allowed_types: + skip_reason = "it does not match the supplied address types" + + if skip_reason: + skipped_paths.append((display_path, script_type, skip_reason)) + continue + + filtered_indexes.append(current_path) + filtered_scripts.append(script_type) + filtered_strings.append(display_path) + + if not filtered_indexes: + raise ValueError("No derivation paths remain after applying address and script type filters. Use the force options to override.") + + self._path_indexes = filtered_indexes + self._path_script_types = filtered_scripts + self._path_strings = filtered_strings + + if "p2sh" not in self._enabled_script_types: + self.force_p2sh = False + if "p2tr" not in self._enabled_script_types: + self.force_p2tr = False + + if detection_used and detected_types: + detected_labels = sorted(filter(None, (self._script_type_label(t) for t in detected_types))) + if detected_labels: + print("Detected supplied address types:", ", ".join(detected_labels)) + + if skipped_paths: + for path_str, script_type, reason in skipped_paths: + label = self._script_type_label(script_type) + if label: + print(f"Skipping derivation path {path_str} ({label}) because {reason}.") + else: + print(f"Skipping derivation path {path_str} because {reason}.") + elif detection_used and detected_types: + print("All configured derivation paths match the supplied address types.") + + def _should_check_path_for_script(self, script_type): + if not script_type: + return True + return script_type in getattr(self, "_enabled_script_types", {"p2pkh", "p2wpkh", "p2sh", "p2tr"}) + + def _is_script_type_enabled(self, script_type): + if not script_type: + return True + enabled = getattr(self, "_enabled_script_types", {"p2pkh", "p2wpkh", "p2sh", "p2tr"}) + return script_type in enabled + + + + # Creates a wallet instance from either an mpk, an addresses container and address_limit, + # or a hash160s container. If none of these were supplied, prompts the user for each. + # (the BIP32 key derivation path is by default BIP44's account 0) + @classmethod + def create_from_params(cls, mpk = None, addresses = None, address_limit = None, hash160s = None, path = None, is_performance = False, address_start_index = None, force_p2sh = False, checksinglexpubaddress = False, force_p2tr = False, force_bip44 = False, force_bip84 = False, disable_p2sh = False, disable_p2tr = False, disable_bip44 = False, disable_bip84 = False): + self = cls(path, loading=True) + + auto_detected_types = None + + # Process the mpk (master public key) argument + if mpk: + mpk_script_types = self._detect_mpk_script_types(mpk) + if mpk_script_types: + auto_detected_types = mpk_script_types + mpk = convert_to_xpub(mpk) + if not mpk.startswith("xpub"): + raise ValueError("the BIP32 extended public key must begin with 'xpub, ypub or zpub'" + " " + mpk) + mpk = base58check_to_bip32(mpk) + # (it's processed more later) + + # Process the hash160s argument + if hash160s: + if mpk: + print("warning: addressdb is ignored when an mpk is provided", file=sys.stderr) + hash160s = None + else: + self._known_hash160s = hash160s + + # Process the addresses argument + if addresses: + if mpk or hash160s: + print("warning: addresses are ignored when an mpk or addressdb is provided", file=sys.stderr) + addresses = None + else: + detected_types = self._detect_address_types(addresses) + auto_detected_types = detected_types + self._known_hash160s = self._addresses_to_hash160s(addresses) + + self._auto_detected_script_types = auto_detected_types + + # Process the address_limit argument + if address_limit: + if mpk: + print("warning: address limit is ignored when an mpk is provided", file=sys.stderr) + address_limit = None + else: + address_limit = int(address_limit) + if address_limit <= 0: + raise ValueError("the address limit must be > 0") + # (it's assigned to self._addrs_to_generate later) + + if address_start_index: + self._address_start_index = address_start_index + if address_limit < 0: + raise ValueError("the address limit must zero or positive") + else: + self._address_start_index = 0 + + # Set other wallet parameters + self.force_p2sh = force_p2sh + self.checksinglexpubaddress = checksinglexpubaddress + self.force_p2tr = force_p2tr + self.force_bip44 = force_bip44 + self.force_bip84 = force_bip84 + self.disable_p2sh = disable_p2sh + self.disable_p2tr = disable_p2tr + self.disable_bip44 = disable_bip44 + self.disable_bip84 = disable_bip84 + + self._apply_script_type_filters() + + # If mpk, addresses, and hash160s arguments were all not provided, prompt the user for an mpk first + if not mpk and not addresses and not hash160s: + init_gui() + while True: + if tk_root: # Skip if TK is not available... + mpk = tk.simpledialog.askstring("Master extended public key", + "Please enter your account extended public key (xpub, ypub or zpub) if you " + "have it, or click Cancel to search by an address instead:", + initialvalue=self._performance_xpub() if is_performance else None) + else: + print("Error: No MPK or addresses specified... Exiting...") + exit() + + if not mpk: + break # if they pressed Cancel, stop prompting for an mpk + mpk = mpk.strip() + try: + mpk = convert_to_xpub(mpk) + if not mpk.startswith("xpub"): + raise ValueError("not a BIP32 extended public key (doesn't start with 'xpub', 'ypub' or 'zpub')") + mpk = base58check_to_bip32(mpk) + break + except ValueError as e: + tk.messagebox.showerror("Master extended public key", "The entered key is invalid ({})".format(e)) + + # If an mpk has been provided (in the function call or from a user), extract the + # required chaincode and adjust the path to match the mpk's depth and child number + if mpk: + if mpk.depth == 0: + print("xpub depth: 0") + assert mpk.child_number == 0, "child_number == 0 when depth == 0" + else: + if mpk.child_number < 2**31: + child_num = mpk.child_number + else: + child_num = str(mpk.child_number - 2**31) + "'" + print("xpub depth: {}\n" + "xpub parent fingerprint: {}\n" + "xpub child #: {}" + .format(mpk.depth, base64.b16encode(mpk.fingerprint), child_num)) + self._chaincode = mpk.chaincode + for i in range(0, len(self._path_indexes)): + if mpk.depth <= len(self._path_indexes[i]): # if this, ensure the path + self._path_indexes[i] = self._path_indexes[i][:mpk.depth] # length matches the depth + if self._path_indexes[i] and self._path_indexes[i][-1] != mpk.child_number: + if len(self._path_indexes) > 1: #If multiple derivation paths have been specified + #Just throw a warning + print("WARNING: Derivaton path: " + str(i+1) + " does not match the xpub you have provided.") + else: + raise ValueError("the extended public key's child # doesn't match " + "the corresponding index of this wallet's path") + elif mpk.depth == 1 + len(self._path_indexes[i]) and self._append_last_index: + self._path_indexes[i] += mpk.child_number, + else: + if len(self._path_indexes) > 1: # If multiple derivation paths have been specified + # Just throw a warning + print("WARNING: Derivaton path: " + str(i+1) + " does not match the xpub you have provided.") + else: + raise ValueError( + "the extended public key's depth exceeds the length of this wallet's path ({})" + .format(len(self._path_indexes[i]))) + + else: # else if not mpk + + # If we don't have an mpk but need to append the last + # index, assume it's the external (non-change) chain + if self._append_last_index: + for current_path_indexes in self._path_indexes: + current_path_indexes += 0, + + # If an mpk Testing Mnemonic:'t provided (at all), and addresses and hash160s arguments also + # weren't provided (in the original function call), prompt the user for addresses. + if not addresses and not hash160s: + # init_gui() was already called above + self._known_hash160s = None + while True: + addresses = tk.simpledialog.askstring("Addresses", + "Please enter at least one address from the first account in your wallet, " + "preferably some created early in the account's lifetime:", + initialvalue="1Hp6UXuJjzt9eSBa9LhtW97KPb44bq4CAQ" if is_performance else None) + if not addresses: break + addresses.replace(",", " ") + addresses.replace(";", " ") + addresses = addresses.split() + if not addresses: break + try: + # (raises ValueError or TypeError on failure): + self._known_hash160s = self._addresses_to_hash160s(addresses) + break + except (ValueError, TypeError) as e: + tk.messagebox.showerror("Addresses", "An entered address is invalid ({})".format(e)) + + # If there are still no hash160s available (and no mpk), check for an address database before giving up + if not self._known_hash160s: + if os.path.isfile(ADDRESSDB_DEF_FILENAME): + print("Using address database file '"+ADDRESSDB_DEF_FILENAME+"' the in current directory.") + else: + print("notice: address database file '"+ADDRESSDB_DEF_FILENAME+"' does not exist in current directory", file=sys.stderr) + sys.exit("canceled") + + # There are some wallets where address generation limit doesn't apply at all... + if type(self) in [btcrecover.btcrseed.WalletPolkadotSubstrate]: + address_limit = 1 + + if not address_limit: + init_gui() # might not have been called yet + before_the = "one(s) you just entered" if addresses else "first one in actual use" + + suggested_addr_limit = 10 + # There are some wallets where account Generation limit isn't generally relevant, so suggest to set it as 1 + if type(self) in [btcrecover.btcrseed.WalletTron, btcrecover.btcrseed.WalletEthereum, btcrecover.btcrseed.WalletSolana, + btcrecover.btcrseed.WalletCosmos, btcrecover.btcrseed.WalletStellar]: + suggested_addr_limit = 1 + + if tk_root: # Skip if TK is not available... + address_limit = tk.simpledialog.askinteger("Address limit", + "Please enter the address generation limit. Smaller will\n" + "be faster, but it must be equal to at least the number\n" + "of addresses created before the "+before_the+":\n" + "(If unsure, the number below is a sensible default...)", minvalue=1, initialvalue=suggested_addr_limit) + else: + print("No address generation limit specified... Exiting...") + exit() + + if not address_limit: + sys.exit("canceled") + self._addrs_to_generate = address_limit + + if not self._known_hash160s: + print("Loading address database ...") + self._known_hash160s = AddressSet.fromfile(open(ADDRESSDB_DEF_FILENAME, "rb")) + print("Loaded", len(self._known_hash160s), "addresses from database ...") + + return self + + #def convert_to_xpub(self, mpk): + # convert_to_xpub(mpk) + + # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped + @staticmethod + def verify_mnemonic_syntax(mnemonic_ids): + # Length must be divisible by 3 and all ids must be present + return len(mnemonic_ids) % 3 == 0 and None not in mnemonic_ids + + def return_verified_password_or_false(self, mnemonic_ids_list): + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if not isinstance(self.opencl_algo,int) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def _return_verified_password_or_false_cpu(self, mnemonic_ids_list): + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + + if self.pre_start_benchmark or (not self._checksum_in_generator and not self._skip_worker_checksum): + # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test (Only required at the benchmark step, this is handled in the password generator now) + if not self._verify_checksum(mnemonic_ids): + continue + + # If we are writing out the checksummed seeds, add them to the queue + if self._savevalidseeds and not self.pre_start_benchmark: + self.worker_out_queue.put(mnemonic_ids) + continue + + # Convert the mnemonic sentence to seed bytes (according to BIP39 or Electrum2) + _derive_seed_list = self._derive_seed(mnemonic_ids) + + for derived_seed, salt in _derive_seed_list: + if type(self) is not WalletXLM: + seed_bytes = hmac.new("Bitcoin seed".encode('utf-8'), derived_seed, hashlib.sha512).digest() + else: + seed_bytes = derived_seed + + if self._verify_seed(seed_bytes, salt): + return mnemonic_ids, count # found it + + return False, count + + def _return_verified_password_or_false_opencl(self, mnemonic_ids_list): + cleaned_mnemonic_ids_list = [] + + for mnemonic in mnemonic_ids_list: + if not self._checksum_in_generator and not self._skip_worker_checksum: + if self._verify_checksum(mnemonic): + if (type(self) is WalletElectrum2): + cleaned_mnemonic_ids_list.append(self._space.join(mnemonic).encode()) + else: + cleaned_mnemonic_ids_list.append(" ".join(mnemonic).encode()) + + else: + if type(self) is WalletElectrum2: + cleaned_mnemonic_ids_list.append(self._space.join(mnemonic).encode()) + else: + cleaned_mnemonic_ids_list.append(" ".join(mnemonic).encode()) + + for i, salt in enumerate(self._derivation_salts,0): + if type(self) is WalletElectrum2: + salt = b"electrum" + salt + else: + salt = b"mnemonic" + salt + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512[i], cleaned_mnemonic_ids_list, + salt, 2048, 64) + + results = zip(cleaned_mnemonic_ids_list,clResult) + + for cleaned_mnemonic, derived_seed in results: + if type(self) is not WalletXLM: + seed_bytes = hmac.new("Bitcoin seed".encode('utf-8'), derived_seed, hashlib.sha512).digest() + else: + seed_bytes = derived_seed + + if self._verify_seed(seed_bytes, salt): + if isinstance(mnemonic_ids_list[0], list): + found_mnemonic = cleaned_mnemonic.decode().split(" ") + else: + found_mnemonic = tuple(cleaned_mnemonic.decode().split(" ")) + return found_mnemonic, mnemonic_ids_list.index(found_mnemonic) + 1 # found it + + return False, len(mnemonic_ids_list) + + def _verify_seed(self, arg_seed_bytes, salt = None): + if salt is None: + salt = self._derivation_salts[0] + # Derive the chain of private keys for the specified path as per BIP32 + + if self.checksinglexpubaddress: #Atomic (Eth), MyBitcoinWallet, PT.BTC Wallet Single Address (Does things in a very non-standard way) + seed_bytes = arg_seed_bytes + privkey_bytes = seed_bytes[:32] # These wallets basically use the xprv a single private key... + pubkey = coincurve.PublicKey.from_valid_secret(privkey_bytes).format(compressed = False) + pubkey_hash160 = self.pubkey_to_hash160(pubkey) + if pubkey_hash160 in self._known_hash160s: + privkey_wif = base58.b58encode_check(bytes([0x80]) + privkey_bytes + bytes([0x1])) + print("Match found on Non-Standard Single Address, Privkey (Bitcoin Base58): ", privkey_wif) + print("Match found on Non-Standard Single Address, Privkey (Generic Hex): ", privkey_bytes.hex()) + return True + + for path_idx, current_path_index in enumerate(self._path_indexes): + path_script_type = self._path_script_types[path_idx] if path_idx < len(self._path_script_types) else None + if not self._should_check_path_for_script(path_script_type): + continue + seed_bytes = arg_seed_bytes + privkey_bytes = seed_bytes[:32] + chaincode_bytes = seed_bytes[32:] + + for i in current_path_index: + if i < 2147483648: # if it's a normal child key, derive the compressed public key + try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format() + except ValueError: break + else: # else it's a hardened child key + data_to_hmac = b"\0" + privkey_bytes # prepended "\0" as per BIP32 + data_to_hmac += struct.pack(">I", i) # append the index (big-endian) as per BIP32 + + seed_bytes = hmac.new(chaincode_bytes, data_to_hmac, hashlib.sha512).digest() + + # The child private key is the parent one + the first half of the seed_bytes (mod n) + privkey_bytes = int_to_bytes((bytes_to_int(seed_bytes[:32]) + + bytes_to_int(privkey_bytes)) % GENERATOR_ORDER, 32) + chaincode_bytes = seed_bytes[32:] + + # If an extended public key was provided, check the derived chain code against it + if self._chaincode: + if chaincode_bytes == self._chaincode: + return True # found it + + else: + # (note: the rest assumes the address index isn't hardened) + + # Derive the final public keys, searching for a match with known_hash160s + # (these first steps below are loop invariants) + try: data_to_hmac = coincurve.PublicKey.from_valid_secret(privkey_bytes).format() + except ValueError: break + privkey_int = bytes_to_int(privkey_bytes) + + for i in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + seed_bytes = hmac.new(chaincode_bytes, + data_to_hmac + struct.pack(">I", i), hashlib.sha512).digest() + + # The final derived private key is the parent one + the first half of the seed_bytes + d_privkey_bytes = int_to_bytes((bytes_to_int(seed_bytes[:32]) + + privkey_int) % GENERATOR_ORDER, 32) + + script_candidates = [] + try_p2tr = self._is_script_type_enabled("p2tr") and ( + (current_path_index and (current_path_index[0] - 2 ** 31) == 86) or getattr(self, "force_p2tr", False)) + try_p2pkh = self._is_script_type_enabled("p2pkh") and ( + path_script_type in (None, "p2pkh") or getattr(self, "force_bip44", False)) + try_p2wpkh = self._is_script_type_enabled("p2wpkh") and ( + path_script_type in (None, "p2wpkh") or getattr(self, "force_bip84", False)) + try_p2sh = self._is_script_type_enabled("p2sh") and ( + (current_path_index and (current_path_index[0] - 2 ** 31) == 49) or getattr(self, "force_p2sh", False)) + + if try_p2tr or try_p2pkh or try_p2wpkh or try_p2sh: + base_pubkey = coincurve.PublicKey.from_valid_secret(d_privkey_bytes) + else: + base_pubkey = None + + if try_p2tr and base_pubkey is not None: + tweaked = P2TR_tools._P2TRUtils.TweakPublicKey(base_pubkey) + if len(tweaked) != 32: + return False + script_candidates.append(("p2tr", tweaked)) + + if base_pubkey is not None and (try_p2pkh or try_p2wpkh or try_p2sh): + d_pubkey = base_pubkey.format(compressed=False) + pubkey_hash160 = self.pubkey_to_hash160(d_pubkey) + + if try_p2pkh: + script_candidates.append(("p2pkh", pubkey_hash160)) + if try_p2wpkh: + script_candidates.append(("p2wpkh", pubkey_hash160)) + if try_p2sh: + witness_program = b"\x00\x14" + pubkey_hash160 + script_candidates.append(("p2sh", ripemd160(hashlib.sha256(witness_program).digest()))) + + seen_hashes = set() + for candidate_type, test_hash160 in script_candidates: + if test_hash160 in seen_hashes: + continue + seen_hashes.add(test_hash160) + + if test_hash160 in self._known_hash160s: #Check if this hash160 is in our list of known hash160s + global seedfoundpath + seedfoundpath = "m/" + for index in current_path_index: + if index >= 2147483648: + index -= 2 ** 31 + seedfoundpath += str(index) + "'" + else: + seedfoundpath += str(index) + + seedfoundpath += "/" + + seedfoundpath += str(i) + + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ***MATCHING SEED FOUND***, Matched on Address at derivation path:", seedfoundpath) + #print("Found match with Hash160: ", binascii.hexlify(test_hash160)) + + if(len(self._derivation_salts) > 1): + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ***MATCHING SEED FOUND***, Matched with BIP39 Passphrase:", salt.decode()) + + return True + return False + + # Returns a dummy xpub for performance testing purposes + @staticmethod + def _performance_xpub(): + # an xpub at path m/44'/0'/0', as Mycelium for Android would export + return "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx" + + +############### BIP39 ############### + +@register_selectable_wallet_class("Bitcoin Standard BIP39/BIP44") +class WalletBIP39(WalletBIP32): + FIRSTFOUR_TAG = "-firstfour" + _checksum_in_generator = False + + # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() ) + _language_words = {} + @classmethod + def _load_wordlists(cls): + assert not cls._language_words, "_load_wordlists() should only be called once from the first init()" + cls._do_load_wordlists("bip39") + for wordlist_lang in list(cls._language_words): # takes a copy of the keys so the dict can be safely changed + wordlist = cls._language_words[wordlist_lang] + assert len(wordlist) == 2048, "BIP39 wordlist has 2048 words" + # Special case for the four languages whose words may be truncated to the first four letters + if wordlist_lang in ("en", "es", "fr", "it", "pt", "cs"): + cls._language_words[wordlist_lang + cls.FIRSTFOUR_TAG] = [ w[:4] for w in wordlist ] + # + @classmethod + def _do_load_wordlists(cls, name, wordlist_langs = None): + if not wordlist_langs: + wordlist_langs = [] + for filename in glob.iglob(os.path.join(wordlists_dir, name + "-??*.txt")): + wordlist_langs.append(os.path.basename(filename)[len(name)+1:-4]) # e.g. "en", or "zh-hant" + for lang in wordlist_langs: + if lang in cls._language_words: + continue + cls._language_words[lang] = load_wordlist(name, lang) + + @property + def word_ids(self): return self._words + @staticmethod + def id_to_word(id): return id # returns a UTF-8 encoded bytestring + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/BTC.txt") + super(WalletBIP39, self).__init__(path, loading) + if not self._language_words: + self._load_wordlists() + pbkdf2_library_name = btcrpass.load_pbkdf2_library().__name__ # btcrpass's pbkdf2 library is used in _derive_seed() + self._kdf_overhead = 0.0026 if pbkdf2_library_name == "hashlib" else 0.013 + + def __setstate__(self, state): + # (Re)load the pbkdf2 library if necessary + btcrpass.load_pbkdf2_library() + self.__dict__ = state + + # Converts a mnemonic word from a Python unicode (as produced by load_wordlist()) + # into a bytestring (of type str) in the format required by BIP39 + @staticmethod + def _unicode_to_bytes(word): + assert isinstance(word, str) + return sys.intern(unicodedata.normalize("NFKD", word)) + + # Configures the values of four globals used later in config_btcrecover(): + # mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes; + # also selects the appropriate wordlist language to use + def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrases = [u"",], expected_len = None, closematch_cutoff = 0.65): + if expected_len: + if expected_len < 12: + raise ValueError("minimum BIP39 sentence length is 12 words") + if expected_len > 24: + raise ValueError("maximum BIP39 sentence length is 24 words") + if expected_len % 3 != 0: + raise ValueError("BIP39 sentence length must be evenly divisible by 3") + + # Do most of the work in this function: + passphrases = self._config_mnemonic(mnemonic_guess, lang, passphrases, expected_len, closematch_cutoff) + + self._derivation_salts = [] + + # The pbkdf2-derived salt, based on the passphrase, as per BIP39 (needed by _derive_seed()); + # first ensure that this version of Python supports the characters present in the passphrase + for passphrase in passphrases: + if sys.maxunicode < 65536: # if this Python is a "narrow" Unicode build + for c in passphrase: + c = ord(c) + if 0xD800 <= c <= 0xDBFF or 0xDC00 <= c <= 0xDFFF: + raise ValueError("this version of Python doesn't support passphrases with Unicode code points > "+str(sys.maxunicode)) + + _derivation_salt = self._unicode_to_bytes(passphrase) + + self._derivation_salts.append(_derivation_salt.encode()) + + # Special case for wallets which tell users to record only the first four letters of each word; + # convert all short words into long ones (intentionally done *after* the finding of close words). + # Specifically, update self._words and the globals mnemonic_ids_guess and close_mnemonic_ids. + if self._lang.endswith(self.FIRSTFOUR_TAG): + long_lang_words = self._language_words[self._lang[:-len(self.FIRSTFOUR_TAG)]] + assert isinstance(long_lang_words[0], str), "long words haven't yet been converted into bytes" + assert isinstance(self._words[0], str), "short words have already been converted into bytes" + assert len(long_lang_words) == len(self._words), "long and short word lists have the same length" + long_lang_words = [ self._unicode_to_bytes(l) for l in long_lang_words ] + short_to_long = { s:l for s,l in zip(self._words, long_lang_words) } + self._words = long_lang_words + # + global mnemonic_ids_guess # the to-be-replaced short-words guess + long_ids_guess = () # the new long-words guess + for short_id in mnemonic_ids_guess: + long_ids_guess += None if short_id is None else short_to_long[short_id], + mnemonic_ids_guess = long_ids_guess + # + global close_mnemonic_ids + close_mnemonic_ids_forKeys = copy.deepcopy(close_mnemonic_ids) # Make a copy of the dictionary so that we can edit the keys safely + if close_mnemonic_ids: + assert isinstance(iter(close_mnemonic_ids).__next__(), str), "close word keys have already been converted into bytes" + assert isinstance(iter(close_mnemonic_ids.values()).__next__()[0][0], str), "close word values have already been converted into bytes" + for key in close_mnemonic_ids_forKeys.keys(): + vals = close_mnemonic_ids.pop(key) + # vals is a tuple containing length-1 tuples which in turn each contain one word in bytes-format + expanded_vals = [] + for v in vals: + expanded_vals.append((short_to_long[v[0]],)) + + close_mnemonic_ids.update({short_to_long[key] : tuple(expanded_vals)}) + + # Calculate each word's index in binary (needed by _verify_checksum()) + self._word_to_binary = { word : "{:011b}".format(i) for i,word in enumerate(self._words) } + + # Chances a checksum is valid, e.g. 1/16 for 12 words, 1/256 for 24 words + self._checksum_ratio = 2.0**( -( len(mnemonic_ids_guess) + num_inserts - num_deletes )//3 ) + # + def _config_mnemonic(self, mnemonic_guess, lang, passphrases, expected_len, closematch_cutoff): + + # If a mnemonic guess wasn't provided, prompt the user for one + if not mnemonic_guess: + init_gui() + if tk_root: # Skip if TK is not available... + mnemonic_guess = tk.simpledialog.askstring("Seed", + "Please enter your best guess for your seed (mnemonic):") + else: + print("No mnemonic guess specified... Exiting...") + exit() + + if not mnemonic_guess: + sys.exit("canceled") + + # Note: this is not in BIP39's preferred encoded form yet, instead it's + # in the same format as load_wordlist creates (NFC normalized Unicode) + mnemonic_guess = unicodedata.normalize("NFC", str(mnemonic_guess).lower()).split() + if len(mnemonic_guess) == 1: # assume it's a logographic script (no spaces, e.g. Chinese) + mnemonic_guess = tuple(mnemonic_guess) + + # Select the appropriate wordlist language to use + if not lang: + #print("Debug Loaded Languages") + #for lang, one_languages_words in self._language_words.items(): + # print(lang) + language_word_hits = {} # maps a language id to the # of words found in that language + for word in mnemonic_guess: + for lang, one_languages_words in self._language_words.items(): + if word in one_languages_words: + language_word_hits.setdefault(lang, 0) + language_word_hits[lang] += 1 + if len(language_word_hits) == 0: + raise ValueError("can't guess wordlist language: 0 valid words") + if len(language_word_hits) == 1: + best_guess = language_word_hits.popitem() + else: + sorted_hits = language_word_hits.items() + #sorted_hits.sort(key=lambda x: x[1]) # sort based on hit count + sorted_hits = sorted(sorted_hits, key=lambda x: x[1]) + best_guess = sorted_hits[-1] + second_guess = sorted_hits[-2] + # at least 20% must be exclusive to the best_guess language + if best_guess[1] - second_guess[1] < 0.2 * len(mnemonic_guess): + if (best_guess[1] == second_guess[1] and + best_guess[0][:2] == second_guess[0][:2] and + "-firstfour" in best_guess[0]+second_guess[0]): + pass + else: + raise ValueError("can't guess wordlist language: top best guesses ({}, {}) are too close ({}, {})" + .format(best_guess[0], second_guess[0], best_guess[1], second_guess[1])) + # at least half must be valid words + if best_guess[1] < 0.5 * len(mnemonic_guess): + raise ValueError("can't guess wordlist language: best guess ({}) has only {} valid word(s)" + .format(best_guess[0], best_guess[1])) + lang = best_guess[0] + # + try: + words = self._language_words[lang] + self.current_wordlist = words + except KeyError: # consistently raise ValueError for any bad inputs + raise ValueError("can't find wordlist for language code '{}'".format(lang)) + self._lang = lang + + print("Using the '{}' wordlist.".format(lang)) + + # Build the mnemonic_ids_guess and pre-calculate similar mnemonic words + global mnemonic_ids_guess, close_mnemonic_ids + self._initial_words_valid = True # start off by assuming they're all valid + mnemonic_ids_guess = () # the best initial guess (w/no invalid words) + # + # close_mnemonic_ids is a dict; each dict key is a mnemonic_id (a string), and + # each dict value is a tuple containing length 1 tuples, and finally each of the + # length 1 tuples contains a single mnemonic_id which is similar to the dict's key; + # e.g.: { "a-word" : ( ("a-ward", ), ("a-work",) ), "other-word" : ... } + close_mnemonic_ids = {} + for word in mnemonic_guess: + close_words = difflib.get_close_matches(word, words, sys.maxsize, closematch_cutoff) + if close_words: + if close_words[0] != word: + print(u"'{}' was in your guess, but it's not a valid seed word;\n" + u" trying '{}' instead.".format(word, close_words[0])) + self._initial_words_valid = False + mnemonic_ids_guess += self._unicode_to_bytes(close_words[0]), # *now* convert to BIP39's format + close_mnemonic_ids[mnemonic_ids_guess[-1]] = \ + tuple( (self._unicode_to_bytes(w),) for w in close_words[1:] ) + else: + if __name__ == b"__main__": + print(u"'{}' was in your guess, but there is no similar seed word;\n" + u" trying all possible seed words here instead.".format(word)) + else: + if word != 'seed_token_placeholder': + print(u"'{}' was in your seed, but there is no similar seed word.".format(word)) + self._initial_words_valid = False + mnemonic_ids_guess += None, + + guess_len = len(mnemonic_ids_guess) + if not expected_len: # (this is always explicitly specified for Electrum 2 seeds) + assert not isinstance(self, WalletElectrum2), "WalletBIP39._config_mnemonic: expected_len is specified for WalletElectrum2 objects" + if guess_len < 12: + expected_len = 12 + elif guess_len > 24: + expected_len = 24 + else: + off_by = guess_len % 3 + if off_by == 0: # If the supplied guess is a valid length, assume that all words have been supplied + expected_len = guess_len + else: # If less words have been supplied, round up to the nearest valid seed length (Assume words are missing by default) + expected_len = guess_len + 3 - off_by + + print("Assuming a", expected_len, "word mnemonic. (This can be overridden with --mnemonic-length)") + if expected_len not in (12,24): + print("WARNING: Assuming an uncommon mnemonic length... (Normally 12 or 24) Double check your wallet documentation to see what mnemonic lengths it supports...") + + global num_inserts, num_deletes + num_inserts = max(expected_len - guess_len, 0) + num_deletes = max(guess_len - expected_len, 0) + if num_inserts and not isinstance(self, WalletElectrum2): + print("Seed sentence was too short, inserting {} word{} into each guess." + .format(num_inserts, "s" if num_inserts > 1 else "")) + if num_deletes: + print("Seed sentence was too long, deleting {} word{} from each guess." + .format(num_deletes, "s" if num_deletes > 1 else "")) + + # Now that we're done with the words in Unicode format, + # convert them to BIP39's encoding and save for future reference + self._words = tuple(map(self._unicode_to_bytes, words)) + + if passphrases is True: + init_gui() + while True: + if tk_root: # Skip if TK is not available... + entered_passphrase = tk.simpledialog.askstring("Passphrase", + "Please enter the passphrase you added when the seed was first created:", show="*") + else: + print("No passphrase specified... Exiting...") + exit() + + if not entered_passphrase: + sys.exit("canceled") + if entered_passphrase == tk.simpledialog.askstring("Passphrase", "Please re-enter the passphrase:", show="*"): + passphrases = [entered_passphrase, ] + break + tk.messagebox.showerror("Passphrase", "The passphrases did not match, try again.") + + return passphrases + + # Called by WalletBIP32.return_verified_password_or_false() to verify a BIP39 checksum + def _verify_checksum(self, mnemonic_words): + # Convert from the mnemonic_words (ids) back to the entropy bytes + checksum + try: + bit_string = "".join(self._word_to_binary[w] for w in mnemonic_words) + except: + # only get here if there was something wrong with the nemonic words + print ("invalid nemonic sentence: ") + print (mnemonic_words) + return False + cksum_len_in_bits = len(mnemonic_words) // 3 # as per BIP39 + entropy_bytes = bytearray() + for i in range(0, len(bit_string) - cksum_len_in_bits, 8): + entropy_bytes.append(int(bit_string[i:i+8], 2)) + cksum_int = int(bit_string[-cksum_len_in_bits:], 2) + # + # Calculate and verify the checksum + return ord(hashlib.sha256(entropy_bytes).digest()[:1]) >> 8-cksum_len_in_bits \ + == cksum_int + + # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed + def _derive_seed(self, mnemonic_words): + # Note: the words are already in BIP39's normalized form + seedList = [] + for salt in self._derivation_salts: + seedList.append(btcrpass.pbkdf2_hmac("sha512", " ".join(mnemonic_words).encode('utf-8'), b"mnemonic" + salt, 2048)) + + return zip(seedList,self._derivation_salts) + + # Produces a long stream of differing and incorrect mnemonic_ids guesses (for testing) + # (uses mnemonic_ids_guess, num_inserts, and num_deletes globals as set by config_mnemonic()) + def performance_iterator(self): + # This used to just itereate through the entire space, starting at "abandon abandon abandon ..." + # and "counting" up from there. However "abandon abandon ... abandon about" is actually in use + # in the blockchain (buggy software or people just messing around), and in address-database + # mode this creates an unwanted positive hit, so now we have to start with a random prefix. + length = len(mnemonic_ids_guess) + num_inserts - num_deletes + assert length >= 12 + prefix = tuple(random.choice(self._words) for i in range(length-4)) + for guess in itertools.product(self._words, repeat=4): + yield prefix + guess + + + def init_opencl_kernel(self): + # keep btcrseed checks happy + pass + + +@register_selectable_wallet_class("LND aezeed (CipherSeed)") +class WalletAezeed(WalletBIP39): + def __init__(self, path=None, loading=False): + super(WalletAezeed, self).__init__(path, loading) + self._word_to_index = { + word: idx for idx, word in enumerate(self._language_words["en"]) + } + self._passphrases = [] + self._last_cipherseed = None # type: Optional[aezeed.DecipheredCipherSeed] + self._checksum_only_mode = False + + @classmethod + def create_from_params( + cls, + mpk=None, + addresses=None, + address_limit=None, + hash160s=None, + path=None, + is_performance=False, + address_start_index=None, + force_p2sh=False, + checksinglexpubaddress=False, + force_p2tr=False, + force_bip44=False, + force_bip84=False, + disable_p2sh=False, + disable_p2tr=False, + disable_bip44=False, + disable_bip84=False, + ): + if mpk or addresses or hash160s: + wallet = super(WalletAezeed, cls).create_from_params( + mpk=mpk, + addresses=addresses, + address_limit=address_limit, + hash160s=hash160s, + path=path, + is_performance=is_performance, + address_start_index=address_start_index, + force_p2sh=force_p2sh, + checksinglexpubaddress=checksinglexpubaddress, + force_p2tr=force_p2tr, + force_bip44=force_bip44, + force_bip84=force_bip84, + disable_p2sh=disable_p2sh, + disable_p2tr=disable_p2tr, + disable_bip44=disable_bip44, + disable_bip84=disable_bip84, + ) + wallet._checksum_only_mode = False + return wallet + + wallet = cls(path, loading=True) + + if wallet._append_last_index: + for current_path_indexes in wallet._path_indexes: + current_path_indexes += 0, + + wallet._addrs_to_generate = 0 + wallet._address_start_index = 0 + wallet._known_hash160s = set() + + wallet.force_p2sh = force_p2sh + wallet.checksinglexpubaddress = checksinglexpubaddress + wallet.force_p2tr = force_p2tr + wallet.force_bip44 = force_bip44 + wallet.force_bip84 = force_bip84 + wallet.disable_p2sh = disable_p2sh + wallet.disable_p2tr = disable_p2tr + wallet.disable_bip44 = disable_bip44 + wallet.disable_bip84 = disable_bip84 + wallet._auto_detected_script_types = None + wallet._apply_script_type_filters() + + if address_limit: + print( + "warning: address limit is ignored when no addresses are supplied; " + "running in checksum-only mode", + file=sys.stderr, + ) + + if address_start_index not in (None, 0): + print( + "warning: address start index is ignored when running in checksum-only mode", + file=sys.stderr, + ) + + wallet._checksum_only_mode = True + print( + "WARNING: No addresses or xpub supplied for aezeed recovery. " + "Only checksum validation will be performed; manually verify any " + "recovered seed before use.", + ) + return wallet + + def config_mnemonic( + self, + mnemonic_guess=None, + lang=None, + passphrases=[u""], + expected_len=None, + closematch_cutoff=0.65, + ): + if lang is None: + lang = "en" + elif lang != "en": + raise ValueError("aezeed mnemonics are only defined for the English wordlist") + if expected_len is None: + expected_len = 24 + if expected_len != 24: + raise ValueError("aezeed mnemonics must be exactly 24 words long") + + selected_passphrases = super(WalletAezeed, self).config_mnemonic( + mnemonic_guess=mnemonic_guess, + lang=lang, + passphrases=passphrases, + expected_len=expected_len, + closematch_cutoff=closematch_cutoff, + ) + if selected_passphrases is None: + selected_passphrases = passphrases + self._passphrases = [str(p) for p in selected_passphrases] + self._derivation_salts = [ + (p.encode("utf-8") if p else aezeed.DEFAULT_PASSPHRASE.encode("utf-8")) + for p in self._passphrases + ] + self._checksum_ratio = 1.0 / (2 ** 32) + return selected_passphrases + + def _verify_checksum(self, mnemonic_words): + return aezeed.validate_mnemonic(mnemonic_words, self._word_to_index) + + def _derive_seed(self, mnemonic_words): + seeds = [] + self._last_cipherseed = None + for passphrase in self._passphrases: + try: + cipherseed = aezeed.decode_mnemonic( + mnemonic_words, passphrase, self._word_to_index + ) + except aezeed.InvalidPassphraseError: + continue + self._last_cipherseed = cipherseed + salt_bytes = ( + passphrase.encode("utf-8") + if passphrase + else aezeed.DEFAULT_PASSPHRASE.encode("utf-8") + ) + seeds.append((cipherseed.entropy, salt_bytes)) + return seeds + + def _verify_seed(self, arg_seed_bytes, salt=None): + if self._checksum_only_mode: + return self._last_cipherseed is not None + return super(WalletAezeed, self)._verify_seed(arg_seed_bytes, salt) + + +############### bitcoinj ############### + +@register_selectable_wallet_class("Bitcoinj compatible") +class WalletBitcoinj(WalletBIP39): + + def __init__(self, path = None, loading = False): + # Just calls WalletBIP39.__init__() with a hardcoded path + if path: raise ValueError("can't specify a BIP32 path with Bitcoinj wallets") + super(WalletBitcoinj, self).__init__(["m/0'/0/"], loading) + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + if wallet_file.read(1) == b"\x0a": # protobuf field number 1 of type length-delimited + network_identifier_len = ord(wallet_file.read(1)) + if 1 <= network_identifier_len < 128: + wallet_file.seek(2 + network_identifier_len) + if wallet_file.read(1) in b"\x12\x1a": # field number 2 or 3 of type length-delimited + return True + return False + + # Load a bitcoinj wallet file (the part of it we need, just the chaincode) + @classmethod + def load_from_filename(cls, wallet_filename): + from . import wallet_pb2 + pb_wallet = wallet_pb2.Wallet() + with open(wallet_filename, "rb") as wallet_file: + pb_wallet.ParseFromString(wallet_file.read(btcrpass.MAX_WALLET_FILE_SIZE)) # up to 64M, typical size is a few k + if pb_wallet.encryption_type == wallet_pb2.Wallet.UNENCRYPTED: + raise ValueError("this bitcoinj wallet is not encrypted") + + # Search for the (one and only) master public extended key (whose path length is 0) + self = None + for key in pb_wallet.key: + if key.HasField("deterministic_key") and len(key.deterministic_key.path) == 0: + assert not self, "only one master public extended key is in the wallet file" + assert len(key.deterministic_key.chain_code) == 32, "chaincode length is 32 bytes" + self = cls(loading=True) + self._chaincode = key.deterministic_key.chain_code + # Because it's the *master* xpub, it has an empty path + self._path_indexes = () + + if not self: + raise ValueError("No master public extended key was found in this bitcoinj wallet file") + return self + + # Returns a dummy xpub for performance testing purposes + @staticmethod + def _performance_xpub(): + # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export + return "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4QLCpPu" + + +############### Electrum2 ############### +# Electron-Cash forked after Electrum 2.7, so this is the option to use with Electron-Cash (And likely other Electrum forks) + +@register_selectable_wallet_class('Electrum 2+ or Electron-Cash ("standard" wallets initially created with Electrum 2 or later)') +class WalletElectrum2(WalletBIP39): + + # From Electrum 2.x's mnemonic.py (coalesced) + CJK_INTERVALS = ( + ( 0x1100, 0x11ff), + ( 0x2e80, 0x2fdf), + ( 0x2ff0, 0x2fff), + ( 0x3040, 0x31ff), + ( 0x3400, 0x4dbf), + ( 0x4e00, 0xa4ff), + ( 0xa960, 0xa97f), + ( 0xac00, 0xd7ff), + ( 0xf900, 0xfaff), + ( 0xff00, 0xffef), + (0x16f00, 0x16f9f), + (0x1b000, 0x1b0ff), + (0x20000, 0x2a6df), + (0x2a700, 0x2b81f), + (0x2f800, 0x2fa1d), + (0xe0100, 0xe01ef)) + + # Load the wordlists for all languages (actual one to use is selected in config_mnemonic() ) + @classmethod + def _load_wordlists(cls): + assert not cls._language_words, "_load_wordlists() should only be called once from the first init()" + # Load all standard BIP39 wordlists first so Electrum2-specific + # lists cannot overwrite them if they share the same language code + cls._do_load_wordlists("bip39") + cls._do_load_wordlists("electrum2") + for lang in list(cls._language_words): + words = cls._language_words[lang] + if len(words) == 2048 and lang in ("en", "es", "fr", "it", "pt", "cs"): + cls._language_words[lang + cls.FIRSTFOUR_TAG] = [w[:4] for w in words] + assert all(len(w) >= 1411 for w in cls._language_words.values()), \ + "Electrum2 wordlists are at least 1411 words long" # because we assume a max mnemonic length of 13 + + def __init__(self, path = None, loading = False): + # Just calls WalletBIP39.__init__() with default Electrum path if none specified + try: + if not path: + path = load_pathlist("./derivationpath-lists/Electrum.txt") + + #Throw a warning if someone is attempting to use a BIP39 derivation path with an Electrum wallet + elif path[2] == '4': + print("") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ") + print("WARNINIG: Electrum wallets don't use standard BIP39 derivation Paths..") + print(" You probably want to use m/0'/0 for a Segwit wallet, m/0 Legacy...") + print(" (Or just run without --bip32-path to check both types") + print("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ") + print("") + except IndexError: + pass #Handle the error if only valid paths have been passed + + super(WalletElectrum2, self).__init__(path, loading) + self._checksum_ratio = 2.0 / 256.0 # 2 in 256 checksums are valid on average + self._needs_passphrase = None + + for idx, path_str in enumerate(getattr(self, "_path_strings", ())): + if path_str == "m/0'/0" and idx < len(self._path_script_types): + self._path_script_types[idx] = None + + @staticmethod + def is_wallet_file(wallet_file): + wallet_file.seek(0) + data = wallet_file.read(8) + if data[0] == ord('{'): + return None # "maybe yes" + try: + data = base64.b64decode(data) + except TypeError: return False # "definitely no" + if data.startswith(b"BIE1"): + sys.exit("error: Electrum 2.8+ fully-encrypted wallet files cannot be read,\n" + "try to recover from your master extended public key or an address instead") + return False # "definitely no" + + # Load an Electrum2 wallet file (the part of it we need, just the master public key) + @classmethod + def load_from_filename(cls, wallet_filename): + import json + + with open(wallet_filename) as wallet_file: + wallet = json.load(wallet_file) + wallet_type = wallet.get("wallet_type") + if not wallet_type: + raise ValueError("Unrecognized wallet format (Electrum2 wallet_type not found)") + if wallet_type == "old": # if it's been converted from 1.x to 2.y (y<7), return a WalletElectrum1 object + return WalletElectrum1._load_from_dict(wallet) + if not wallet.get("use_encryption"): + raise ValueError("Electrum2 wallet is not encrypted") + seed_version = wallet.get("seed_version", "(not found)") + if wallet.get("seed_version") not in (11, 12, 13): # all 2.x versions as of April 2022 + raise NotImplementedError("Unsupported Electrum2 seed version " + str(seed_version)) + if wallet_type != "standard": + raise NotImplementedError("Unsupported Electrum2 wallet type: " + wallet_type) + + mpk = needs_passphrase = None + while True: # "loops" exactly once; only here so we've something to break out of + + # Electrum 2.7+ standard wallets have a keystore + keystore = wallet.get("keystore") + if keystore: + keystore_type = keystore.get("type", "(not found)") + + # Wallets originally created by an Electrum 2.x version + if keystore_type == "bip32": + mpk = keystore["xpub"] + if keystore.get("passphrase"): + needs_passphrase = True + break + + # Former Electrum 1.x wallet after conversion to Electrum 2.7+ standard-wallet format + elif keystore_type == "old": + # Construct and return a WalletElectrum1 object + mpk = base64.b16decode(keystore["mpk"], casefold=True) + if len(mpk) != 64: + raise ValueError("Electrum1 master public key is not 64 bytes long") + self = WalletElectrum1(loading=True) + self._master_pubkey = "\x04".encode() + mpk # prepend the uncompressed tag + return self + + else: + print("warning: found unsupported keystore type " + keystore_type, file=sys.stderr) + + # Electrum 2.0 - 2.6.4 wallet (of any wallet type) + mpks = wallet.get("master_public_keys") + if mpks: + mpk = list(mpks.values())[0] + break + + raise RuntimeError("No master public keys found in Electrum2 wallet") + + assert mpk + wallet = cls.create_from_params(mpk) + wallet._needs_passphrase = needs_passphrase + return wallet + + # Converts a mnemonic word from a Python unicode (as produced by load_wordlist()) + # into a bytestring (of type str) via the same method as Electrum 2.x + @staticmethod + def _unicode_to_bytes(word): + assert isinstance(word, str) + word = unicodedata.normalize("NFKD", word) + word = filter(lambda c: not unicodedata.combining(c), word) # Electrum 2.x removes combining marks + return sys.intern("".join(word)) + + def config_mnemonic(self, mnemonic_guess = None, lang = None, passphrases = [u"",], expected_len = None, closematch_cutoff = 0.65): + if expected_len is None: + expected_len_specified = False + if self._needs_passphrase or getattr(self, "_passphrase_recovery", False): + expected_len = 12 + print("notice: presence of a mnemonic passphrase implies a 12-word long Electrum 2.7+ mnemonic", + file=sys.stderr) + else: + init_gui() + if tk_root: # Skip if TK is not available... + if tk.messagebox.askyesno("Electrum 2.x version", + "Did you CREATE your wallet with Electrum version 2.7 (released Oct 2 2016) or later? (Or using a fork like Electron-Cash)" + "\n\nPlease choose No if you're unsure.", + default=tk.messagebox.NO): + expected_len = 12 + else: + expected_len = 13 + else: + print("No You need to specify expected mnemonic length with this versonof electrum2 wallet.. Exiting...") + exit() + + print("Assuming a", expected_len, "word mnemonic. (This can be overridden with --mnemonic-length)") + + else: + expected_len_specified = True + if expected_len > 13: + print("WARNING: Maximum mnemonic length for standard Electrum2 wallets is 13 words, you specified", expected_len) + + if self._needs_passphrase and not passphrase: + passphrase = True # tells self._config_mnemonic() to prompt for a passphrase below + init_gui() + if tk_root: # Skip if TK is not available... + tk.messagebox.showwarning("Passphrase", + 'This Electrum seed was extended with "custom words" (a seed passphrase) when it ' + "was first created. You will need to enter it to continue.\n\nNote that this seed " + "passphrase is NOT the same as the wallet password that's entered to spend funds.") + else: + print("No passphrase specified... Exiting...") + exit() + + # Calls WalletBIP39's generic version (note the leading _) with the mnemonic + # length (which for Electrum2 wallets alone is treated only as a maximum length) + passphrases = self._config_mnemonic(mnemonic_guess, lang, passphrases, expected_len, closematch_cutoff) + + # Python 2.x running Electrum 2.x has a Unicode bug where if there are any code points > 65535, + # they might be normalized differently between different Python 2 builds (narrow vs. wide Unicode) + self._derivation_salts = [] + for passphrase in passphrases: + assert isinstance(passphrase, str) + if sys.maxunicode < 65536: # the check for narrow Unicode builds looks for UTF-16 surrogate pairs: + maybe_buggy = any(0xD800 <= ord(c) <= 0xDBFF or 0xDC00 <= ord(c) <= 0xDFFF for c in passphrase) + else: # the check for wide Unicode builds: + maybe_buggy = any(ord(c) > 65535 for c in passphrase) + if maybe_buggy: + print("warning: due to Unicode incompatibilities, it's strongly recommended\n" + " that you run seedrecover.py on the same computer (or at least\n" + " the same OS) where you created your wallet", file=sys.stderr) + + if expected_len_specified and num_inserts: + print("notice: for Electrum 2.x, --mnemonic-length is the max length tried, but not necessarily the min", + file=sys.stderr) + + # The pbkdf2-derived salt (needed by _derive_seed()); Electrum 2.x is similar to BIP39, + # however it differs in the iffy(?) normalization procedure and the prepended string + import string + passphrase = unicodedata.normalize("NFKD", passphrase) # problematic w/Python narrow Unicode builds, same as Electrum + passphrase = passphrase.lower() # (?) + passphrase = filter(lambda c: not unicodedata.combining(c), passphrase) # remove combining marks + passphrase = "".join(passphrase) + passphrase = " ".join(passphrase.split()) # replace whitespace sequences with a single ASCII space + # remove ASCII whitespace between CJK characters (?) + passphrase = "".join(c for i,c in enumerate(passphrase) if not ( + c in string.whitespace + and any(intvl[0] <= ord(passphrase[i-1]) <= intvl[1] for intvl in self.CJK_INTERVALS) + and any(intvl[0] <= ord(passphrase[i+1]) <= intvl[1] for intvl in self.CJK_INTERVALS))) + + _derivation_salt = passphrase + + self._derivation_salts.append(_derivation_salt.encode()) + + # Electrum 2.x doesn't separate mnemonic words with spaces in sentences for any CJK + # scripts when calculating the checksum or deriving a binary seed (even though this + # seem inappropriate for some CJK scripts such as Hiragana as used by the ja wordlist) + self._space = "" if self._lang in ("ja", "zh-hans", "zh-hant") else " " + + # Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped + @staticmethod + def verify_mnemonic_syntax(mnemonic_ids): + # a valid electrum mnemonic is at most 13 words long (and all ids must be present) + # Some wallets (Cakewallet) also create 24 word seeds that use electrum checksum & derivation. + return len(mnemonic_ids) in (list(range(1,14)) + [24]) and None not in mnemonic_ids + + # Called by WalletBIP32.return_verified_password_or_false() to verify an Electrum2 checksum + def _verify_checksum(self, mnemonic_words): + testDigest = hmac.new("Seed version".encode(), self._space.join(mnemonic_words).encode(), hashlib.sha512) \ + .digest()[0] + return testDigest in [1,16] + + # Called by WalletBIP32.return_verified_password_or_false() to create a binary seed + def _derive_seed(self, mnemonic_words): + # Note: the words are already in Electrum2's normalized form + seedList = [] + for salt in self._derivation_salts: + seedList.append(btcrpass.pbkdf2_hmac("sha512", self._space.join(mnemonic_words).encode(), b"electrum" + salt, 2048)) + + return zip(seedList,self._derivation_salts) + + # Returns a dummy xpub for performance testing purposes + @staticmethod + def _performance_xpub(): + # an xpub at path m, as Electrum would export + return "xpub661MyMwAqRbcGsUXkGBkytQkYZ6M16bFWwTocQDdPSm6eJ1wUsxG5qty1kTCUq7EztwMscUstHVo1XCJMxWyLn4PP1asLjt4gPt3HkA81qe" + + +############### Ethereum ############### + +@register_selectable_wallet_class('Ethereum Standard BIP39/BIP44 (Or Most EVM Wallets)') +class WalletEthereum(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/ETH.txt") + super(WalletEthereum, self).__init__(path, loading) + + + def __setstate__(self, state): + import lib.eth_hash.auto + super(WalletEthereum, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletEthereum, cls).create_from_params(*args, **kwargs) + return self + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + for address in addresses: + if address[:2].lower() == "0x": + address = address[2:] + if len(address) != 40: + raise ValueError("length (excluding any '0x' prefix) of Ethereum addresses must be 40") + cur_hash160 = base64.b16decode(address, casefold=True) + if not address.islower(): # verify the EIP55 checksum unless all letters are lowercase + checksum = keccak(base64.b16encode(cur_hash160).lower()) + for nibble, c in enumerate(address, 0): + if c.isalpha() and \ + c.isupper() != bool(checksum[nibble // 2] & (0b1000 if nibble&1 else 0b10000000)): + raise ValueError("invalid EIP55 checksum") + hash160s.add( (cur_hash160) ) + return hash160s + + @staticmethod + def pubkey_to_hash160(uncompressed_pubkey): + """convert from an uncompressed public key to its Ethereum hash160 form + + :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString + :type uncompressed_pubkey: str + :return: last 20 bytes of keccak256(raw_64_byte_pubkey) + :rtype: str + """ + assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == 4 + return keccak(uncompressed_pubkey[1:])[-20:] + +############### Hedera (Ed25519) ############### + +@register_selectable_wallet_class('Hedera BIP39/44 (ed25519)') +class WalletHederaEd25519(WalletBIP39): + + _HEDERA_LEDGER_ID = b"\x00" + _ACCOUNT_RE = re.compile(r"^(\d+)\.(\d+)\.([0-9a-fA-F]+)(?:-([a-z]{5}))?$") + + def __init__(self, path=None, loading=False): + if not bip_utils_available: + exit("Hedera Ed25519 wallet support requires the bip_utils package. Install it via 'pip3 install bip_utils'.") + if not path: + path = load_pathlist("./derivationpath-lists/HEDERA.txt") + super(WalletHederaEd25519, self).__init__(path, loading) + self._hedera_shard = 0 + self._hedera_realm = 0 + + @staticmethod + def _alias_bytes_to_account_string(alias_bytes: bytes) -> str: + shard = int.from_bytes(alias_bytes[:4], "big") + realm = int.from_bytes(alias_bytes[4:12], "big") + num = int.from_bytes(alias_bytes[12:], "big") + return f"{shard}.{realm}.{num}" + + @staticmethod + def _parse_account_string(account: str) -> Tuple[int, int, Union[int, bytes], Optional[str], bool]: + match = WalletHederaEd25519._ACCOUNT_RE.fullmatch(account) + if not match: + raise ValueError("Hedera account IDs must be in 'shard.realm.num' format") + + shard = int(match.group(1)) + realm = int(match.group(2)) + num_or_hex = match.group(3) + checksum = match.group(4) + + if shard < 0 or realm < 0: + raise ValueError("Hedera account components must be non-negative") + + if re.fullmatch(r"\d+", num_or_hex): + num = int(num_or_hex) + if num < 0: + raise ValueError("Hedera account components must be non-negative") + return shard, realm, num, checksum.lower() if checksum else None, False + + if len(num_or_hex) % 2: + raise ValueError("hex-encoded Hedera identifiers must have an even length") + + try: + alias_bytes = bytes.fromhex(num_or_hex) + except ValueError as exc: + raise ValueError("invalid hex characters in Hedera identifier") from exc + + return shard, realm, alias_bytes, checksum.lower() if checksum else None, True + + @staticmethod + def _account_string_to_alias_bytes(account: str) -> bytes: + shard, realm, payload, _checksum, is_alias = WalletHederaEd25519._parse_account_string(account) + if is_alias: + raise ValueError("hex alias accounts cannot be converted to solidity addresses") + return ( + shard.to_bytes(4, "big") + + realm.to_bytes(8, "big") + + int(payload).to_bytes(8, "big") + ) + + @staticmethod + def _hedera_checksum(shard: int, realm: int, num: int) -> str: + addr = f"{shard}.{realm}.{num}" + digits = [] + s0 = 0 + s1 = 0 + s = 0 + sh = 0 + p3 = 26 ** 3 + p5 = 26 ** 5 + ascii_a = ord("a") + m = 1000003 + w = 31 + + ledger_bytes = bytearray(WalletHederaEd25519._HEDERA_LEDGER_ID) + ledger_bytes.extend(b"\x00" * 6) + + for ch in addr: + digits.append(10 if ch == "." else int(ch, 10)) + + for idx, value in enumerate(digits): + s = (w * s + value) % p3 + if idx % 2 == 0: + s0 = (s0 + value) % 11 + else: + s1 = (s1 + value) % 11 + + for value in ledger_bytes: + sh = (w * sh + value) % p5 + + c = ((((len(addr) % 5) * 11 + s0) * 11 + s1) * p3 + s + sh) % p5 + c = (c * m) % p5 + + answer = [] + for _ in range(5): + answer.append(chr(ascii_a + (c % 26))) + c //= 26 + + return "".join(reversed(answer)) + + def _addresses_to_hash160s(self, addresses): + hash160s = set() + inferred_index = None + detected_shard = None + detected_realm = None + deterministic_addr_hexes = set() + deterministic_account_strings = set() + key_bound_target_present = False + for address in addresses: + if isinstance(address, bytes): + address = address.decode() + cleaned = address.strip() + if not cleaned: + continue + + lowered = cleaned.lower() + + if lowered.startswith("0x"): + hex_part = lowered[2:] + if not re.fullmatch(r"[0-9a-f]+", hex_part): + raise ValueError("invalid hex characters in Hedera identifier") + if len(hex_part) == 40: + hash160s.add(("addr", hex_part)) + key_bound_target_present = True + elif len(hex_part) == 64: + hash160s.add(("priv", hex_part)) + hash160s.add(("pub", hex_part)) + key_bound_target_present = True + else: + raise ValueError("hex-encoded Hedera identifiers must be 20 or 32 bytes long") + continue + + if "." in cleaned: + shard, realm, payload, checksum, is_alias = WalletHederaEd25519._parse_account_string(cleaned) + + if detected_shard is None: + detected_shard = shard + detected_realm = realm + elif shard != detected_shard or realm != detected_realm: + detected_shard = None + detected_realm = None + + if is_alias: + alias_hex = payload.hex() + alias_string = f"{shard}.{realm}.{alias_hex}" + hash160s.add(("alias_der", alias_hex)) + hash160s.add(("acc", alias_string)) + if checksum: + hash160s.add(("acc", f"{alias_string}-{checksum}")) + key_bound_target_present = True + continue + + num = int(payload) + if num < 0: + raise ValueError("Hedera account components must be non-negative") + + alias_bytes = ( + shard.to_bytes(4, "big") + + realm.to_bytes(8, "big") + + num.to_bytes(8, "big") + ) + base_account = f"{shard}.{realm}.{num}" + hash160s.add(("addr", alias_bytes.hex())) + hash160s.add(("acc", base_account)) + checksum = checksum or WalletHederaEd25519._hedera_checksum(shard, realm, num) + hash160s.add(("acc", f"{base_account}-{checksum}")) + deterministic_addr_hexes.add(alias_bytes.hex()) + deterministic_account_strings.add(base_account) + deterministic_account_strings.add(f"{base_account}-{checksum}") + if inferred_index is None or num < inferred_index: + inferred_index = num + continue + + if not re.fullmatch(r"[0-9a-f]+", lowered): + raise ValueError("unsupported Hedera identifier format") + + if len(lowered) == 40: + hash160s.add(("addr", lowered)) + key_bound_target_present = True + elif len(lowered) == 64: + hash160s.add(("priv", lowered)) + hash160s.add(("pub", lowered)) + key_bound_target_present = True + else: + raise ValueError("hex-encoded Hedera identifiers must be 20 or 32 bytes long") + + if detected_shard is not None and detected_realm is not None: + self._hedera_shard = detected_shard + self._hedera_realm = detected_realm + self._inferred_address_start_index = inferred_index + self._hedera_deterministic_addr_hexes = deterministic_addr_hexes + self._hedera_deterministic_account_strings = deterministic_account_strings + self._hedera_require_key_bound_match = not key_bound_target_present + return hash160s + + @classmethod + def create_from_params(cls, *args, **kwargs): + provided_start_index = kwargs.get("address_start_index") + self = super(WalletHederaEd25519, cls).create_from_params(*args, **kwargs) + if provided_start_index is None: + inferred_index = getattr(self, "_inferred_address_start_index", None) + if inferred_index is not None: + self._address_start_index = inferred_index + return self + + def return_verified_password_or_false(self, mnemonic_ids_list): + return self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + def _return_verified_password_or_false_cpu(self, mnemonic_ids_list): + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + + if self.pre_start_benchmark or (not self._checksum_in_generator and not self._skip_worker_checksum): + if not self._verify_checksum(mnemonic_ids): + continue + + if self._savevalidseeds and not self.pre_start_benchmark: + self.worker_out_queue.put(mnemonic_ids) + continue + + mnemonic_phrase = " ".join(mnemonic_ids) + + for salt in self._derivation_salts: + salt_str = salt.decode() if isinstance(salt, bytes) else salt + seed_bytes = Bip39SeedGenerator(mnemonic_phrase).Generate(salt_str) + + if self._verify_seed(seed_bytes, salt): + return mnemonic_ids, count + + return False, count + + def _verify_seed(self, seed_bytes, salt=None): + deterministic_addr_hexes = getattr(self, "_hedera_deterministic_addr_hexes", set()) + deterministic_account_strings = getattr(self, "_hedera_deterministic_account_strings", set()) + require_key_bound = getattr(self, "_hedera_require_key_bound_match", False) + + for path_str in getattr(self, "_path_strings", ["m"]): + base_ctx = Bip32Slip10Ed25519.FromSeedAndPath(seed_bytes, path_str) + + for account_index in range(self._address_start_index, + self._address_start_index + self._addrs_to_generate): + relative_index = account_index - self._address_start_index + if relative_index < 0: + continue + child_ctx = base_ctx.ChildKey(0x80000000 + relative_index) + + priv_hex = child_ctx.PrivateKey().Raw().ToHex().lower() + if ("priv", priv_hex) in self._known_hash160s: + return True + + pub_bytes = child_ctx.PublicKey().RawCompressed().ToBytes() + pub_key_bytes = pub_bytes[1:] if len(pub_bytes) == 33 and pub_bytes[0] == 0 else pub_bytes + pub_hex = pub_key_bytes.hex().lower() + if ("pub", pub_hex) in self._known_hash160s: + return True + + der_prefix = bytes.fromhex("302a300506032b6570032100") + alias_der = der_prefix + pub_key_bytes + alias_der_hex = alias_der.hex() + if ("alias_der", alias_der_hex) in self._known_hash160s: + return True + + proto_key = bytes.fromhex("1220") + pub_key_bytes + alias_bytes = keccak(proto_key)[-20:] + alias_hex = alias_bytes.hex() + alias_match = False + if ("addr", alias_hex) in self._known_hash160s: + if alias_hex not in deterministic_addr_hexes: + return True + alias_match = True + + shard = getattr(self, "_hedera_shard", 0) + realm = getattr(self, "_hedera_realm", 0) + solidity_bytes = ( + shard.to_bytes(4, "big") + + realm.to_bytes(8, "big") + + account_index.to_bytes(8, "big") + ) + solidity_hex = solidity_bytes.hex() + solidity_match = ("addr", solidity_hex) in self._known_hash160s + + base_account = f"{shard}.{realm}.{account_index}" + base_account_match = ("acc", base_account) in self._known_hash160s + + checksum = WalletHederaEd25519._hedera_checksum(shard, realm, account_index) + base_account_checksum = f"{base_account}-{checksum}" + checksum_match = ("acc", base_account_checksum) in self._known_hash160s + + alias_account_string = f"{shard}.{realm}.{alias_der_hex}" + if ("acc", alias_account_string) in self._known_hash160s: + return True + + weak_match_found = False + if alias_match: + weak_match_found = True + if solidity_match and solidity_hex in deterministic_addr_hexes: + weak_match_found = True + if base_account_match and base_account in deterministic_account_strings: + weak_match_found = True + if checksum_match and base_account_checksum in deterministic_account_strings: + weak_match_found = True + + if weak_match_found and not require_key_bound: + return True + + return False + +############### Ethereum Validator ############### + +@register_selectable_wallet_class('Ethereum Validator BIP39') +class WalletEthereumValidator(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not eth2_staking_deposit_available: + exit("Ethereum Validator Seed Recovery requires the staking-deposit and py_ecc modules, please see the installation documentation at http://btcrecover.readthedocs.io/INSTALL/#staking-deposit for further information") + super(WalletEthereumValidator, self).__init__(None, loading) + + # Default method for adding addresses, doesn't worry about validating the addresses + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + + #With Py_Crypto_HD_Wallet type wallets we don't worry about converting to hash160 + # (Minor performancce hit, but not an issue) + for address in addresses: + if address[:2].lower() == "0x": + address = address[2:] + if len(address) == 96: + hash160s.add(address) + else: + raise ValueError("length (excluding any '0x' prefix) of Ethereum Validator pubkey must be 96") + + return hash160s + + def passwords_per_seconds(self, seconds): + # Eth2 validator derivation is *very* slow when compared to BIP39, the below figure is a realistic minimum for an i5 laptop from 2021 + return 40 / self._checksum_ratio / self._addrs_to_generate / len(self._derivation_salts) + + + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + + # Set path as EIP-2334 format + # https://eips.ethereum.org/EIPS/eip-2334 + purpose = '12381' + coin_type = '3600' + account = account_index + withdrawal_key_path = f'm/{purpose}/{coin_type}/{account}/0' + signing_key_path = f'{withdrawal_key_path}/0' + + signing_sk = mnemonic_and_path_to_key(mnemonic=" ".join(mnemonic), path=signing_key_path, + password=salt.decode()) + + signing_pk = bls.SkToPk(signing_sk) + + if signing_pk.hex() in self._known_hash160s: + return True + + return False + + def return_verified_password_or_false(self, mnemonic_list): + + for count, mnemonic in enumerate(mnemonic_list, 1): + + if self.pre_start_benchmark or (not self._checksum_in_generator and not self._skip_worker_checksum): + # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test (Only required at the benchmark step, this is handled in the password generator now) + if not self._verify_checksum(mnemonic): + continue + + if self._verify_seed(mnemonic): + return mnemonic, count # found it + + return False, count + +############### Zilliqa ############### + +@register_selectable_wallet_class('Zilliqa Standard BIP39/44 (***Ledger Nano CURRENTLY UNSUPPORTED***)') +class WalletZilliqa(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/ZIL.txt") + super(WalletZilliqa, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletZilliqa, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletZilliqa, cls).create_from_params(*args, **kwargs) + return self + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + for address in addresses: + cur_hash160= base64.b16decode(zilliqa_account(address=address).address, casefold=True) + + hash160s.add(cur_hash160) + return hash160s + + @staticmethod + def pubkey_to_hash160(uncompressed_pubkey): + """convert from an uncompressed public key to its Ethereum hash160 form + + :param uncompressed_pubkey: SEC 1 EllipticCurvePoint OctetString + :type uncompressed_pubkey: str + :return: last 20 bytes of sha256(raw_64_byte_pubkey) + :rtype: str + """ + assert len(uncompressed_pubkey) == 65 and uncompressed_pubkey[0] == 4 + #print(compress_pubkey(uncompressed_pubkey)) + #print(binascii.hexlify(compress_pubkey(uncompressed_pubkey))) + hash160 = hashlib.sha256(compress_pubkey(uncompressed_pubkey)).digest()[-20:] + + return hash160 + +############### Cardano ############### + +@register_selectable_wallet_class('Cardano Shelly-Era BIP39/44') +class WalletCardano(WalletBIP39): + + def __init__(self, path = None, loading = False): + super(WalletCardano, self).__init__(None, loading) + if not path: path = load_pathlist("./derivationpath-lists/ADA.txt") + self._path_list = path + + self._check_icarus = False + self._check_ledger = False + self._check_trezor = False + + for current_path in self._path_list: + root_node_derivation_type, current_path = current_path.split(":") + if root_node_derivation_type == "icarus": self._check_icarus = True + if root_node_derivation_type == "ledger": self._check_ledger = True + if root_node_derivation_type == "trezor": self._check_trezor = True + if root_node_derivation_type == "byron": + print("Byron derivation not currently supported") + exit() + + def __setstate__(self, state): + super(WalletCardano, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + kwargs["address_limit"] = 1 #Address limit not relevant in Cardano-Shelly + self = super(WalletCardano, cls).create_from_params(*args, **kwargs) + return self + + def passwords_per_seconds(self, seconds): + if not self._passwords_per_second: + scalar_multiplies = 0 + for i in self._path_indexes[0]: # Just use the first derivation path for this... + if i < 2147483648: # if it's a normal child key + scalar_multiplies += 1 # then it requires a scalar multiply + if not self._chaincode: + scalar_multiplies += self._addrs_to_generate + 1 # each addr. to generate req. a scalar multiply + self._passwords_per_second = \ + calc_passwords_per_second(self._checksum_ratio, self._kdf_overhead, scalar_multiplies) + passwords_per_second = max(int(round(self._passwords_per_second * seconds)), 1) + # Divide the speed by however many passphrases we are testing for each seed (Otherwise the benchmarking step takes ages) + return passwords_per_second / len(self._derivation_salts) / 5 + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + + for address in addresses: + address_data = bech32.bech32_decode(address) + + if address_data[0] not in ("addr","stake"): + raise ValueError("Error: Invalid Cardano-Shelly Address: ", address) + address_hexlist = bech32.convertbits(address_data[1], 5, 8, False) + addr_hash = ''.join([f'{c:02x}' for c in address_hexlist]) + + #hash160s.add(addr_hash) + hash160s.add(addr_hash[-56:]) + + return hash160s + + # Called by WalletCardano.return_verified_password_or_false() to create a binary seed + def _derive_seed(self, mnemonic_words, passphrase_list = None): + if not passphrase_list: + salts = self._derivation_salts + else: + salts = passphrase_list + + seedList = [] + for salt in salts: + if self._check_icarus: + seedList.append(("icarus", + cardano.generateMasterKey_Icarus(mnemonic=mnemonic_words, + passphrase=salt, + wordlist=self.current_wordlist, + langcode=self._lang, + trezor=False) + ,salt)) + + if self._check_ledger: + seedList.append(("ledger", + cardano.generateMasterKey_Ledger(mnemonic=" ".join(mnemonic_words), + passphrase=salt) + ,salt)) + + if self._check_trezor: + seedList.append(("trezor", + cardano.generateMasterKey_Icarus(mnemonic=mnemonic_words, + passphrase=salt, + wordlist=self.current_wordlist, + langcode=self._lang, + trezor=True) + ,salt)) + + return seedList + + def _verify_seed(self, derivation_type, root_node, salt = None): + if salt is None: + salt = self._derivation_salts[0] + # Derive the chain of private keys for the specified path as per BIP32 + + for current_path in self._path_list: + root_node_derivation_type, current_path = current_path.split(":") + if root_node_derivation_type != derivation_type: + continue + + # Note: + # Address generation limit isn't actually relevant for most Cardano wallets, as all "base addresses" + # include the same account staking key. + # As such, you can simply derive the stake public key and check against that. + # (This gives a performance boost, even for an address limit of 1) + + #account_node = cardano.derive_child_keys(root_node, current_path, True) + ((Stake_kLP, Stake_kRP), Stake_AP, Stake_cP) = cardano.derive_child_keys(root_node, current_path + "/2/0", True) + stake_pubkeyhash = hashlib.blake2b(Stake_AP, digest_size=28).digest() + bech32_data = stake_pubkeyhash.hex() + if bech32_data in self._known_hash160s: # Check if this hash160 is in our list of known hash160s + global seedfoundpath + seedfoundpath = "m/" + current_path + + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ": ***MATCHING SEED FOUND***, Matched on Address at derivation path:", seedfoundpath) + # print("Found match with Hash160: ", binascii.hexlify(test_hash160)) + + if (len(self._derivation_salts) > 1): + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ": ***MATCHING SEED FOUND***, Matched with BIP39 Passphrase:", salt[8:]) + + return True + + # Child key derivation for Cardano is not required (see above) but leaving it here as it might be useful in the future + + # for i in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + # + # Spend_AP, Spend_cP = cardano.derive_child_keys(account_node, "0/%d"%i, False) + # Stake_AP, Stake_cP = cardano.derive_child_keys(account_node, "2/0", False) + # + # spend_pubkeyhash = hashlib.blake2b(Spend_AP, digest_size=28).digest() + # stake_pubkeyhash = hashlib.blake2b(Stake_AP, digest_size=28).digest() + # + # bech32_data = (b"\x01" + spend_pubkeyhash + stake_pubkeyhash).hex() + # bech32_data = stake_pubkeyhash.hex() + # + # if bech32_data in self._known_hash160s: #Check if this hash160 is in our list of known hash160s + # global seedfoundpath + # seedfoundpath = "m/" + current_path + "/0/%d"%i + # + # print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ***MATCHING SEED FOUND***, Matched on Address at derivation path:", seedfoundpath) + # #print("Found match with Hash160: ", binascii.hexlify(test_hash160)) + # + # if(len(self._derivation_salts) > 1): + # print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ***MATCHING SEED FOUND***, Matched with BIP39 Passphrase:", salt[8:]) + # + # return True + return False + + + def return_verified_password_or_false(self, mnemonic_ids_list): + return self._return_verified_password_or_false_opencl(mnemonic_ids_list) if not isinstance(self.opencl_algo,int) \ + else self._return_verified_password_or_false_cpu(mnemonic_ids_list) + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def _return_verified_password_or_false_cpu(self, mnemonic_ids_list): + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + + if self.pre_start_benchmark or (not self._checksum_in_generator and not self._skip_worker_checksum): + # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test (Only required at the benchmark step, this is handled in the password generator now) + if not self._verify_checksum(mnemonic_ids): + continue + + # If we are writing out the checksummed seeds, add them to the queue + if self._savevalidseeds and not self.pre_start_benchmark: + self.worker_out_queue.put(mnemonic_ids) + continue + + # Convert the mnemonic sentence to seed bytes + _derive_seed_list = self._derive_seed(mnemonic_ids) + + for derivation_type, derived_seed, salt in _derive_seed_list: + if self._verify_seed(derivation_type, derived_seed, salt): + return mnemonic_ids, count # found it + + return False, count + + def _return_verified_password_or_false_opencl(self, mnemonic_ids_list): + checksummed_mnemonic_ids_list = [] + for mnemonic in mnemonic_ids_list: + if self._verify_checksum(mnemonic): + checksummed_mnemonic_ids_list.append(mnemonic) + + rootKeys = [] + + for i, salt in enumerate(self._derivation_salts,0): + if self._check_ledger: + mnemonic_list = [] + for mnemonic in checksummed_mnemonic_ids_list: + + mnemonic_list.append(" ".join(mnemonic).encode()) + + clResult = self.opencl_algo.cl_pbkdf2(self.opencl_context_pbkdf2_sha512[i], mnemonic_list, b"mnemonic"+salt, 2048, 64) + + results = zip(checksummed_mnemonic_ids_list, clResult) + + for mnemonic, result in results: + rootKeys.append((mnemonic, "ledger", cardano.generateRootKey_Ledger(result), salt)) + + if self._check_icarus or self._check_trezor: + if self._check_icarus: + entropy_list = [] + for mnemonic in checksummed_mnemonic_ids_list: + entropy_list.append(cardano.mnemonic_to_entropy(words=mnemonic, + wordlist=self.current_wordlist, + langcode=self._lang, + trezorDerivation=False)) + + clResult = self.opencl_algo.cl_pbkdf2_saltlist(self.opencl_context_pbkdf2_sha512_saltlist, salt, entropy_list, 4096, 96) + + results = zip(checksummed_mnemonic_ids_list, clResult) + + for mnemonic, result in results: + rootKeys.append((mnemonic, "icarus", cardano.generateRootKey_Icarus(result), salt)) + + if self._check_trezor: + entropy_list = [] + for mnemonic in checksummed_mnemonic_ids_list: + entropy_list.append(cardano.mnemonic_to_entropy(words=mnemonic, + wordlist=self.current_wordlist, + langcode=self._lang, + trezorDerivation=True)) + + clResult = self.opencl_algo.cl_pbkdf2_saltlist(self.opencl_context_pbkdf2_sha512_saltlist, salt, entropy_list, 4096, 96) + + results = zip(checksummed_mnemonic_ids_list, clResult) + + for mnemonic, result in results: + rootKeys.append((mnemonic, "trezor", cardano.generateRootKey_Icarus(result), salt)) + + for (mnemonic_full, derivationType, masterkey, salt) in rootKeys: + if " ".join(mnemonic_full) == "cave table seven there praise limit fat decorate middle gold ten battle trigger luggage demand": + print("Derivation Type:", derivationType) + (kL, kR), AP, cP = masterkey + print("Master Key") + print("kL:", kL.hex()) + print("kR:", kR.hex()) + print("AP:", AP.hex()) + print("cP:", cP.hex()) + + print("#Rootkeys:", len(rootKeys)) + + if self._verify_seed(derivationType, masterkey, salt): + return mnemonic_full, mnemonic_ids_list.index(mnemonic_full)+1 # found it + + return False, len(mnemonic_ids_list) + +############### Py_Crypto_HD_Wallet Based Wallets #################### +class WalletPyCryptoHDWallet(WalletBIP39): + def __init__(self, path = None, loading = False): + if not py_crypto_hd_wallet_available: + print() + print("ERROR: Cannot import py_crypto_hd_wallet which is required for this wallet type, install it via 'pip3 install py_crypto_hd_wallet'") + exit() + + super(WalletPyCryptoHDWallet, self).__init__(None, loading) + + + def __setstate__(self, state): + super(WalletPyCryptoHDWallet, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletPyCryptoHDWallet, cls).create_from_params(*args, **kwargs) + return self + + def passwords_per_seconds(self, seconds): + if self.opencl: + exit("Error: Wallet Type does not support OpenCL acceleration") + + if not self._passwords_per_second: + scalar_multiplies = 0 + for i in self._path_indexes[0]: # Just use the first derivation path for this... + if i < 2147483648: # if it's a normal child key + scalar_multiplies += 1 # then it requires a scalar multiply + if not self._chaincode: + scalar_multiplies += self._addrs_to_generate + 1 # each addr. to generate req. a scalar multiply + self._passwords_per_second = \ + calc_passwords_per_second(self._checksum_ratio, self._kdf_overhead, scalar_multiplies) + passwords_per_second = max(int(round(self._passwords_per_second * seconds)), 1) + # Divide the speed by however many passphrases we are testing for each seed (Otherwise the benchmarking step takes ages) + return passwords_per_second / len(self._derivation_salts) / 10 + + # Default method for adding addresses, doesn't worry about validating the addresses + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + + #With Py_Crypto_HD_Wallet type wallets we don't worry about converting to hash160 + # (Minor performancce hit, but not an issue) + for address in addresses: + hash160s.add(address) + + return hash160s + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def return_verified_password_or_false(self, mnemonic_ids_list): + + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + + if self.pre_start_benchmark or (not self._checksum_in_generator and not self._skip_worker_checksum): + # Check the (BIP39 or Electrum2) checksum; most guesses will fail this test (Only required at the benchmark step, this is handled in the password generator now) + if not self._verify_checksum(mnemonic_ids): + continue + + # If we are writing out the checksummed seeds, add them to the queue + if self._savevalidseeds and not self.pre_start_benchmark: + self.worker_out_queue.put(mnemonic_ids) + continue + + if self._verify_seed(mnemonic_ids): + return mnemonic_ids, mnemonic_ids_list.index(mnemonic_ids)+1 # found it + + return False, len(mnemonic_ids_list) + +############### Solana ############### + +@register_selectable_wallet_class('Solana BIP39/44 (Currently only on m/44\'/501\'/0\'/0\' derivation path)') +class WalletSolana(WalletPyCryptoHDWallet): + + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.SOLANA) + + wallet2 = wallet.CreateFromMnemonic("Solana", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + testAddress = wallet2.ToDict()['change_key']['address'] + + if testAddress in self._known_hash160s: + return True + + return False + +############### Avax ############### + +@register_selectable_wallet_class('Avalanche BIP39/44 (X-Addresses)') +class WalletAvalanche(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.AVAX_X_CHAIN) + + wallet2 = wallet.CreateFromMnemonic("Avalanche", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + wallet2.Generate(addr_num=self._addrs_to_generate, addr_off=self._address_start_index, acc_idx=0, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + walletDict = wallet2.ToDict()['address'] + + for address in walletDict: + if walletDict[address]['address'] in self._known_hash160s: + return True + + return False + + +############### Stellar ############### + +@register_selectable_wallet_class('Stellar BIP39/44') +class WalletStellar(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.STELLAR) + + wallet2 = wallet.CreateFromMnemonic("Stellar", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + if wallet2.ToDict()['account_key']['address'] in self._known_hash160s: + return True + + return False + +############### Tron ############### + +@register_selectable_wallet_class('Tron BIP39/44') +class WalletTron(WalletPyCryptoHDWallet): + + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.TRON) + + wallet2 = wallet.CreateFromMnemonic("Tron", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + address = wallet2.ToDict()['address']['address_0']['address'] + wallet_hex = base58_tools.b58decode_check(address)[1:] + #print(binascii.hexlify(wallet_hex)) + if wallet_hex in self._known_hash160s: + #print("correct:", binascii.hexlify(wallet_hex)) + return True + + return False + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + # Convert Tron addresses back to their equivalant HEX representation + + for address in addresses: + address_hex = base58_tools.b58decode_check(address)[1:] + hash160s.add(address_hex) + + return hash160s + +############### Polkadot (Substrate) ############### + +@register_selectable_wallet_class('Polkdadot Substrate Wallet (sr25519)') +class WalletPolkadotSubstrate(WalletPyCryptoHDWallet): + substratePaths = [""] + + def __init__(self, path = None, loading = False): + if not py_crypto_hd_wallet_available: + print() + print("ERROR: Cannot import py_crypto_hd_wallet which is required for Polkadot wallets, install it via 'pip3 install py_crypto_hd_wallet'") + exit() + + if path: + self.substratePaths = path + + super(WalletPyCryptoHDWallet, self).__init__(None, loading) + + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + wallet = py_crypto_hd_wallet.HdWalletSubstrateFactory(py_crypto_hd_wallet.HdWalletSubstrateCoins.POLKADOT) + + wallet2 = wallet.CreateFromMnemonic("PolkadotSubstrate", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for path in self.substratePaths: + wallet2.Generate(path = path) + if wallet2.ToDict()['key']['address'] in self._known_hash160s: + return True + + return False + +############### Cosmos ############### + +@register_selectable_wallet_class('Cosmos BIP44 + Any many other Cosmos Chains (Nym, GravityBridge, etc)') +class WalletCosmos(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.COSMOS) + + wallet2 = wallet.CreateFromMnemonic("Cosmos", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + if wallet2.ToDict()['address']['address_0']['address'] in self._known_hash160s: + return True + + return False + + # Override method for adding addresses, allows easy use of Cosmos Clones ( + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + + #With Py_Crypto_HD_Wallet type wallets we don't worry about converting to hash160 + # (Minor performancce hit, but not an issue) + for address in addresses: + hrp, pkey = bech32.bech32_decode(address) + + # Basically just convert all addresses to the equivalent cosmos address + cosmos_address = bech32.bech32_encode('cosmos', pkey) + + hash160s.add(cosmos_address) + + return hash160s + +############### Tezos ############### + +@register_selectable_wallet_class('Tezos BIP44') +class WalletTezos(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.TEZOS) + + wallet2 = wallet.CreateFromMnemonic("Tezos", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + if wallet2.ToDict()['change_key']['address'] in self._known_hash160s: + return True + + return False + +############### Secret Network ############### + +@register_selectable_wallet_class('Secret Network BIP44 (Old/Ledger Derivation Path)') +class WalletSecretNetworkOld(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.SECRET_NETWORK_OLD) + + wallet2 = wallet.CreateFromMnemonic("Cosmos", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + if wallet2.ToDict()['address']['address_0']['address'] in self._known_hash160s: + return True + + return False + +@register_selectable_wallet_class('Secret Network BIP44 (New Derivation Path)') +class WalletSecretNetworkNew(WalletPyCryptoHDWallet): + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.SECRET_NETWORK_NEW) + + wallet2 = wallet.CreateFromMnemonic("Cosmos", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + if wallet2.ToDict()['address']['address_0']['address'] in self._known_hash160s: + return True + + return False + + +############### MultiverseX ############### + +@register_selectable_wallet_class('MultiversX BIP39/44') +class WalletMultiversX(WalletPyCryptoHDWallet): + + def _verify_seed(self, mnemonic, passphrase = None): + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + wallet = py_crypto_hd_wallet.HdWalletBipFactory(py_crypto_hd_wallet.HdWalletBip44Coins.ELROND) + + wallet2 = wallet.CreateFromMnemonic("Elrond", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) + + wallet2.Generate(addr_num=self._addrs_to_generate, addr_off=self._address_start_index, acc_idx=0, + change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) + + test_addresses_dicts = wallet2.ToDict()['address'] + + test_addresses = test_addresses_dicts.values() + + for test_address in test_addresses: + if test_address['address'] in self._known_hash160s: + return True + + return False + + +# This uses the PyCryptoHDWallet implementation of Cardano Shelly, which is a bit more limited in terms of derivation path +# and wallet support, but is also several times faster. (ToDo: Implement speed improvement in to other implementation) +# +# ############### pyhd-cardano-shelly-ledger ############### +# @register_selectable_wallet_class('PyCryptoHD-Cardano-Shelly-Ledger') +# class WalletPyCryptoCardanoShellyLedger(WalletPyCryptoHDWallet): +# +# def _verify_seed(self, mnemonic, passphrase = None): +# if passphrase: +# testSaltList = [passphrase] +# else: +# testSaltList = self._derivation_salts +# +# for salt in testSaltList: +# +# wallet = py_crypto_hd_wallet.HdWalletCardanoShelleyFactory(py_crypto_hd_wallet.HdWalletCardanoShelleyCoins.CARDANO_LEDGER) +# +# wallet2 = wallet.CreateFromMnemonic("Tron", mnemonic = " ".join(mnemonic), passphrase = salt.decode()) +# +# for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): +# wallet2.Generate(addr_num=1, addr_off=0, acc_idx=account_index, +# change_idx=py_crypto_hd_wallet.HdWalletBipChanges.CHAIN_EXT) +# +# if wallet2.ToDict()['address']['address_0']['address'] in self._known_hash160s: +# return True +# +# if wallet2.ToDict()['staking_key']['address'] in self._known_hash160s: +# return True +# +# return False + +############### Helium ############### + +@register_selectable_wallet_class('Helium BIP39/44 & Mobile') +class WalletHelium(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not nacl_available: + exit("Helium Wallet Requires the nacl module, this can be installed via pip3 install pynacl") + if not bitstring_available: + exit("Helium Wallet Requires the bitstring module, this can be installed via pip3 install bitstring") + super(WalletHelium, self).__init__(None, loading) + + + def __setstate__(self, state): + super(WalletHelium, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + kwargs["address_limit"] = 1 #Address limit not relevant in Helium Wallets + + self = super(WalletHelium, cls).create_from_params(*args, **kwargs) + return self + + def passwords_per_seconds(self, seconds): + # A bit ugly to put this here, but a good spot in that it only gets called once before anyhting starts... + if self._savevalidseeds: + exit("Error: Save Valid Seeds not supported for Helium Wallets") + if self._checksum_in_generator: + exit("Error: Checksum in Generator not supported for Helium Wallets") + if self.opencl: + exit("Error: Wallet Type does not support OpenCL acceleration") + + return 20000 + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + + for address in addresses: + + address_data = base58.b58decode_check(address) + + hash160s.add(address_data[2:]) + + return hash160s + + # Pulled from https://github.com/trezor/python-mnemonic and modified to fix bug in Trezor derivation + # See https://github.com/trezor/trezor-firmware/pull/1388 + def _verify_seed(self, words: Union[List[str], str]): + wordlist = self.current_wordlist + langcode = self._lang + if not isinstance(words, tuple) and not isinstance(words, list): + words = words.split(" ") + if len(words) not in [12, 15, 18, 21, 24]: + raise ValueError( + "Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)." + % len(words) + ) + # Look up all the words in the list and construct the + # concatenation of the original entropy and the checksum. + concatLenBits = len(words) * 11 + concatBits = [False] * concatLenBits + wordindex = 0 + if langcode == "en": + use_binary_search = True + else: + use_binary_search = False + for word in words: + # Find the words index in the wordlist + ndx = ( + binary_search(wordlist, word) + if use_binary_search + else wordlist.index(word) + ) + if ndx < 0: + raise LookupError('Unable to find "%s" in word list.' % word) + # Set the next 11 bits to the value of the index. + for ii in range(11): + concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0 + wordindex += 1 + checksumLengthBits = concatLenBits // 33 + entropyLengthBits = concatLenBits - checksumLengthBits + # Extract original entropy as bytes. + entropy = bytearray(entropyLengthBits // 8) + + for ii in range(len(entropy)): + for jj in range(8): + if concatBits[(ii * 8) + jj]: + entropy[ii] |= 1 << (7 - jj) + # Take the digest of the entropy. + hashBytes = hashlib.sha256(entropy).digest() + hashBits = list( + itertools.chain.from_iterable( + [c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes + ) + ) + + # Need to just keep a copy of the BIP39 checksum + entropyLengthBits = concatLenBits - checksumLengthBits + checksumBits = BitArray(concatBits).bin[-checksumLengthBits:] + + # Mobile wallet generates seeds with a checksum of 0000 + if (checksumBits != '0000'): + # Check all the checksum bits for BIP39 seeds + for i in range(checksumLengthBits): + if concatBits[entropyLengthBits + i] != hashBits[i]: + if not self._skip_worker_checksum: #Can ignore the checksum if forced + return False + + seed = bytes(entropy + entropy) + + public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed) + + if public_key in self._known_hash160s: + return True + + return False + + # This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic + # is correct return it, else return False for item 0; return a count of mnemonics checked for item 1 + def return_verified_password_or_false(self, mnemonic_ids_list): + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + + if self._verify_seed(mnemonic_ids): + return mnemonic_ids, count # found it + + return False, count + + +############### BCH ############### + +@register_selectable_wallet_class('BCH Standard BIP39/44') +class WalletBCH(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/BCH.txt") + super(WalletBCH, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletBCH, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletBCH, cls).create_from_params(*args, **kwargs) + return self + +############### Dash ############### + +@register_selectable_wallet_class('Dash Standard BIP39/44') +class WalletDash(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/DASH.txt") + super(WalletDash, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletDash, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletDash, cls).create_from_params(*args, **kwargs) + return self + +############### Dogecoin ############### + +@register_selectable_wallet_class('Dogecoin Standard BIP39/44') +class WalletDogecoin(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/DOGE.txt") + super(WalletDogecoin, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletDogecoin, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletDogecoin, cls).create_from_params(*args, **kwargs) + return self + +############### Vertcoin ############### + +@register_selectable_wallet_class('Vertcoin Standard BIP39/44') +class WalletVertcoin(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/VTC.txt") + super(WalletVertcoin, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletVertcoin, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletVertcoin, cls).create_from_params(*args, **kwargs) + return self + +############### Litecoin ############### + +@register_selectable_wallet_class('Litecoin Standard BIP39/44') +class WalletLitecoin(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/LTC.txt") + super(WalletLitecoin, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletLitecoin, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletLitecoin, cls).create_from_params(*args, **kwargs) + return self + +############### Monacoin ############### + +@register_selectable_wallet_class('Monacoin Standard BIP39/44') +class WalletMonacoin(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/MONA.txt") + super(WalletMonacoin, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletMonacoin, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletMonacoin, cls).create_from_params(*args, **kwargs) + return self + +############### DigiByte ############### + +@register_selectable_wallet_class('DigiByte Standard BIP39/44') +class WalletDigiByte(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/DGB.txt") + super(WalletDigiByte, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletDigiByte, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletDigiByte, cls).create_from_params(*args, **kwargs) + return self + +############### Groestlcoin ############### + +@register_selectable_wallet_class('Groestlcoin Standard BIP39/44') +class WalletGroestlecoin(WalletBIP39): + + def __init__(self, path = None, loading = False): + global groestlcoin_hash + try: + import groestlcoin_hash + except ModuleNotFoundError: + print() + print("ERROR: Cannot import groestlcoin_hash which is required for GRS wallets, install it via 'pip3 install groestlcoin_hash'") + exit() + + if not path: path = load_pathlist("./derivationpath-lists/GRS.txt") + super(WalletGroestlecoin, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletGroestlecoin, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletGroestlecoin, cls).create_from_params(*args, **kwargs) + return self + +############### Ripple ############### + +@register_selectable_wallet_class('Ripple Standard BIP39/44') +class WalletRipple(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = load_pathlist("./derivationpath-lists/XRP.txt") + super(WalletRipple, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletRipple, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletRipple, cls).create_from_params(*args, **kwargs) + return self + +############### Stacks (STX) ############### + +@register_selectable_wallet_class('Stacks Standard BIP39/44') +class WalletStacks(WalletBIP39): + + def __init__(self, path = None, loading = False): + if not path: path = ["m/44'/5757'/0'/0"] + super(WalletStacks, self).__init__(path, loading) + + + def __setstate__(self, state): + super(WalletStacks, self).__setstate__(state) + # (re-)load the required libraries after being unpickled + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = super(WalletStacks, cls).create_from_params(*args, **kwargs) + return self + + @staticmethod + def _addresses_to_hash160s(addresses): + hash160s = set() + for address in addresses: + ver, hash160 = c32.c32addressDecode(address) + hash160s.add(binascii.unhexlify(hash160)) + return hash160s + +@register_selectable_wallet_class("Stellar (XLM) BIP39") +class WalletXLM(WalletBIP39): + def __init__(self, path=None, loading=False): + # Use Stellar's default derivation path if none specified + if not path: path = ["m/44'/148'"] + super(WalletXLM, self).__init__(path, loading) + + def _verify_seed(self, arg_seed_bytes, passphrase=None): + try: + from slip10 import SLIP10, HARDENED_INDEX + except ImportError: + exit( + "\nERROR: Cannot Load slip10, install it via pip3 install slip10") + + try: + from stellar_sdk import Keypair + except ImportError: + exit( + "\nERROR: Cannot Load stellar_sdk, install it via pip3 install stellar_sdk") + + """ + Verify whether BIP39-derived seed bytes match a known Stellar address. + Uses SLIP-0010 / Ed25519 via Trezor python-slip10. + """ + if passphrase: + testSaltList = [passphrase] + else: + testSaltList = self._derivation_salts + + for salt in testSaltList: + + for account_index in range(self._address_start_index, self._address_start_index + self._addrs_to_generate): + + # Initialize SLIP10 with Ed25519 curve + slip = SLIP10.from_seed(arg_seed_bytes, curve_name="ed25519") + + # Stellar increments addresses on what is normally the account index. All addresses are also hardened... + bip, coin = self._path_indexes[0] + path = [ + bip, + coin, + account_index + HARDENED_INDEX + ] + + derived = slip.get_child_from_path(path) + + # Extract private key (32 bytes) + privkey = derived.get_privkey_from_path([]) + + # Derive Stellar address (G...) + kp = Keypair.from_raw_ed25519_seed(privkey) + pubkey = kp.raw_public_key() + + # Check for match + if pubkey in self._known_hash160s: + if salt and len(self._derivation_salts) > 1: + print("Passphrase:", salt.decode()) + return True + + return False + + def _addresses_to_hash160s(self, addresses): + """ + Override for Stellar: Just store the pubkey (despite the function name) + """ + try: + from stellar_sdk import StrKey + except ImportError: + exit( + "\nERROR: Cannot Load stellar_sdk, install it via pip3 install stellar_sdk") + + hash160s = [] + + for addr in addresses: + try: + pubkey_bytes = StrKey.decode_ed25519_public_key(addr) + hash160s.append(pubkey_bytes) + except Exception as e: + print(f"Warning: Could not decode Stellar address '{addr}': {e}") + continue + + return hash160s + +############### SLIP39 Seed Share ############### + +@register_selectable_wallet_class("SLIP39 Seed Share") +class WalletSLIP39Seed(WalletBase): + """Wallet class used to validate SLIP39 shares. + + This class allows :mod:`seedrecover.py` to recover SLIP39 shares with + typographical errors. It validates guesses using the ``shamir-mnemonic`` + library without requiring any address checking. + """ + + _words = None + + def __init__(self, path=None, loading=False): + if not shamir_mnemonic_available: + print() + print( + "ERROR: Cannot import shamir_mnemonic which is required for SLIP39 share recovery, install it via 'pip3 install shamir-mnemonic[cli]'" + ) + exit() + super(WalletSLIP39Seed, self).__init__(loading) + + @classmethod + def _load_wordlist(cls): + if not cls._words: + from shamir_mnemonic import wordlist as sw + cls._words = tuple(sw.WORDLIST) + cls._word_to_id = {word: idx for idx, word in enumerate(cls._words)} + + @property + def word_ids(self): + return range(len(self._words)) + + @classmethod + def id_to_word(cls, idx): + return cls._words[idx] + + @classmethod + def config_mnemonic(cls, mnemonic_guess=None, closematch_cutoff=0.65, expected_len=None): + """Configure globals for SLIP39 share recovery.""" + if not mnemonic_guess: + init_gui() + if tk_root: + mnemonic_guess = tk.simpledialog.askstring( + "SLIP39 share", "Please enter your best guess for the SLIP39 share:") + else: + print("No mnemonic guess specified... Exiting...") + exit() + if not mnemonic_guess: + sys.exit("canceled") + + cls._load_wordlist() + mnemonic_guess = str(mnemonic_guess) + + global mnemonic_ids_guess, close_mnemonic_ids, num_inserts, num_deletes + mnemonic_ids_guess = () + close_mnemonic_ids = {} + for word in mnemonic_guess.lower().split(): + close_words = difflib.get_close_matches(word, cls._words, sys.maxsize, closematch_cutoff) + if close_words: + if close_words[0] != word: + print(f"'{word}' was in your guess, but it's not a valid SLIP39 word;\n trying '{close_words[0]}' instead.") + mnemonic_ids_guess += cls._word_to_id[close_words[0]], + close_mnemonic_ids[mnemonic_ids_guess[-1]] = tuple((cls._word_to_id[w],) for w in close_words[1:]) + else: + if word != 'seed_token_placeholder': + print(f"'{word}' was in your guess, but there is no similar SLIP39 word;\n trying all possible seed words here instead.") + mnemonic_ids_guess += None, + + guess_len = len(mnemonic_ids_guess) + if expected_len is None: + expected_len = max(guess_len, slip39_min_words) + if guess_len > 28: + expected_len = 33 + print( + "Assuming a", + expected_len, + "word share. (This can be overridden with --share-length)", + ) + + num_inserts = max(expected_len - len(mnemonic_ids_guess), 0) + num_deletes = max(len(mnemonic_ids_guess) - expected_len, 0) + + @classmethod + def create_from_params(cls, *args, **kwargs): + self = cls(loading=True) + self._load_wordlist() + return self + + # Performs basic checks so that clearly invalid mnemonic_ids can be skipped + @staticmethod + def verify_mnemonic_syntax(mnemonic_ids): + return ( + len(mnemonic_ids) + == len(mnemonic_ids_guess) + num_inserts - num_deletes + and None not in mnemonic_ids + ) + + def passwords_per_seconds(self, seconds): + return max(int(seconds * 1000), 1) + + def _verify_checksum(self, mnemonic_ids): + from shamir_mnemonic.share import Share + try: + Share.from_mnemonic(" ".join(self.id_to_word(i) for i in mnemonic_ids)) + return True + except Exception: + return False + + def return_verified_password_or_false(self, mnemonic_ids_list): + for count, mnemonic_ids in enumerate(mnemonic_ids_list, 1): + if None not in mnemonic_ids and self._verify_checksum(mnemonic_ids): + return mnemonic_ids, count + return False, count + + def performance_iterator(self): + """Generate infinite SLIP39 share guesses for performance testing.""" + length = len(mnemonic_ids_guess) + num_inserts - num_deletes + prefix = tuple(random.randrange(len(self._words)) for _ in range(max(length - 4, 0))) + for guess in itertools.product(range(len(self._words)), repeat=min(length, 4)): + yield prefix + guess +################################### Main ################################### + +tk_root = None +def init_gui(): + # just return and leave tk_root as none if gui force disabled... + global no_gui + if no_gui: + print("Warning: GUI disabled, you will need to set some recovery arguments manually") + return + + global disable_security_warnings + global tk_root, tk, tkFileDialog, tkSimpleDialog, tkMessageBox + if not tk_root: + + if sys.platform == "win32": + # Some py2exe .dll's, when registered as Windows shell extensions (e.g. SpiderOak), can interfere + # with Python scripts which spawn a shell (e.g. a file selection dialog). The code below blocks + # required modules from loading and prevents any such py2exe .dlls from causing too much trouble. + sys.modules["win32api"] = None + sys.modules["win32com"] = None + + try: + import tkinter as tk + import tkinter.filedialog + import tkinter.simpledialog + import tkinter.messagebox + tk_root = tk.Tk(className="seedrecover.py") # initialize library + tk_root.withdraw() # but don't display a window (yet) + if not disable_security_warnings: + tkinter.messagebox.showinfo("Security Warning", "Most crypto wallet software and hardware wallets go to great lengths to protect your wallet password, seed phrase and private keys. BTCRecover isn't designed to offer this level of security, so it is possible that malware on your PC could gain access to this sensitive information while it is stored in memory in the use of this tool...\n\nAs a precaution, you should run this tool in a secure, offline environment and not simply use your normal, internet connected desktop environment... At the very least, you should disconnect your PC from the network and only reconnect it after moving your funds to a new seed... (Or if you run the tool on your internet conencted PC, move it to a new seed as soon as practical\n\nYou can disable this message by running this tool with the --dsw argument") + except: + print("Warning: Unable to load TK, no gui available, you will need to set some recovery arguments manually") + +# seed.py uses routines from password.py to generate guesses, however instead +# of dealing with passwords (immutable sequences of characters), it deals with +# seeds (represented as immutable sequences of mnemonic_ids). More specifically, +# seeds are tuples of mnemonic_ids, and a mnemonic_id is just an int for Electrum1, +# or a UTF-8 bytestring id for most other wallet types. + +# These are simple typo generators; see btcrpass.py for additional information. +# Instead of returning iterables of sequences of characters (iterables of strings), +# these return iterables of sequences of mnemonic_ids (iterables of partial seeds). +# +@btcrpass.register_simple_typo("deleteword") +def delete_word(mnemonic_ids, i): + return (), +# +@btcrpass.register_simple_typo("replaceword") +def replace_word(mnemonic_ids, i): + if mnemonic_ids[i] is None: return (), # don't touch invalid words + return ((new_id,) for new_id in loaded_wallet.word_ids if new_id != mnemonic_ids[i]) +# +@btcrpass.register_simple_typo("replacecloseword") +def replace_close_word(mnemonic_ids, i): + if mnemonic_ids[i] is None: return (), # don't touch invalid words + return close_mnemonic_ids[mnemonic_ids[i]] # the pre-calculated similar words +# +@btcrpass.register_simple_typo("replacewrongword") +def replace_wrong_word(mnemonic_ids, i): + if mnemonic_ids[i] is not None: return (), # only replace invalid words + return ((new_id,) for new_id in loaded_wallet.word_ids) + + +# Builds a command line and then runs btcrecover with it. +# typos - max number of mistakes to apply to each guess +# big_typos - max number of "big" mistakes to apply to each guess; +# a big mistake involves replacing or inserting a word using the +# full word list, and significantly increases the search time +# min_typos - min number of mistakes to apply to each guess +num_inserts = num_deletes = 0 +def run_btcrecover(typos, big_typos = 0, min_typos = 0, is_performance = False, extra_args = [], tokenlist = None, passwordlist = None, listpass = None, min_tokens = None, max_tokens = None, mnemonic_length = None, seed_transform_wordswaps = None, seed_transform_trezor_common_mistakes = None, keep_tokens_order = False): + if typos < 0: # typos == 0 is silly, but causes no harm + raise ValueError("typos must be >= 0") + if big_typos < 0: + raise ValueError("big-typos must be >= 0") + if big_typos > typos: + raise ValueError("typos includes big_typos, therefore it must be >= big_typos") + # min_typos < 0 is silly, but causes no harm + # typos < min_typos is an error; it's checked in btcrpass.parse_arguments() + + # Local copies of globals whose changes should only be visible locally + l_num_inserts = num_inserts + l_num_deletes = num_deletes + + # Number of words that were definitely wrong in the guess + num_wrong = sum(map(lambda id: id is None, mnemonic_ids_guess)) + + # Start building the command-line arguments + btcr_args = "--typos " + str(typos) + + if tokenlist: + btcr_args += " --tokenlist " + str(tokenlist) + + if max_tokens: + btcr_args += " --max-tokens " + str(max_tokens) + else: + btcr_args += " --max-tokens " + str(big_typos) + + if min_tokens: + btcr_args += " --min-tokens " + str(min_tokens) + else: + btcr_args += " --min-tokens " + str(big_typos) + + btcr_args += " --seedgenerator" + btcr_args += " --mnemonic-length " + str(mnemonic_length) + + if keep_tokens_order: + btcr_args += " --keep-tokens-order" + + if passwordlist: + btcr_args += " --passwordlist " + str(passwordlist) + btcr_args += " --seedgenerator" + + if listpass: + btcr_args += " --listpass" + + if is_performance: + btcr_args += " --performance" + # These typos are not supported by seedrecover with --performance testing: + l_num_inserts = l_num_deletes = num_wrong = 0 + + if seed_transform_wordswaps: + btcr_args += " --seed-transform-wordswaps " + str(seed_transform_wordswaps) + if seed_transform_trezor_common_mistakes: + btcr_args += " --seed-transform-trezor-common-mistakes " + str(seed_transform_trezor_common_mistakes) + + # First, check if there are any required typos (if there are missing or extra + # words in the guess) and adjust the max number of other typos to later apply + + any_typos = typos # the max number of typos left after removing required typos + #big_typos = # the max number of "big" typos after removing required typos (an arg from above) + + if l_num_deletes: # if the guess is too long (extra words need to be deleted) + any_typos -= l_num_deletes + btcr_args += " --typos-deleteword" + if l_num_deletes < typos: + btcr_args += " --max-typos-deleteword " + str(l_num_deletes) + + if num_wrong: # if any of the words were invalid (and need to be replaced) + any_typos -= num_wrong + big_typos -= num_wrong + btcr_args += " --typos-replacewrongword" + if num_wrong < typos: + btcr_args += " --max-typos-replacewrongword " + str(num_wrong) + + # For (only) Electrum2, num_inserts are not required, so we try several sub-phases with a + # different number of inserts each time; for all others the total num_inserts are required + if isinstance(loaded_wallet, WalletElectrum2): + num_inserts_to_try = range(l_num_inserts + 1) # try a range + else: + num_inserts_to_try = l_num_inserts, # only try the required max + for subphase_num, cur_num_inserts in enumerate(num_inserts_to_try, 1): + + # Create local copies of these which are reset at the beginning of each loop + l_any_typos = any_typos + l_big_typos = big_typos + l_btcr_args = btcr_args + + ids_to_try_inserting = None + if cur_num_inserts: # if the guess is too short (words need to be inserted) + l_any_typos -= cur_num_inserts + l_big_typos -= cur_num_inserts + # (instead of --typos-insert we'll set inserted_items=ids_to_try_inserting below) + ids_to_try_inserting = ((id,) for id in loaded_wallet.word_ids) + l_btcr_args += " --max-adjacent-inserts " + str(cur_num_inserts) + if cur_num_inserts < typos: + l_btcr_args += " --max-typos-insert " + str(cur_num_inserts) + + # For >1 subphases, print this out now or just after the skip-this-phase check below + if len(num_inserts_to_try) > 1: + subphase_msg = " - subphase {}/{}: with {} inserted seed word{}".format( + subphase_num, len(num_inserts_to_try), + cur_num_inserts, "" if cur_num_inserts == 1 else "s") + if subphase_num > 1: + print(subphase_msg) + maybe_skipping = "the remainder of this phase." + else: + maybe_skipping = "this phase." + + if l_any_typos < 0: # if too many typos are required to generate valid mnemonics + print("Not enough mistakes permitted to produce a valid seed; skipping", maybe_skipping) + return False + if l_big_typos < 0: # if too many big typos are required to generate valid mnemonics + print("Not enough entirely different seed words permitted; skipping", maybe_skipping) + return False + assert typos >= cur_num_inserts + l_num_deletes + num_wrong + + if subphase_num == 1 and len(num_inserts_to_try) > 1: + print(subphase_msg) + + # Because btcrecover doesn't support --min-typos-* on a per-typo basis, it ends + # up generating some invalid guesses. We can use --min-typos to filter out some + # of them (the remainder is later filtered out by verify_mnemonic_syntax()). + min_typos = max(min_typos, cur_num_inserts + l_num_deletes + num_wrong) + if min_typos: + l_btcr_args += " --min-typos " + str(min_typos) + + # Next, if the required typos above haven't consumed all available typos + # (as specified by the function's args), add some "optional" typos + + if l_any_typos: + l_btcr_args += " --typos-swap" + if l_any_typos < typos: + l_btcr_args += " --max-typos-swap " + str(l_any_typos) + + if l_big_typos: # if there are any big typos left, add the replaceword typo + l_btcr_args += " --typos-replaceword" + if l_big_typos < typos: + l_btcr_args += " --max-typos-replaceword " + str(l_big_typos) + + # only add replacecloseword typos if they're not already covered by the + # replaceword typos added above and there exists at least one close word + num_replacecloseword = l_any_typos - l_big_typos + if num_replacecloseword > 0 and any(len(ids) > 0 for ids in close_mnemonic_ids.values()): + l_btcr_args += " --typos-replacecloseword" + if num_replacecloseword < typos: + l_btcr_args += " --max-typos-replacecloseword " + str(num_replacecloseword) + + btcrpass.parse_arguments( + l_btcr_args.split() + extra_args, + inserted_items= ids_to_try_inserting, + wallet= loaded_wallet, + base_iterator= (mnemonic_ids_guess,) if not is_performance else None, # the one guess to modify + perf_iterator= lambda: loaded_wallet.performance_iterator(), + check_only= loaded_wallet.verify_mnemonic_syntax, + disable_security_warning_param=True + ) + (mnemonic_found, not_found_msg) = btcrpass.main() + + if mnemonic_found: + return mnemonic_found + elif not_found_msg is None: + return None # An error occurred or Ctrl-C was pressed inside btcrpass.main() + + return False # No error occurred; the mnemonic wasn't found + + +def register_autodetecting_wallets(): + """Registers wallets which can do file auto-detection with btcrecover's auto-detect mechanism + + :rtype: None + """ + btcrpass.clear_registered_wallets() + for wallet_cls, description in selectable_wallet_classes: + if hasattr(wallet_cls, "is_wallet_file"): + btcrpass.register_wallet_class(wallet_cls) + +def build_search_phases(wallet, phase, phase_transform): + """Return the run_btcrecover() phase configuration for the current invocation.""" + + if phase: + phases = [phase] + else: + passwords_per_seconds = wallet.passwords_per_seconds(1) + if passwords_per_seconds < 25: + phases = [dict(typos=1), dict(typos=2, min_typos=2)] + else: + phases = [dict(typos=2)] + phases.extend( + ( + dict(typos=1, big_typos=1), + dict(typos=2, big_typos=1, min_typos=2), + ) + ) + phases.append( + dict(typos=2, big_typos=2, min_typos=2, extra_args=["--no-dupchecks"]) + ) + + if phase_transform: + for phase_params in phases: + phase_params.update(phase_transform) + + return phases + + +def main(argv): + global loaded_wallet + loaded_wallet = wallet_type = None + create_from_params = {} # additional args to pass to wallet_type.create_from_params() + config_mnemonic_params = {} # additional args to pass to wallet.config_mnemonic() + phase = {} # if only one phase is requested, the args to pass to run_btcrecover() + phase_transform = {} # args applied to all run_btcrecover() phases without overriding defaults + extra_args = [] # additional args to pass to btcrpass.parse_arguments() (in run_btcrecover()) + listseeds = False + + if argv or "_ARGCOMPLETE" in os.environ: + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--wallet", metavar="FILE", help="the wallet file") + parser.add_argument("--wallet-type", metavar="TYPE", help="if not using a wallet file, the wallet type") + parser.add_argument("--mpk", metavar="XPUB-OR-HEX", help="if not using a wallet file, the master public key (xpub, ypub or zpub)") + parser.add_argument("--addrs", metavar="ADDRESS", nargs="+", help="if not using an mpk, address(es) in the wallet") + parser.add_argument("--addressdb", metavar="FILE", nargs="?", help="if not using addrs, use a full address database (default: %(const)s)", const=ADDRESSDB_DEF_FILENAME) + parser.add_argument("--addr-limit", type=int, metavar="COUNT", help="if using addrs or addressdb, the address generation limit") + parser.add_argument("--addr-start-index", type=int, metavar="COUNT", help="The index at which the addr-limit starts counting (Useful for wallets like Wasabi that may not start at zero)") + parser.add_argument("--typos", type=int, metavar="COUNT", help="the max number of mistakes to try (default: auto)") + parser.add_argument("--big-typos", type=int, metavar="COUNT", help="the max number of big (entirely different word) mistakes to try (default: auto or 0)") + parser.add_argument("--min-typos", type=int, metavar="COUNT", help="enforce a min # of mistakes per guess") + parser.add_argument("--close-match",type=float,metavar="CUTOFF",help="try words which are less/more similar for each mistake (0.0 to 1.0, default: 0.65)") + parser.add_argument("--passphrase", action="store_true", help="the mnemonic is augmented with a known passphrase (BIP39 or Electrum 2.x only)") + parser.add_argument("--passphrase-arg", metavar="PASSPHRASE", nargs="+", help="the mnemonic is augmented with a known passphrase, entered directly as an argument (BIP39 or Electrum 2.x only)") + parser.add_argument("--passphrase-list", metavar="FILE", help="Path to a file containing a list of passphrases to test") + parser.add_argument("--passphrase-prompt", action="store_true", help="prompt for the mnemonic passphrase via the terminal (default: via the GUI)") + parser.add_argument("--mnemonic", metavar="MNEMONIC", help="Your best guess of the mnemonic (if not entered, you will be prompted)") + parser.add_argument("--mnemonic-prompt", action="store_true", help="prompt for the mnemonic guess via the terminal (default: via the GUI)") + parser.add_argument("--mnemonic-length", type=int, metavar="WORD-COUNT", help="the length of the correct mnemonic (default: auto)") + parser.add_argument("--language", metavar="LANG-CODE", help="the wordlist language to use (see wordlists/README.md, default: auto)") + parser.add_argument("--bip32-path", metavar="PATH", nargs="+", help="path (e.g. m/0'/0/) excluding the final index. You can specify multiple derivation paths seperated by a space Eg: m/84'/0'/0'/0 m/84'/0'/1'/0 (default: BIP44,BIP49 & BIP84 account 0)") + parser.add_argument("--substrate-path", metavar="PATH", nargs="+", help="Substrate path (eg: //hard/soft). You can specify multiple derivation paths by a space Eg: //hard /soft //hard/soft (default: No Path)") + parser.add_argument("--slip39", action="store_true", help="recover a SLIP39 seed share") + parser.add_argument("--share-length", type=int, metavar="WORD-COUNT", help="the length of the SLIP39 share (default: auto)") + parser.add_argument("--checksinglexpubaddress", action="store_true", help="Check non-standard single address wallets (Like Atomic, MyBitcoinWallet, PT.BTC") + parser.add_argument("--force-p2sh", action="store_true", help="Force checking of P2SH segwit addresses for all derivation paths (Required for devices like CoolWallet S if if you are using P2SH segwit accounts on a derivation path that doesn't start with m/49')") + parser.add_argument("--force-p2tr", action="store_true", help="Force checking of P2TR (Taproot) addresses for all derivation paths (Required for wallets like Bitkeep/Bitget that put all accounts on m/44')") + parser.add_argument("--force-bip44", action="store_true", help="Force checking of BIP44 legacy (P2PKH) addresses even if they don't match the supplied addresses") + parser.add_argument("--force-bip84", action="store_true", help="Force checking of BIP84 native SegWit (P2WPKH) addresses even if they don't match the supplied addresses") + parser.add_argument("--disable-p2sh", action="store_true", help="Disable checking of P2SH segwit addresses") + parser.add_argument("--disable-p2tr", action="store_true", help="Disable checking of P2TR (Taproot) addresses") + parser.add_argument("--disable-bip44", action="store_true", help="Disable checking of BIP44 legacy (P2PKH) addresses") + parser.add_argument("--disable-bip84", action="store_true", help="Disable checking of BIP84 native SegWit (P2WPKH) addresses") + parser.add_argument("--pathlist", metavar="FILE", help="A list of derivation paths to be searched") + parser.add_argument("--transform-wordswaps", type=int, metavar="COUNT", help="Test swapping COUNT pairs of words within the mnemonic") + parser.add_argument( + "--transform-trezor-common-mistakes", + type=int, + metavar="COUNT", + help=( + "Test replacing up to COUNT mnemonic words using Trezor's " + "commonly misspelled word list" + ), + ) + parser.add_argument("--skip", type=int, metavar="COUNT", help="skip this many initial passwords for continuing an interrupted search") + parser.add_argument("--threads", type=int, metavar="COUNT", help="number of worker threads (default: For CPU Processing, logical CPU cores, for GPU, physical CPU cores)") + parser.add_argument("--worker", metavar="ID#(ID#2, ID#3)/TOTAL#", help="divide the workload between TOTAL# servers, where each has a different ID# between 1 and TOTAL# (You can optionally assign between 1 and TOTAL IDs of work to a server (eg: 1,2/3 will assign both slices 1 and 2 of the 3 to the server...)") + parser.add_argument("--max-eta", type=int, help="max estimated runtime before refusing to even start (default: 168 hours, i.e. 1 week)") + parser.add_argument("--no-eta", action="store_true", help="disable calculating the estimated time to completion") + parser.add_argument("--no-dupchecks", "-d", action="count", default=0, help="disable duplicate guess checking to save memory; specify up to four times for additional effect") + parser.add_argument("--no-progress", action="store_true", help="disable the progress bar") + parser.add_argument( + "--pre-start-seconds", + type=float, + default=30.0, + metavar="SECONDS", + help="limit how long the pre-start benchmark runs for (default: %(default)s seconds); use 0 to skip it", + ) + parser.add_argument( + "--skip-pre-start", + action="store_true", + help="skip the pre-start benchmark; equivalent to --pre-start-seconds 0", + ) + parser.add_argument("--no-pause", action="store_true", help="never pause before exiting (default: auto)") + parser.add_argument("--no-gui", action="store_true", help="Force disable the gui elements") + parser.add_argument( + "--beep-on-find", + action="store_true", + help="play a two-tone alert roughly every ten seconds when a seed is found", + ) + parser.add_argument( + "--beep-on-find-pcspeaker", + action="store_true", + help="force the alert to use the internal PC speaker when a seed is found", + ) + parser.add_argument("--performance", action="store_true", help="run a continuous performance test (Ctrl-C to exit)") + parser.add_argument("--btcr-args", action="store_true", help=argparse.SUPPRESS) + parser.add_argument("--version","-v",action="store_true", help="show full version information and exit") + parser.add_argument("--disablesecuritywarnings", "--dsw", action="store_true", help="Disable Security Warning Messages") + parser.add_argument("--tokenlist", metavar="FILE", help="The list of BIP39 words to be searched, formatted as a tokenlist") + parser.add_argument("--keep-tokens-order", action="store_true", + help="try tokens in the order in which they are listed in the file, without trying their permutations") + parser.add_argument("--max-tokens", type=int, help="The max number of tokens use to create potential seeds from the tokenlist") + parser.add_argument("--min-tokens", type=int, help="The minimum number of tokens use to create potential seeds from the tokenlist") + parser.add_argument("--seedlist", metavar="FILE", nargs="?", const="-", + help="A list of seed phrases to test (exactly one per line) from this file or from stdin, if used in conjunction with --multi-file-seedlist, this is the name of the first file to load") + parser.add_argument("--multi-file-seedlist",action="store_true", help="Enables the loading of a seedlist file split over mulitple files with the suffix _XXXX.txt") + + parser.add_argument("--listseeds", action="store_true", + help="Just list all seed phrase combinations to test and exit") + parser.add_argument("--savevalidseeds", metavar="FILE", + help="Only list valid seed combinations, then exit. (Similar to --listseeds, but only lists valid BIP39/Electrum seeds)") + parser.add_argument("--savevalidseeds-filesize", type=int, metavar="COUNT", help="The number of valid seeds to include in each file, multiple output files are automatically incremented when this number is reached") + + parser.add_argument("--skip-worker-checksum", action="store_true", + help="Skip the checksum test for BIP39/Electrum seeds (This will force test all seeds, as opposed to 1/10, and will slow things down a lot)") + opencl_group = parser.add_argument_group("OpenCL acceleration") + opencl_group.add_argument("--enable-opencl", action="store_true", help="enable experimental OpenCL-based (GPU) acceleration (only supports BIP39 (for supported coin) and Electrum wallets)") + opencl_group.add_argument("--opencl-workgroup-size", type=int, nargs="+", metavar="PASSWORD-COUNT", help="OpenCL global work size (Seeds are tested in batches, this impacts that batch size)") + opencl_group.add_argument("--opencl-platform", type=int, nargs="+", metavar="ID", help="Choose the OpenCL platform (GPU) to use (default: auto)") + opencl_group.add_argument("--opencl-devices", metavar="ID1 ID2 ID3", nargs="+", help="Choose which OpenCL devices for a given to use as a space seperated list eg: 1 2 4 (default: all)") + opencl_group.add_argument("--opencl-info", action="store_true", help="list available GPU names and IDs, then exit") + opencl_group.add_argument("--force-checksum-in-generator", action="store_true", help="GPU processing currently performs seed checksums in the main thread, which works well for 12 word BIP39 seeds, but hurts performance in 12 and 24 word seeds") + + # Optional bash tab completion support + try: + import argcomplete + argcomplete.autocomplete(parser) + except ImportError: + pass + assert argv + + # Parse the args; unknown args will be passed to btcrpass.parse_arguments() iff --btcr-args is specified + args, extra_args = parser.parse_known_args(argv) + if extra_args and not args.btcr_args: + parser.parse_args(argv) # re-parse them just to generate an error for the unknown args + assert False + + # Assign the no-gui to a global variable... + global no_gui + if args.no_gui: + no_gui = True + + # Pass an argument so that btcrpass knows that we are running a seed recovery + extra_args.append("--btcrseed") + + #Disable Security Warnings if parameter set... + global disable_security_warnings + if args.disablesecuritywarnings: + disable_security_warnings = True + else: + disable_security_warnings = False + + success_alert.configure_pc_speaker(args.beep_on_find_pcspeaker) + beep_on_find_enabled = args.beep_on_find or args.beep_on_find_pcspeaker + success_alert.set_beep_on_find(beep_on_find_enabled) + if beep_on_find_enabled: + extra_args.append("--beep-on-find") + if args.beep_on_find_pcspeaker: + extra_args.append("--beep-on-find-pcspeaker") + + # Version information is always printed by seedrecover.py, so just exit + if args.version: sys.exit(0) + + if args.opencl_info: + info = opencl_information() + info.printfullinfo() + exit(0) + + if args.wallet: + loaded_wallet = btcrpass.load_wallet(args.wallet) + + if args.savevalidseeds: + if args.enable_opencl: exit("Error: SaveValidSeeds not a valid option when OpenCL is in use...") + print("WARNING: Seeds aren't actually checked when --savevalidseeds argument is used, only generated, checksummed and saved...") + args.addrs = ['1QLSbWFtVNnTFUq5vxDRoCpvvsSqTTS88P'] + args.addr_limit = 1 + args.no_eta = True + args.no_dupchecks = 4 + if args.wallet_type: + if args.wallet_type.lower() == "ethereum": + args.wallet_type = "bip39" + + # Look up the --wallet-type arg in the list of selectable_wallet_classes + if args.slip39: + wallet_type = WalletSLIP39Seed + elif args.wallet_type: + if args.wallet: + print("warning: --wallet-type is ignored when a wallet is provided", file=sys.stderr) + else: + args.wallet_type = args.wallet_type.lower() + wallet_type_names = [] + for cls, desc in selectable_wallet_classes: + wallet_type_names.append(cls.__name__.replace("Wallet", "", 1).lower()) + if wallet_type_names[-1] == args.wallet_type: + wallet_type = cls + break + else: + wallet_type_names.sort() + sys.exit("--wallet-type must be one of: " + ", ".join(wallet_type_names)) + + if args.mpk: + if args.wallet: + print("warning: --mpk is ignored when a wallet is provided", file=sys.stderr) + else: + create_from_params["mpk"] = args.mpk + + if args.addrs: + if args.wallet: + print("warning: --addrs is ignored when a wallet is provided", file=sys.stderr) + else: + create_from_params["addresses"] = args.addrs + + if args.addr_limit is not None: + if args.wallet: + print("warning: --addr-limit is ignored when a wallet is provided", file=sys.stderr) + else: + create_from_params["address_limit"] = args.addr_limit + + if args.addr_start_index is not None: + create_from_params["address_start_index"] = args.addr_start_index + + if args.addressdb and not os.path.isfile(args.addressdb): + sys.exit("file '{}' does not exist".format(args.addressdb)) + + if args.typos is not None: + phase["typos"] = args.typos + + if args.big_typos is not None: + phase["big_typos"] = args.big_typos + if not args.typos: + phase["typos"] = args.big_typos + + if args.min_typos is not None: + if not phase.get("typos"): + sys.exit("--typos must be specified when using --min_typos") + phase["min_typos"] = args.min_typos + + if args.close_match is not None: + config_mnemonic_params["closematch_cutoff"] = args.close_match + + if not disable_security_warnings: + # Print a security warning before giving users the chance to enter ir seed.... + # Also a good idea to keep this warning as late as possible in terms of not needing it to be display for --version --help, or if there are errors in other parameters. + print("btcrseed") + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + print( + "Most crypto wallet software and hardware wallets go to great lengths to protect your wallet password, seed phrase and private keys. BTCRecover isn't designed to offer this level of security, so it is possible that malware on your PC could gain access to this sensitive information while it is stored in memory in the use of this tool...") + print() + print( + "As a precaution, you should run this tool in a secure, offline environment and not simply use your normal, internet connected desktop environment... At the very least, you should disconnect your PC from the network and only reconnect it after moving your funds to a new seed... (Or if you run the tool on your internet conencted PC, move it to a new seed as soon as practical)") + print() + print("You can disable this message by running this tool with the --dsw argument") + print() + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + + if args.mnemonic: + config_mnemonic_params["mnemonic_guess"] = args.mnemonic + + if args.mnemonic_prompt: + encoding = sys.stdin.encoding or "ASCII" + if "utf" not in encoding.lower(): + print("terminal does not support UTF; mnemonics with non-ASCII chars might not work", file=sys.stderr) + mnemonic_guess = input("Please enter your best guess for your mnemonic (seed)\n> ") + if not mnemonic_guess: + sys.exit("canceled") + config_mnemonic_params["mnemonic_guess"] = mnemonic_guess + + if args.passphrase_prompt: + import getpass + encoding = sys.stdin.encoding or "ASCII" + if "utf" not in encoding.lower(): + print("warning: terminal does not support UTF; passwords with non-ASCII chars might not work", file=sys.stderr) + print("(note your passphrase will not be displayed as you type)") + while True: + passphrase = getpass.getpass("Please enter the passphrase you added when the seed was first created: ") + if not passphrase: + sys.exit("canceled") + if passphrase == getpass.getpass("Please re-enter the passphrase: "): + break + print("The passphrases did not match, try again.") + config_mnemonic_params["passphrases"] = [passphrase,] + elif args.passphrase_arg: + config_mnemonic_params["passphrases"] = args.passphrase_arg + elif args.passphrase or args.passphrase_list: + config_mnemonic_params["passphrases"] = True # config_mnemonic() will prompt for one + + if args.passphrase_list: + passphrases = load_passphraselist(args.passphrase_list) + config_mnemonic_params["passphrases"] = passphrases + + if args.seedlist or args.tokenlist: + if args.mnemonic_length is None: + exit("Error: Mnemonic length needs to be specificed if using tokenlist or passwordlist") + if args.language is None: + if args.wallet_type != 'electrum1': + exit("Error: Language needs to be specificed if using tokenlist or passwordlist") + config_mnemonic_params["mnemonic_guess"] = ("seed_token_placeholder " * args.mnemonic_length)[:-1] + phase["big_typos"] = args.mnemonic_length + phase["typos"] = args.mnemonic_length + phase["max_tokens"] = args.max_tokens + phase["min_tokens"] = args.min_tokens + phase["mnemonic_length"] = args.mnemonic_length + phase["keep_tokens_order"] = args.keep_tokens_order + + if args.tokenlist: + phase["tokenlist"] = args.tokenlist + + if args.seedlist: + phase["passwordlist"] = args.seedlist + + if args.wallet_type == "electrum1": + args.language = None + + if args.language: + config_mnemonic_params["lang"] = args.language.lower() + + if args.mnemonic_length is not None: + config_mnemonic_params["expected_len"] = args.mnemonic_length + + if args.share_length is not None: + config_mnemonic_params["expected_len"] = args.share_length + + if args.bip32_path and not args.pathlist: + if args.wallet: + print("warning: --bip32-path is ignored when a wallet is provided", file=sys.stderr) + else: + create_from_params["path"] = args.bip32_path + + if args.substrate_path and not args.pathlist: + if args.wallet: + print("warning: --bip32-path is ignored when a wallet is provided", file=sys.stderr) + else: + create_from_params["path"] = args.substrate_path + + if args.pathlist: + if args.bip32_path: + print("warning: Pathlist overrides any --bip32-path or --substrate-path provided", file=sys.stderr) + create_from_params["path"] = load_pathlist(args.pathlist) + + if args.force_p2sh: + create_from_params["force_p2sh"] = True + + if args.force_p2tr: + create_from_params["force_p2tr"] = True + + if args.force_bip44: + create_from_params["force_bip44"] = True + + if args.force_bip84: + create_from_params["force_bip84"] = True + + if args.disable_p2sh: + create_from_params["disable_p2sh"] = True + + if args.disable_p2tr: + create_from_params["disable_p2tr"] = True + + if args.disable_bip44: + create_from_params["disable_bip44"] = True + + if args.disable_bip84: + create_from_params["disable_bip84"] = True + + if args.transform_wordswaps: + print("SEED-TRANSFORM: Checking", args.transform_wordswaps, "pairs of swapped words for each possible mnemonic") + phase_transform["seed_transform_wordswaps"] = args.transform_wordswaps + if args.transform_trezor_common_mistakes: + print( + "SEED-TRANSFORM: Checking up to", + args.transform_trezor_common_mistakes, + "Trezor common-mistake substitutions for each possible mnemonic", + ) + phase_transform["seed_transform_trezor_common_mistakes"] = ( + args.transform_trezor_common_mistakes + ) + + if args.checksinglexpubaddress: + create_from_params["checksinglexpubaddress"] = True + + # These arguments and their values are passed on to btcrpass.parse_arguments() + for argkey in "skip", "threads", "worker", "max_eta", "pre_start_seconds": + if args.__dict__[argkey] is not None: + extra_args.extend(("--"+argkey.replace("_", "-"), str(args.__dict__[argkey]))) + + + # These arguments (which have no values) are passed on to btcrpass.parse_arguments() + for argkey in "no_eta", "no_progress", "skip_pre_start": + if args.__dict__[argkey]: + extra_args.append("--"+argkey.replace("_", "-")) + + # Special Case for --no-dupchecks + if args.__dict__["no_dupchecks"] is not None: + for i in range(0, args.__dict__["no_dupchecks"]): + extra_args.append("--no-dupchecks") + + if args.performance: + create_from_params["is_performance"] = phase["is_performance"] = True + phase.setdefault("typos", 0) + if not args.mnemonic_prompt: + # Create a dummy mnemonic; only its language and length are used for anything + config_mnemonic_params["mnemonic_guess"] = " ".join("act" for i in range(args.mnemonic_length or 12)) + + if args.addressdb: + print("Loading address database ...") + createdAddressDB = create_from_params["hash160s"] = AddressSet.fromfile(open(args.addressdb, "rb")) + print("Loaded", len(createdAddressDB), "addresses from database ...") + + # Special Case where we don't know any mnemonic words (Using TokenList or PasswordList) + # simply configure the menonic to be all invalid words... + + if args.listseeds: + listseeds = True + phase["listpass"] = True + + else: # else if no command-line args are present + # Print a security warning before giving users the chance to enter ir seed.... + # Also a good idea to keep this warning as late as possible in terms of not needing it to be display for --version --help, or if there are errors in other parameters. + print("btcrseed") + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + print( + "Most crypto wallet software and hardware wallets go to great lengths to protect your wallet password, seed phrase and private keys. BTCRecover isn't designed to offer this level of security, so it is possible that malware on your PC could gain access to this sensitive information while it is stored in memory in the use of this tool...") + print() + print( + "As a precaution, you should run this tool in a secure, offline environment and not simply use your normal, internet connected desktop environment... At the very least, you should disconnect your PC from the network and only reconnect it after moving your funds to a new seed... (Or if you run the tool on your internet conencted PC, move it to a new seed as soon as practical)") + print() + print("You can disable this message by running this tool with the --dsw argument") + print() + print("* * * * * * * * * * * * * * * * * * * *") + print("* Security: Warning *") + print("* * * * * * * * * * * * * * * * * * * *") + print() + + global pause_at_exit + pause_at_exit = True + atexit.register(lambda: pause_at_exit and + not multiprocessing.current_process().name.startswith("PoolWorker-") and + input("Press Enter to exit ...")) + + if not loaded_wallet and not wallet_type: # neither --wallet nor --wallet-type were specified + + # Ask for a wallet file + init_gui() + if tk_root: # Skip if TK is not available... + wallet_filename = tk.filedialog.askopenfilename(title="Please select your wallet file if you have one") + else: + print("No wallet file or type specified... Exiting...") + exit() + + if wallet_filename: + loaded_wallet = btcrpass.load_wallet(wallet_filename) # raises on failure; no second chance + + if not loaded_wallet: # if no wallet file was chosen + + if not wallet_type: # if --wallet-type wasn't specified + + if tk_root: # Skip if TK is not available... + # Without a wallet file, we can't automatically determine the wallet type, so prompt the + # user to select a wallet that's been registered with @register_selectable_wallet_class + selectable_wallet_classes.sort(key=lambda x: x[1]) # sort by description + + class WalletTypeDialog(tk.simpledialog.Dialog): + def body(self, master): + self.wallet_type = None + self._index_to_cls = [] + self._selected_index = tk.IntVar(value= -1) + for i, (cls, desc) in enumerate(selectable_wallet_classes): + self._index_to_cls.append(cls) + tk.Radiobutton(master, variable=self._selected_index, value=i, text=desc) \ + .grid(row = i % 20, column = i // 20, sticky = tk.W, pady = 0) + def validate(self): + if self._selected_index.get() < 0: + tk.messagebox.showwarning("Wallet Type", "Please select a wallet type") + return False + return True + def apply(self): + self.wallet_type = self._index_to_cls[self._selected_index.get()] + # + wallet_type_dialog = WalletTypeDialog(tk_root, "Please select your wallet type") + wallet_type = wallet_type_dialog.wallet_type + if not wallet_type: + sys.exit("canceled") + + else: + print("No wallet or wallet type speciiced... Exiting...") + exit() + + try: + loaded_wallet = wallet_type.create_from_params(**create_from_params) + except TypeError as e: + matched = re.match(r"create_from_params\(\) got an unexpected keyword argument '(.*)'", str(e)) + if matched: + sys.exit("{} does not support the {} option".format(wallet_type.__name__, matched.group(1))) + raise + except ValueError as e: + sys.exit(e) + + # ===================== + # Set Wallet Parameters + # ===================== + + try: + loaded_wallet.config_mnemonic(**config_mnemonic_params) + except TypeError as e: + matched = re.match(r"config_mnemonic\(\) got an unexpected keyword argument '(.*)'", str(e)) + if matched: + sys.exit("{} does not support the {} option".format(loaded_wallet.__class__.__name__, matched.group(1))) + raise + except ValueError as e: + sys.exit(e) + + try: + if args.skip_worker_checksum: + loaded_wallet._skip_worker_checksum = True + else: + loaded_wallet._skip_worker_checksum = False + + loaded_wallet._savevalidseeds = False + if args.savevalidseeds: + loaded_wallet._savevalidseeds = args.savevalidseeds + if args.savevalidseeds_filesize: + if args.savevalidseeds_filesize <= 0: + print("ERROR: --savevalidseed-filesize needs to be a positive whole number") + exit() + else: + print("NOTICE: No Seed file size specified, Setting Seed Filesize to 10 million seeds per file") + args.savevalidseeds_filesize = 10000000 # 10 million checksummed seeds will produce files about 1gb each (for 24 word seeds), quite easy to work with... + + loaded_wallet._seedfilecount = args.savevalidseeds_filesize + if loaded_wallet._skip_worker_checksum: + print("WARNING: Skipping Worker Checksum is probably not what you want when using --savevalideeds argument") + + if args.multi_file_seedlist: + loaded_wallet.load_multi_file_seedlist = True + else: + loaded_wallet.load_multi_file_seedlist = False + + + ############################## + # OpenCL related arguments + ############################## + + loaded_wallet.opencl = False + loaded_wallet.opencl_algo = -1 + loaded_wallet.opencl_context_pbkdf2_sha512 = -1 + # Parse and syntax check all of the GPU related options + if args.enable_opencl: + if not module_opencl_available: + exit("\nERROR: Cannot Load pyOpenCL, see the installation guide at https://btcrecover.readthedocs.io/en/latest/GPU_Acceleration/") + + if not hasattr(loaded_wallet, "_return_verified_password_or_false_opencl"): + btcrpass.error_exit("Wallet Type: " + loaded_wallet.__class__.__name__ + " does not support OpenCL acceleration") + + loaded_wallet.opencl = True + # Append GPU related arguments to be sent to BTCrpass + extra_args.append("--enable-opencl") + + if args.force_checksum_in_generator: + print() + print("Note: Performing Seed Checksum in the Generator Step will result in inaccurate speed and password count numbers (Only seeds with valid checksum are included in the count)") + print() + loaded_wallet._checksum_in_generator = True + + # + if args.opencl_platform: + loaded_wallet.opencl_platform = args.opencl_platform[0] + loaded_wallet.opencl_device_worksize = 0 + extra_args.append("--opencl-platform") + extra_args.append(str(args.opencl_platform[0])) + for device in pyopencl.get_platforms()[args.opencl_platform[0]].get_devices(): + if device.max_work_group_size > loaded_wallet.opencl_device_worksize: + loaded_wallet.opencl_device_worksize = device.max_work_group_size + # + # Else if specific devices weren't requested, try to build a good default list + else: + btcrecover.opencl_helpers.auto_select_opencl_platform(loaded_wallet) + #print("OpenCL: Auto Selecting: ", best_device, "on Platform: ", best_platform) + + if args.opencl_devices: + loaded_wallet.opencl_devices = args.opencl_devices + loaded_wallet.opencl_devices = [int(x) for x in loaded_wallet.opencl_devices] + if max(loaded_wallet.opencl_devices) > (len(pyopencl.get_platforms()[loaded_wallet.opencl_platform].get_devices()) - 1): + print("Error: Invalid OpenCL device selected") + exit() + + loaded_wallet.opencl_algo = 0 + loaded_wallet.opencl_context_pbkdf2_sha512 = 0 + + extra_args.append("--opencl-workgroup-size") + if args.opencl_workgroup_size: + loaded_wallet.opencl_device_worksize = args.opencl_workgroup_size[0] + extra_args.append(str(args.opencl_workgroup_size[0])) + leastbad_worksize = loaded_wallet.opencl_device_worksize + else: + if args.force_checksum_in_generator or args.skip_worker_checksum: + leastbad_worksize = loaded_wallet.opencl_device_worksize + else: # If the worksize hasn't be manually specificed, come up with a sensible automatic setting which matches the device worksize with the anticipated checksum error rate + leastbad_worksize = loaded_wallet.opencl_device_worksize * 50 + if args.mnemonic_length: + mnemonic_length = args.mnemonic_length + else: + mnemonic_length = len(mnemonic_ids_guess) + if mnemonic_length == 12: + if args.wallet_type: + if args.wallet_type.lower() == "electrum2": + leastbad_worksize = int(loaded_wallet.opencl_device_worksize * 125) + else: + leastbad_worksize = int(loaded_wallet.opencl_device_worksize * 16) + if mnemonic_length == 18: + leastbad_worksize = int(loaded_wallet.opencl_device_worksize * 64) + if mnemonic_length == 24: + leastbad_worksize = int(loaded_wallet.opencl_device_worksize * 256) + extra_args.append(str(leastbad_worksize)) + + #print("OpenCL: Using Work Group Size: ", leastbad_worksize) + #print() + # + # if not --enable-opencl: sanity checks + else: + loaded_wallet.opencl = False + for argkey in "opencl_platform", "opencl_workgroup_size": + if args.__dict__[argkey] != parser.get_default(argkey): + print("Warning: --" + argkey.replace("_", "-"), "is ignored without --enable-opencl", + file=sys.stderr) + + except UnboundLocalError: pass + + + # Seeds for some wallet types have a checksum which is unlikely to be correct + # for the initial provided seed guess; if it is correct, let the user know + try: + if ( loaded_wallet._initial_words_valid + and loaded_wallet.verify_mnemonic_syntax(mnemonic_ids_guess) + and loaded_wallet._verify_checksum(mnemonic_ids_guess) ): + print(u"Initial seed guess has a valid checksum ({:.2g}% chance).".format(loaded_wallet._checksum_ratio * 100.0)) + except AttributeError: pass + + # Now that most of the GUI code is done, undo any Windows shell extension workarounds from init_gui() + if sys.platform == "win32" and tk_root: + del sys.modules["win32api"] + del sys.modules["win32com"] + # Some py2exe-compiled .dll shell extensions set sys.frozen, which should only be set + # for "frozen" py2exe .exe's; this causes problems with multiprocessing, so delete it + try: + del sys.frozen + except AttributeError: pass + + phases = build_search_phases(loaded_wallet, phase, phase_transform) + + for phase_num, phase_params in enumerate(phases, 1): + # Print Timestamp that this step occured + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": ", end="") + + # Print a friendly message describing this phase's search settings + print("Phase {}/{}: ".format(phase_num, len(phases)), end="") + if phase_params["typos"] == 1: + print("1 mistake", end="") + else: + print("up to {} mistakes".format(phase_params["typos"]), end="") + if phase_params.get("big_typos"): + if phase_params["big_typos"] == phase_params["typos"] == 1: + print(" which can be an entirely different seed word.") + else: + print(", {} of which can be an entirely different seed word.".format(phase_params["big_typos"])) + else: + print(", excluding entirely different seed words.") + + # Perform this phase's search + phase_params.setdefault("extra_args", []).extend(extra_args) + + mnemonic_found = run_btcrecover(**phase_params) + + if not listseeds: + # Print Timestamp that this step occured + print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ": Search Complete") + + if mnemonic_found: + return " ".join(loaded_wallet.id_to_word(i) for i in mnemonic_found), loaded_wallet.get_path_coin() + elif mnemonic_found is None: + return None, loaded_wallet.get_path_coin() # An error occurred or Ctrl-C was pressed inside btcrpass.main() + elif loaded_wallet._savevalidseeds: # Don't give a message that seed isn't found, that isn't relevant in this instance + pass + else: + print(" Seed not found" + ( ", sorry..." if phase_num==len(phases) else "" )) + + return False, None # No error occurred; the mnemonic wasn't found + +def show_mnemonic_gui(mnemonic_sentence, path_coin): + """may be called *after* main() to display the successful result iff the GUI is in use + + :param mnemonic_sentence: the mnemonic sentence that was found + :type mnemonic_sentence: unicode + :rtype: None + """ + assert tk_root + global pause_at_exit + padding = 6 + tk.Label(text="WARNING: seed information is sensitive, carefully protect it and do not share", fg="red") \ + .pack(padx=padding, pady=padding) + tk.Label(text="Seed found:").pack(padx=padding, pady=padding) + if isinstance(loaded_wallet, WalletSLIP39Seed): + tk.Label( + text="NOTE: SLIP39 seed recovery matches checksums, so needs to be manually verified", + fg="red", + ).pack(padx=padding, pady=padding) + elif isinstance(loaded_wallet, WalletAezeed) and getattr( + loaded_wallet, "_checksum_only_mode", False + ): + tk.Label( + text=( + "NOTE: aezeed recovery ran without address checks; verify the " + "seed on your wallet before use." + ), + fg="red", + ).pack(padx=padding, pady=padding) + + entry = tk.Entry(width=120, readonlybackground="white") + entry.insert(0, mnemonic_sentence) + entry.config(state="readonly") + entry.select_range(0, tk.END) + entry.pack(fill=tk.X, expand=True, padx=padding, pady=padding) + + tk.Label(text="If this tool helped you to recover funds, please consider donating 1% of what you recovered, in your crypto of choice to:") \ + .pack(padx=padding, pady=padding) + + donation = tk.Listbox(tk_root) + donation.insert(1, "BTC: 37N7B7sdHahCXTcMJgEnHz7YmiR4bEqCrS ") + donation.insert(2, " ") + donation.insert(3, "BCH: qpvjee5vwwsv78xc28kwgd3m9mnn5adargxd94kmrt ") + donation.insert(4, " ") + donation.insert(5, "LTC: M966MQte7agAzdCZe5ssHo7g9VriwXgyqM ") + donation.insert(6, " ") + donation.insert(7, "ETH: 0x72343f2806428dbbc2C11a83A1844912184b4243 ") + donation.insert(8, " ") + + # Selective Donation Addressess depending on path being recovered... (To avoid spamming the dialogue with shitcoins...) + # TODO: Implement this better with a dictionary mapping in seperate PY file with BTCRecover specific donation addys... (Seperate from YY Channel) + if path_coin == 28: + donation.insert(9, "VTC: vtc1qxauv20r2ux2vttrjmm9eylshl508q04uju936n ") + + if path_coin == 22: + donation.insert(9, "MONA: mona1q504vpcuyrrgr87l4cjnal74a4qazes2g9qy8mv ") + + if path_coin == 5: + donation.insert(9, "DASH: Xx2umk6tx25uCWp6XeaD5f7CyARkbemsZG ") + + if path_coin == 121: + donation.insert(9, "ZEN: znUihTHfwm5UJS1ywo911mdNEzd9WY9vBP7 ") + + if path_coin == 3: + donation.insert(9, "DOGE: DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T ") + + donation.pack(fill=tk.X, expand=True, padx=padding, pady=padding) + + tk.Label(text="Just select the address for your coin of choice and copy the address with ctrl-c") \ + .pack(padx=padding, pady=padding) + + tk.Label(text="Find me on Reddit @ https://www.reddit.com/user/Crypto-Guide") \ + .pack(padx=padding, pady=padding) + + tk.Label(text="You may also consider donating to Gurnec, who created and maintained this tool until late 2017 @ 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4") \ + .pack(padx=padding, pady=padding) + + tk_root.deiconify() + tk_root.lift() + entry.focus_set() + tk_root.mainloop() # blocks until the user closes the window + pause_at_exit = False diff --git a/btcrecover/coinomi_pb2.py b/btcrecover/coinomi_pb2.py new file mode 100644 index 000000000..3f8991c98 --- /dev/null +++ b/btcrecover/coinomi_pb2.py @@ -0,0 +1,1212 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: wallet.proto.coinomi +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='wallet.proto.coinomi', + package='com.coinomi.core.protos', + syntax='proto2', + serialized_options=b'\n\027com.coinomi.core.protosB\006Protos', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x14wallet.proto.coinomi\x12\x17\x63om.coinomi.core.protos\"A\n\x0bPeerAddress\x12\x12\n\nip_address\x18\x01 \x02(\x0c\x12\x0c\n\x04port\x18\x02 \x02(\r\x12\x10\n\x08services\x18\x03 \x02(\x04\"M\n\rEncryptedData\x12\x1d\n\x15initialisation_vector\x18\x01 \x02(\x0c\x12\x1d\n\x15\x65ncrypted_private_key\x18\x02 \x02(\x0c\"y\n\x10\x44\x65terministicKey\x12\x12\n\nchain_code\x18\x01 \x02(\x0c\x12\x0c\n\x04path\x18\x02 \x03(\r\x12\x16\n\x0eissued_subkeys\x18\x03 \x01(\r\x12\x16\n\x0elookahead_size\x18\x04 \x01(\r\x12\x13\n\x0bisFollowing\x18\x05 \x01(\x08\"\xd8\x02\n\x03Key\x12/\n\x04type\x18\x01 \x02(\x0e\x32!.com.coinomi.core.protos.Key.Type\x12\x14\n\x0csecret_bytes\x18\x02 \x01(\x0c\x12>\n\x0e\x65ncrypted_data\x18\x03 \x01(\x0b\x32&.com.coinomi.core.protos.EncryptedData\x12\x12\n\npublic_key\x18\x04 \x01(\x0c\x12\r\n\x05label\x18\x05 \x01(\t\x12\x44\n\x11\x64\x65terministic_key\x18\x06 \x01(\x0b\x32).com.coinomi.core.protos.DeterministicKey\"a\n\x04Type\x12\x0c\n\x08ORIGINAL\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\x12\x1a\n\x16\x44\x45TERMINISTIC_MNEMONIC\x10\x03\x12\x15\n\x11\x44\x45TERMINISTIC_KEY\x10\x04\"\x92\x01\n\x10TransactionInput\x12\"\n\x1atransaction_out_point_hash\x18\x01 \x02(\x0c\x12#\n\x1btransaction_out_point_index\x18\x02 \x02(\r\x12\x14\n\x0cscript_bytes\x18\x03 \x02(\x0c\x12\x10\n\x08sequence\x18\x04 \x01(\r\x12\r\n\x05value\x18\x05 \x01(\x03\"\xa7\x01\n\x11TransactionOutput\x12\r\n\x05value\x18\x01 \x02(\x03\x12\x14\n\x0cscript_bytes\x18\x02 \x02(\x0c\x12!\n\x19spent_by_transaction_hash\x18\x03 \x01(\x0c\x12\"\n\x1aspent_by_transaction_index\x18\x04 \x01(\x05\x12\x17\n\x08is_spent\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\r\n\x05index\x18\x06 \x01(\x05\"\x82\x01\n\rUnspentOutput\x12\x16\n\x0eout_point_hash\x18\x01 \x02(\x0c\x12\x17\n\x0fout_point_index\x18\x02 \x02(\r\x12\x14\n\x0cscript_bytes\x18\x03 \x02(\x0c\x12\r\n\x05value\x18\x04 \x02(\x03\x12\x1b\n\x0cis_generated\x18\x05 \x01(\x08:\x05\x66\x61lse\"\xb9\x03\n\x15TransactionConfidence\x12\x41\n\x04type\x18\x01 \x01(\x0e\x32\x33.com.coinomi.core.protos.TransactionConfidence.Type\x12\x1a\n\x12\x61ppeared_at_height\x18\x02 \x01(\x05\x12\x1e\n\x16overriding_transaction\x18\x03 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x04 \x01(\x05\x12:\n\x0c\x62roadcast_by\x18\x05 \x03(\x0b\x32$.com.coinomi.core.protos.PeerAddress\x12\x45\n\x06source\x18\x06 \x01(\x0e\x32\x35.com.coinomi.core.protos.TransactionConfidence.Source\"8\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0c\n\x08\x42UILDING\x10\x01\x12\x0b\n\x07PENDING\x10\x02\x12\x08\n\x04\x44\x45\x41\x44\x10\x03\"U\n\x06Source\x12\x12\n\x0eSOURCE_UNKNOWN\x10\x00\x12\x12\n\x0eSOURCE_NETWORK\x10\x01\x12\x0f\n\x0bSOURCE_SELF\x10\x02\x12\x12\n\x0eSOURCE_TRUSTED\x10\x03\"\xf2\x04\n\x0bTransaction\x12\x0f\n\x07version\x18\x01 \x02(\x05\x12\x0c\n\x04time\x18\x0b \x01(\x05\x12\x0c\n\x04hash\x18\x02 \x02(\x0c\x12\x37\n\x04pool\x18\x03 \x01(\x0e\x32).com.coinomi.core.protos.Transaction.Pool\x12\x11\n\tlock_time\x18\x04 \x01(\r\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x44\n\x11transaction_input\x18\x06 \x03(\x0b\x32).com.coinomi.core.protos.TransactionInput\x12\x16\n\x0enum_of_outputs\x18\x12 \x01(\x05\x12\x46\n\x12transaction_output\x18\x07 \x03(\x0b\x32*.com.coinomi.core.protos.TransactionOutput\x12\x12\n\nblock_hash\x18\x08 \x03(\x0c\x12 \n\x18\x62lock_relativity_offsets\x18\t \x03(\x05\x12\x42\n\nconfidence\x18\n \x01(\x0b\x32..com.coinomi.core.protos.TransactionConfidence\x12\x10\n\x08token_id\x18\x0c \x01(\x05\x12\x13\n\x0b\x65xtra_bytes\x18\r \x01(\x0c\x12\x19\n\nis_trimmed\x18\x0e \x01(\x08:\x05\x66\x61lse\x12\x15\n\nvalue_sent\x18\x0f \x01(\x03:\x01\x30\x12\x19\n\x0evalue_received\x18\x10 \x01(\x03:\x01\x30\x12\x0b\n\x03\x66\x65\x65\x18\x11 \x01(\x03\"5\n\x04Pool\x12\x0b\n\x07UNSPENT\x10\x04\x12\t\n\x05SPENT\x10\x05\x12\x08\n\x04\x44\x45\x41\x44\x10\n\x12\x0b\n\x07PENDING\x10\x10\"0\n\rAddressStatus\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\t\x12\x0e\n\x06status\x18\x02 \x02(\t\"\x92\x03\n\x0cWalletPocket\x12\x1a\n\x12network_identifier\x18\x01 \x02(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12)\n\x03key\x18\x03 \x03(\x0b\x32\x1c.com.coinomi.core.protos.Key\x12\x1c\n\x14last_seen_block_hash\x18\x04 \x01(\x0c\x12\x1e\n\x16last_seen_block_height\x18\x05 \x01(\r\x12!\n\x19last_seen_block_time_secs\x18\x06 \x01(\x03\x12\x39\n\x0btransaction\x18\x07 \x03(\x0b\x32$.com.coinomi.core.protos.Transaction\x12>\n\x0e\x61\x64\x64ress_status\x18\x08 \x03(\x0b\x32&.com.coinomi.core.protos.AddressStatus\x12\n\n\x02id\x18\t \x01(\t\x12>\n\x0eunspent_output\x18\n \x03(\x0b\x32&.com.coinomi.core.protos.UnspentOutput\"N\n\x10ScryptParameters\x12\x0c\n\x04salt\x18\x01 \x02(\x0c\x12\x10\n\x01n\x18\x02 \x01(\x03:\x05\x31\x36\x33\x38\x34\x12\x0c\n\x01r\x18\x03 \x01(\x05:\x01\x38\x12\x0c\n\x01p\x18\x04 \x01(\x05:\x01\x31\"\xc3\x03\n\x06Wallet\x12\x12\n\x07version\x18\x01 \x01(\x05:\x01\x31\x12*\n\x04seed\x18\x02 \x01(\x0b\x32\x1c.com.coinomi.core.protos.Key\x12\x1f\n\x17seed_password_protected\x18\x07 \x01(\x08\x12\x30\n\nmaster_key\x18\x03 \x02(\x0b\x32\x1c.com.coinomi.core.protos.Key\x12T\n\x0f\x65ncryption_type\x18\x04 \x01(\x0e\x32..com.coinomi.core.protos.Wallet.EncryptionType:\x0bUNENCRYPTED\x12H\n\x15\x65ncryption_parameters\x18\x05 \x01(\x0b\x32).com.coinomi.core.protos.ScryptParameters\x12\x36\n\x07pockets\x18\x06 \x03(\x0b\x32%.com.coinomi.core.protos.WalletPocket\"N\n\x0e\x45ncryptionType\x12\x0f\n\x0bUNENCRYPTED\x10\x01\x12\x18\n\x14\x45NCRYPTED_SCRYPT_AES\x10\x02\x12\x11\n\rENCRYPTED_AES\x10\x03\x42!\n\x17\x63om.coinomi.core.protosB\x06Protos' +) + + + +_KEY_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='com.coinomi.core.protos.Key.Type', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='ORIGINAL', index=0, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENCRYPTED_SCRYPT_AES', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DETERMINISTIC_MNEMONIC', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DETERMINISTIC_KEY', index=3, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=566, + serialized_end=663, +) +_sym_db.RegisterEnumDescriptor(_KEY_TYPE) + +_TRANSACTIONCONFIDENCE_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='com.coinomi.core.protos.TransactionConfidence.Type', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='BUILDING', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PENDING', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DEAD', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1416, + serialized_end=1472, +) +_sym_db.RegisterEnumDescriptor(_TRANSACTIONCONFIDENCE_TYPE) + +_TRANSACTIONCONFIDENCE_SOURCE = _descriptor.EnumDescriptor( + name='Source', + full_name='com.coinomi.core.protos.TransactionConfidence.Source', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SOURCE_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SOURCE_NETWORK', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SOURCE_SELF', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SOURCE_TRUSTED', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1474, + serialized_end=1559, +) +_sym_db.RegisterEnumDescriptor(_TRANSACTIONCONFIDENCE_SOURCE) + +_TRANSACTION_POOL = _descriptor.EnumDescriptor( + name='Pool', + full_name='com.coinomi.core.protos.Transaction.Pool', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UNSPENT', index=0, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SPENT', index=1, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DEAD', index=2, number=10, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PENDING', index=3, number=16, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=2135, + serialized_end=2188, +) +_sym_db.RegisterEnumDescriptor(_TRANSACTION_POOL) + +_WALLET_ENCRYPTIONTYPE = _descriptor.EnumDescriptor( + name='EncryptionType', + full_name='com.coinomi.core.protos.Wallet.EncryptionType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='UNENCRYPTED', index=0, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENCRYPTED_SCRYPT_AES', index=1, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ENCRYPTED_AES', index=2, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=3099, + serialized_end=3177, +) +_sym_db.RegisterEnumDescriptor(_WALLET_ENCRYPTIONTYPE) + + +_PEERADDRESS = _descriptor.Descriptor( + name='PeerAddress', + full_name='com.coinomi.core.protos.PeerAddress', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='ip_address', full_name='com.coinomi.core.protos.PeerAddress.ip_address', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='port', full_name='com.coinomi.core.protos.PeerAddress.port', index=1, + number=2, type=13, cpp_type=3, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='services', full_name='com.coinomi.core.protos.PeerAddress.services', index=2, + number=3, type=4, cpp_type=4, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=49, + serialized_end=114, +) + + +_ENCRYPTEDDATA = _descriptor.Descriptor( + name='EncryptedData', + full_name='com.coinomi.core.protos.EncryptedData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='initialisation_vector', full_name='com.coinomi.core.protos.EncryptedData.initialisation_vector', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='encrypted_private_key', full_name='com.coinomi.core.protos.EncryptedData.encrypted_private_key', index=1, + number=2, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=116, + serialized_end=193, +) + + +_DETERMINISTICKEY = _descriptor.Descriptor( + name='DeterministicKey', + full_name='com.coinomi.core.protos.DeterministicKey', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='chain_code', full_name='com.coinomi.core.protos.DeterministicKey.chain_code', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='path', full_name='com.coinomi.core.protos.DeterministicKey.path', index=1, + number=2, type=13, cpp_type=3, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='issued_subkeys', full_name='com.coinomi.core.protos.DeterministicKey.issued_subkeys', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='lookahead_size', full_name='com.coinomi.core.protos.DeterministicKey.lookahead_size', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='isFollowing', full_name='com.coinomi.core.protos.DeterministicKey.isFollowing', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=195, + serialized_end=316, +) + + +_KEY = _descriptor.Descriptor( + name='Key', + full_name='com.coinomi.core.protos.Key', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='com.coinomi.core.protos.Key.type', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='secret_bytes', full_name='com.coinomi.core.protos.Key.secret_bytes', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='encrypted_data', full_name='com.coinomi.core.protos.Key.encrypted_data', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='public_key', full_name='com.coinomi.core.protos.Key.public_key', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='label', full_name='com.coinomi.core.protos.Key.label', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='deterministic_key', full_name='com.coinomi.core.protos.Key.deterministic_key', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _KEY_TYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=319, + serialized_end=663, +) + + +_TRANSACTIONINPUT = _descriptor.Descriptor( + name='TransactionInput', + full_name='com.coinomi.core.protos.TransactionInput', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='transaction_out_point_hash', full_name='com.coinomi.core.protos.TransactionInput.transaction_out_point_hash', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='transaction_out_point_index', full_name='com.coinomi.core.protos.TransactionInput.transaction_out_point_index', index=1, + number=2, type=13, cpp_type=3, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='script_bytes', full_name='com.coinomi.core.protos.TransactionInput.script_bytes', index=2, + number=3, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sequence', full_name='com.coinomi.core.protos.TransactionInput.sequence', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='com.coinomi.core.protos.TransactionInput.value', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=666, + serialized_end=812, +) + + +_TRANSACTIONOUTPUT = _descriptor.Descriptor( + name='TransactionOutput', + full_name='com.coinomi.core.protos.TransactionOutput', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='com.coinomi.core.protos.TransactionOutput.value', index=0, + number=1, type=3, cpp_type=2, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='script_bytes', full_name='com.coinomi.core.protos.TransactionOutput.script_bytes', index=1, + number=2, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='spent_by_transaction_hash', full_name='com.coinomi.core.protos.TransactionOutput.spent_by_transaction_hash', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='spent_by_transaction_index', full_name='com.coinomi.core.protos.TransactionOutput.spent_by_transaction_index', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='is_spent', full_name='com.coinomi.core.protos.TransactionOutput.is_spent', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='index', full_name='com.coinomi.core.protos.TransactionOutput.index', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=815, + serialized_end=982, +) + + +_UNSPENTOUTPUT = _descriptor.Descriptor( + name='UnspentOutput', + full_name='com.coinomi.core.protos.UnspentOutput', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='out_point_hash', full_name='com.coinomi.core.protos.UnspentOutput.out_point_hash', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='out_point_index', full_name='com.coinomi.core.protos.UnspentOutput.out_point_index', index=1, + number=2, type=13, cpp_type=3, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='script_bytes', full_name='com.coinomi.core.protos.UnspentOutput.script_bytes', index=2, + number=3, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='com.coinomi.core.protos.UnspentOutput.value', index=3, + number=4, type=3, cpp_type=2, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='is_generated', full_name='com.coinomi.core.protos.UnspentOutput.is_generated', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=985, + serialized_end=1115, +) + + +_TRANSACTIONCONFIDENCE = _descriptor.Descriptor( + name='TransactionConfidence', + full_name='com.coinomi.core.protos.TransactionConfidence', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='com.coinomi.core.protos.TransactionConfidence.type', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='appeared_at_height', full_name='com.coinomi.core.protos.TransactionConfidence.appeared_at_height', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='overriding_transaction', full_name='com.coinomi.core.protos.TransactionConfidence.overriding_transaction', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='depth', full_name='com.coinomi.core.protos.TransactionConfidence.depth', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='broadcast_by', full_name='com.coinomi.core.protos.TransactionConfidence.broadcast_by', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='source', full_name='com.coinomi.core.protos.TransactionConfidence.source', index=5, + number=6, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _TRANSACTIONCONFIDENCE_TYPE, + _TRANSACTIONCONFIDENCE_SOURCE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1118, + serialized_end=1559, +) + + +_TRANSACTION = _descriptor.Descriptor( + name='Transaction', + full_name='com.coinomi.core.protos.Transaction', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='version', full_name='com.coinomi.core.protos.Transaction.version', index=0, + number=1, type=5, cpp_type=1, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time', full_name='com.coinomi.core.protos.Transaction.time', index=1, + number=11, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='hash', full_name='com.coinomi.core.protos.Transaction.hash', index=2, + number=2, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='pool', full_name='com.coinomi.core.protos.Transaction.pool', index=3, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=4, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='lock_time', full_name='com.coinomi.core.protos.Transaction.lock_time', index=4, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='updated_at', full_name='com.coinomi.core.protos.Transaction.updated_at', index=5, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='transaction_input', full_name='com.coinomi.core.protos.Transaction.transaction_input', index=6, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='num_of_outputs', full_name='com.coinomi.core.protos.Transaction.num_of_outputs', index=7, + number=18, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='transaction_output', full_name='com.coinomi.core.protos.Transaction.transaction_output', index=8, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='block_hash', full_name='com.coinomi.core.protos.Transaction.block_hash', index=9, + number=8, type=12, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='block_relativity_offsets', full_name='com.coinomi.core.protos.Transaction.block_relativity_offsets', index=10, + number=9, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='confidence', full_name='com.coinomi.core.protos.Transaction.confidence', index=11, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='token_id', full_name='com.coinomi.core.protos.Transaction.token_id', index=12, + number=12, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='extra_bytes', full_name='com.coinomi.core.protos.Transaction.extra_bytes', index=13, + number=13, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='is_trimmed', full_name='com.coinomi.core.protos.Transaction.is_trimmed', index=14, + number=14, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value_sent', full_name='com.coinomi.core.protos.Transaction.value_sent', index=15, + number=15, type=3, cpp_type=2, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value_received', full_name='com.coinomi.core.protos.Transaction.value_received', index=16, + number=16, type=3, cpp_type=2, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='fee', full_name='com.coinomi.core.protos.Transaction.fee', index=17, + number=17, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _TRANSACTION_POOL, + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1562, + serialized_end=2188, +) + + +_ADDRESSSTATUS = _descriptor.Descriptor( + name='AddressStatus', + full_name='com.coinomi.core.protos.AddressStatus', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='address', full_name='com.coinomi.core.protos.AddressStatus.address', index=0, + number=1, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='status', full_name='com.coinomi.core.protos.AddressStatus.status', index=1, + number=2, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2190, + serialized_end=2238, +) + + +_WALLETPOCKET = _descriptor.Descriptor( + name='WalletPocket', + full_name='com.coinomi.core.protos.WalletPocket', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='network_identifier', full_name='com.coinomi.core.protos.WalletPocket.network_identifier', index=0, + number=1, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='description', full_name='com.coinomi.core.protos.WalletPocket.description', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='key', full_name='com.coinomi.core.protos.WalletPocket.key', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='last_seen_block_hash', full_name='com.coinomi.core.protos.WalletPocket.last_seen_block_hash', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='last_seen_block_height', full_name='com.coinomi.core.protos.WalletPocket.last_seen_block_height', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='last_seen_block_time_secs', full_name='com.coinomi.core.protos.WalletPocket.last_seen_block_time_secs', index=5, + number=6, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='transaction', full_name='com.coinomi.core.protos.WalletPocket.transaction', index=6, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='address_status', full_name='com.coinomi.core.protos.WalletPocket.address_status', index=7, + number=8, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='id', full_name='com.coinomi.core.protos.WalletPocket.id', index=8, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='unspent_output', full_name='com.coinomi.core.protos.WalletPocket.unspent_output', index=9, + number=10, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2241, + serialized_end=2643, +) + + +_SCRYPTPARAMETERS = _descriptor.Descriptor( + name='ScryptParameters', + full_name='com.coinomi.core.protos.ScryptParameters', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='salt', full_name='com.coinomi.core.protos.ScryptParameters.salt', index=0, + number=1, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='n', full_name='com.coinomi.core.protos.ScryptParameters.n', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=True, default_value=16384, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='r', full_name='com.coinomi.core.protos.ScryptParameters.r', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=8, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='p', full_name='com.coinomi.core.protos.ScryptParameters.p', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2645, + serialized_end=2723, +) + + +_WALLET = _descriptor.Descriptor( + name='Wallet', + full_name='com.coinomi.core.protos.Wallet', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='version', full_name='com.coinomi.core.protos.Wallet.version', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='seed', full_name='com.coinomi.core.protos.Wallet.seed', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='seed_password_protected', full_name='com.coinomi.core.protos.Wallet.seed_password_protected', index=2, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='master_key', full_name='com.coinomi.core.protos.Wallet.master_key', index=3, + number=3, type=11, cpp_type=10, label=2, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='encryption_type', full_name='com.coinomi.core.protos.Wallet.encryption_type', index=4, + number=4, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=1, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='encryption_parameters', full_name='com.coinomi.core.protos.Wallet.encryption_parameters', index=5, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='pockets', full_name='com.coinomi.core.protos.Wallet.pockets', index=6, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _WALLET_ENCRYPTIONTYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2726, + serialized_end=3177, +) + +_KEY.fields_by_name['type'].enum_type = _KEY_TYPE +_KEY.fields_by_name['encrypted_data'].message_type = _ENCRYPTEDDATA +_KEY.fields_by_name['deterministic_key'].message_type = _DETERMINISTICKEY +_KEY_TYPE.containing_type = _KEY +_TRANSACTIONCONFIDENCE.fields_by_name['type'].enum_type = _TRANSACTIONCONFIDENCE_TYPE +_TRANSACTIONCONFIDENCE.fields_by_name['broadcast_by'].message_type = _PEERADDRESS +_TRANSACTIONCONFIDENCE.fields_by_name['source'].enum_type = _TRANSACTIONCONFIDENCE_SOURCE +_TRANSACTIONCONFIDENCE_TYPE.containing_type = _TRANSACTIONCONFIDENCE +_TRANSACTIONCONFIDENCE_SOURCE.containing_type = _TRANSACTIONCONFIDENCE +_TRANSACTION.fields_by_name['pool'].enum_type = _TRANSACTION_POOL +_TRANSACTION.fields_by_name['transaction_input'].message_type = _TRANSACTIONINPUT +_TRANSACTION.fields_by_name['transaction_output'].message_type = _TRANSACTIONOUTPUT +_TRANSACTION.fields_by_name['confidence'].message_type = _TRANSACTIONCONFIDENCE +_TRANSACTION_POOL.containing_type = _TRANSACTION +_WALLETPOCKET.fields_by_name['key'].message_type = _KEY +_WALLETPOCKET.fields_by_name['transaction'].message_type = _TRANSACTION +_WALLETPOCKET.fields_by_name['address_status'].message_type = _ADDRESSSTATUS +_WALLETPOCKET.fields_by_name['unspent_output'].message_type = _UNSPENTOUTPUT +_WALLET.fields_by_name['seed'].message_type = _KEY +_WALLET.fields_by_name['master_key'].message_type = _KEY +_WALLET.fields_by_name['encryption_type'].enum_type = _WALLET_ENCRYPTIONTYPE +_WALLET.fields_by_name['encryption_parameters'].message_type = _SCRYPTPARAMETERS +_WALLET.fields_by_name['pockets'].message_type = _WALLETPOCKET +_WALLET_ENCRYPTIONTYPE.containing_type = _WALLET +DESCRIPTOR.message_types_by_name['PeerAddress'] = _PEERADDRESS +DESCRIPTOR.message_types_by_name['EncryptedData'] = _ENCRYPTEDDATA +DESCRIPTOR.message_types_by_name['DeterministicKey'] = _DETERMINISTICKEY +DESCRIPTOR.message_types_by_name['Key'] = _KEY +DESCRIPTOR.message_types_by_name['TransactionInput'] = _TRANSACTIONINPUT +DESCRIPTOR.message_types_by_name['TransactionOutput'] = _TRANSACTIONOUTPUT +DESCRIPTOR.message_types_by_name['UnspentOutput'] = _UNSPENTOUTPUT +DESCRIPTOR.message_types_by_name['TransactionConfidence'] = _TRANSACTIONCONFIDENCE +DESCRIPTOR.message_types_by_name['Transaction'] = _TRANSACTION +DESCRIPTOR.message_types_by_name['AddressStatus'] = _ADDRESSSTATUS +DESCRIPTOR.message_types_by_name['WalletPocket'] = _WALLETPOCKET +DESCRIPTOR.message_types_by_name['ScryptParameters'] = _SCRYPTPARAMETERS +DESCRIPTOR.message_types_by_name['Wallet'] = _WALLET +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PeerAddress = _reflection.GeneratedProtocolMessageType('PeerAddress', (_message.Message,), { + 'DESCRIPTOR' : _PEERADDRESS, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.PeerAddress) + }) +_sym_db.RegisterMessage(PeerAddress) + +EncryptedData = _reflection.GeneratedProtocolMessageType('EncryptedData', (_message.Message,), { + 'DESCRIPTOR' : _ENCRYPTEDDATA, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.EncryptedData) + }) +_sym_db.RegisterMessage(EncryptedData) + +DeterministicKey = _reflection.GeneratedProtocolMessageType('DeterministicKey', (_message.Message,), { + 'DESCRIPTOR' : _DETERMINISTICKEY, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.DeterministicKey) + }) +_sym_db.RegisterMessage(DeterministicKey) + +Key = _reflection.GeneratedProtocolMessageType('Key', (_message.Message,), { + 'DESCRIPTOR' : _KEY, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.Key) + }) +_sym_db.RegisterMessage(Key) + +TransactionInput = _reflection.GeneratedProtocolMessageType('TransactionInput', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONINPUT, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.TransactionInput) + }) +_sym_db.RegisterMessage(TransactionInput) + +TransactionOutput = _reflection.GeneratedProtocolMessageType('TransactionOutput', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONOUTPUT, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.TransactionOutput) + }) +_sym_db.RegisterMessage(TransactionOutput) + +UnspentOutput = _reflection.GeneratedProtocolMessageType('UnspentOutput', (_message.Message,), { + 'DESCRIPTOR' : _UNSPENTOUTPUT, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.UnspentOutput) + }) +_sym_db.RegisterMessage(UnspentOutput) + +TransactionConfidence = _reflection.GeneratedProtocolMessageType('TransactionConfidence', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTIONCONFIDENCE, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.TransactionConfidence) + }) +_sym_db.RegisterMessage(TransactionConfidence) + +Transaction = _reflection.GeneratedProtocolMessageType('Transaction', (_message.Message,), { + 'DESCRIPTOR' : _TRANSACTION, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.Transaction) + }) +_sym_db.RegisterMessage(Transaction) + +AddressStatus = _reflection.GeneratedProtocolMessageType('AddressStatus', (_message.Message,), { + 'DESCRIPTOR' : _ADDRESSSTATUS, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.AddressStatus) + }) +_sym_db.RegisterMessage(AddressStatus) + +WalletPocket = _reflection.GeneratedProtocolMessageType('WalletPocket', (_message.Message,), { + 'DESCRIPTOR' : _WALLETPOCKET, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.WalletPocket) + }) +_sym_db.RegisterMessage(WalletPocket) + +ScryptParameters = _reflection.GeneratedProtocolMessageType('ScryptParameters', (_message.Message,), { + 'DESCRIPTOR' : _SCRYPTPARAMETERS, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.ScryptParameters) + }) +_sym_db.RegisterMessage(ScryptParameters) + +Wallet = _reflection.GeneratedProtocolMessageType('Wallet', (_message.Message,), { + 'DESCRIPTOR' : _WALLET, + '__module__' : 'wallet.proto.coinomi_pb2' + # @@protoc_insertion_point(class_scope:com.coinomi.core.protos.Wallet) + }) +_sym_db.RegisterMessage(Wallet) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/btcrecover/sha512-bc-kernel.cl b/btcrecover/opencl/sha512-bc-kernel.cl similarity index 77% rename from btcrecover/sha512-bc-kernel.cl rename to btcrecover/opencl/sha512-bc-kernel.cl index c179c2ff6..50f465ad3 100644 --- a/btcrecover/sha512-bc-kernel.cl +++ b/btcrecover/opencl/sha512-bc-kernel.cl @@ -70,28 +70,36 @@ typedef uint uint32_t; typedef ulong uint64_t; -// TODO: I've no recent NVIDIA hardware to test this, so it's disabled on NVIDIA for safety -//#if !gpu_nvidia(DEVICE_INFO) || SM_MAJOR >= 5 -#if !gpu_nvidia(DEVICE_INFO) -#define USE_BITSELECT 1 +// TODO: previously disabled for NVIDIA because older cards and drivers +// lacked proper support. We now enable the optimized path for +// NVIDIA GPUs with compute capability (SM) >= 5 and reasonably +// recent drivers, while keeping the fallback for other devices. +#if gpu_nvidia(DEVICE_INFO) +# if SM_MAJOR >= 5 +# define USE_BITSELECT 1 +# endif +#else +# define USE_BITSELECT 1 #endif #if cpu(DEVICE_INFO) #define HAVE_ANDNOT 1 #endif -// TODO: I've no recent NVIDIA hardware to test this, so it's disabled for safety -//#if SM_MAJOR >= 5 && (DEV_VER_MAJOR > 352 || (DEV_VER_MAJOR == 352 && DEV_VER_MINOR >= 21)) -//#define HAVE_LUT3 1 -//inline uint lut3(uint a, uint b, uint c, uint imm) -//{ -// uint r; -// asm("lop3.b32 %0, %1, %2, %3, %4;" -// : "=r" (r) -// : "r" (a), "r" (b), "r" (c), "i" (imm)); -// return r; -//} -//#endif +// TODO: this LUT3-based variant uses the LOP3 instruction. Enable it +// for NVIDIA devices with SM >= 5 and driver version 352.21 or newer. +#if gpu_nvidia(DEVICE_INFO) && SM_MAJOR >= 5 && \ + (DEV_VER_MAJOR > 352 || (DEV_VER_MAJOR == 352 && DEV_VER_MINOR >= 21)) +#define HAVE_LUT3 1 +inline uint lut3(uint a, uint b, uint c, uint imm) +{ + uint r; + asm("lop3.b32 %0, %1, %2, %3, %4;" + : "=r" (r) + : "r" (a), "r" (b), "r" (c), "i" (imm)); + return r; +} +#endif #if USE_BITSELECT #define SWAP64(n) bitselect( \ @@ -196,7 +204,7 @@ void kernel_sha512_bc(__global uint64_t* hashes_buffer, // Copy initial hash into local input variable and convert endianness #pragma unroll for (int i = 0; i < 8; i++) - w[i] = SWAP64(hashes_buffer[i]); + w[i] = SWAP64(hashes_buffer[i]); // Assumes original input length was 64 bytes; add padding to it w[8] = 0x8000000000000000UL; // The appended "1" bit @@ -208,62 +216,62 @@ void kernel_sha512_bc(__global uint64_t* hashes_buffer, // Do a complete SHA512 hash for each requested iteration for (size_t iter_count = 0; iter_count < iterations; iter_count++) { - a = H0; - b = H1; - c = H2; - d = H3; - e = H4; - f = H5; - g = H6; - h = H7; - - #pragma unroll - for (int i = 0; i < 16; i++) { - t = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - - h = g; - g = f; - f = e; - e = d + t; - t = t + Maj(a, b, c) + Sigma0(a); - d = c; - c = b; - b = a; - a = t; - } - - #pragma unroll - for (int i = 16; i < 80; i++) { - w[i & 15] = sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i - 16) & 15] + w[(i - 7) & 15]; - t = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - - h = g; - g = f; - f = e; - e = d + t; - t = t + Maj(a, b, c) + Sigma0(a); - d = c; - c = b; - b = a; - a = t; - } - - // Copy resulting SHA512 hash back into the local input variable - w[0] = a + H0; - w[1] = b + H1; - w[2] = c + H2; - w[3] = d + H3; - w[4] = e + H4; - w[5] = f + H5; - w[6] = g + H6; - w[7] = h + H7; - - // SHA512 output length is always 64 bytes; add padding to it - w[8] = 0x8000000000000000UL; // The appended "1" bit - #pragma unroll - for (int i = 9; i < 15; i++) - w[i] = 0; - w[15] = 512; // The length in bits + a = H0; + b = H1; + c = H2; + d = H3; + e = H4; + f = H5; + g = H6; + h = H7; + + #pragma unroll + for (int i = 0; i < 16; i++) { + t = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); + + h = g; + g = f; + f = e; + e = d + t; + t = t + Maj(a, b, c) + Sigma0(a); + d = c; + c = b; + b = a; + a = t; + } + + #pragma unroll + for (int i = 16; i < 80; i++) { + w[i & 15] = sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i - 16) & 15] + w[(i - 7) & 15]; + t = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); + + h = g; + g = f; + f = e; + e = d + t; + t = t + Maj(a, b, c) + Sigma0(a); + d = c; + c = b; + b = a; + a = t; + } + + // Copy resulting SHA512 hash back into the local input variable + w[0] = a + H0; + w[1] = b + H1; + w[2] = c + H2; + w[3] = d + H3; + w[4] = e + H4; + w[5] = f + H5; + w[6] = g + H6; + w[7] = h + H7; + + // SHA512 output length is always 64 bytes; add padding to it + w[8] = 0x8000000000000000UL; // The appended "1" bit + #pragma unroll + for (int i = 9; i < 15; i++) + w[i] = 0; + w[15] = 512; // The length in bits } // Copy iterated SHA512 hash into the I/O buffer and convert endianness diff --git a/btcrecover/opencl_helpers.py b/btcrecover/opencl_helpers.py new file mode 100644 index 000000000..18075a12f --- /dev/null +++ b/btcrecover/opencl_helpers.py @@ -0,0 +1,224 @@ +# A collection of OpenCL helper functions that are common across both btcrpass and btcrseed (to avoid duplciation) +# btcrpass.py -- btcrecover main library +# Copyright (C) 2019-2021 Stephen Rothery +# +# This file is part of btcrecover. +# +# btcrecover is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# btcrecover is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +try: + from lib.opencl_brute import opencl + from lib.opencl_brute.opencl_information import opencl_information + import pyopencl +except: + pass + +import btcrecover.btcrpass + + +def auto_select_opencl_platform(loaded_wallet): + best_device_worksize = 0 + best_score_sofar = -1 + for i, platformNum in enumerate(pyopencl.get_platforms()): + for device in platformNum.get_devices(): + cur_score = 0 + if device.type & pyopencl.device_type.ACCELERATOR: # always best + if ( + "oclgrind" not in device.name.lower() + ): # Some simulators present as an accelerator... + cur_score += 8 + elif device.type & pyopencl.device_type.GPU: # better than CPU + cur_score += 4 + if "nvidia" in device.vendor.lower(): # is never an IGP: very good + cur_score += 2 + elif "amd" in device.vendor.lower(): # sometimes an IGP: good + cur_score += 1 + if cur_score >= best_score_sofar: # (intel is always an IGP) + if cur_score > best_score_sofar: + best_score_sofar = cur_score + best_device = device.name + best_platform = i + if device.max_work_group_size > best_device_worksize: + best_device_worksize = device.max_work_group_size + + loaded_wallet.opencl_platform = best_platform + loaded_wallet.opencl_device_worksize = best_device_worksize + print("OpenCL: Auto Selecting Best Platform") + + +def init_opencl_contexts(loaded_wallet, openclDevice=0): + dklen = 64 + platform = loaded_wallet.opencl_platform + debug = 0 + write_combined_file = False + + # Create three different objects to use for the contexts, this is required for things like warpwallet, as you can't + # have a single opencl_algo object that is re-used for multiple contexts that use a different algo. + wg = getattr(loaded_wallet, "chunksize", None) + loaded_wallet.opencl_algo = opencl.opencl_algos( + platform, + debug, + write_combined_file, + inv_memory_density=1, + openclDevice=openclDevice, + workgroupsize=wg, + ) + loaded_wallet.opencl_algo_2 = opencl.opencl_algos( + platform, + debug, + write_combined_file, + inv_memory_density=1, + openclDevice=openclDevice, + workgroupsize=wg, + ) + loaded_wallet.opencl_algo_3 = opencl.opencl_algos( + platform, + debug, + write_combined_file, + inv_memory_density=1, + openclDevice=openclDevice, + workgroupsize=wg, + ) + + # Password recovery for blockchain.com wallet + if type(loaded_wallet) is btcrecover.btcrpass.WalletBlockchain: + loaded_wallet.opencl_context_pbkdf2_sha1 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_init( + "sha1", len(loaded_wallet._salt_and_iv), 32 + ) + ) + return + + # Password Recovery for MultiBit Wallets + elif type(loaded_wallet) is btcrecover.btcrpass.WalletMultiBit: + loaded_wallet.opencl_context_multibit_md5 = loaded_wallet.opencl_algo.cl_multibit_md5_init( + len(loaded_wallet._salt)) + return + + # Password recovery for blockchain.com wallet second password + elif type(loaded_wallet) is btcrecover.btcrpass.WalletBlockchainSecondpass: + loaded_wallet.opencl_context_hash_iterations_sha256 = ( + loaded_wallet.opencl_algo.cl_hash_iterations_init("sha256") + ) + return + + # Password recovery for dogechain.info wallet + elif type(loaded_wallet) is btcrecover.btcrpass.WalletDogechain: + loaded_wallet.opencl_context_pbkdf2_sha256 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_init( + "sha256", len(loaded_wallet.salt), 32 + ) + ) + return + + # Password recovery for Yoroi Cardano wallet + elif type(loaded_wallet) is btcrecover.btcrpass.WalletYoroi: + loaded_wallet.opencl_context_pbkdf2_sha512 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_init("sha512", 32, 32) + ) + return + + # Password recovery for Bitcoin core wallet (or clones) + elif type(loaded_wallet) is btcrecover.btcrpass.WalletBitcoinCore: + loaded_wallet.opencl_context_hash_iterations_sha512 = ( + loaded_wallet.opencl_algo.cl_hash_iterations_init("sha512") + ) + return + + # Password recovery for BIP38 wallet, load sCrypt context with a custom kernel + elif type(loaded_wallet) is btcrecover.btcrpass.WalletBIP38: + loaded_wallet.opencl_context_scrypt = loaded_wallet.opencl_algo.cl_scrypt_init( + 14, "sCrypt_Bip38fork.cl" + ) + return + + # Password recovery for Electrum28 Wallet files + elif type(loaded_wallet) is btcrecover.btcrpass.WalletElectrum28: + loaded_wallet.opencl_context_pbkdf2_sha512 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_init("sha512", len(b""), dklen) + ) + return + + # Password recovery for BIP39 Passphrase or Electrum "Extra Words" + elif type(loaded_wallet) is btcrecover.btcrpass.WalletBIP39: + loaded_wallet.opencl_context_pbkdf2_sha512 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_saltlist_init( + "sha512", len(loaded_wallet._mnemonic.encode()), dklen + ) + ) + return + + # Password recovery for brainwallets + elif type(loaded_wallet) is btcrecover.btcrpass.WalletBrainwallet: + loaded_wallet.opencl_context_sha256 = loaded_wallet.opencl_algo.cl_sha256_init() + if loaded_wallet.isWarpwallet: + # loaded_wallet.opencl_context_scrypt = loaded_wallet.opencl_algo_2.cl_scrypt_init(18, "sCrypt_Bip38forkN18.cl") + loaded_wallet.opencl_context_pbkdf2_sha256 = ( + loaded_wallet.opencl_algo_3.cl_pbkdf2_init( + rtype="sha256", saltlen=len(loaded_wallet.salt) + 1, dklen=32 + ) + ) + return + + # Password recovery for Metamask wallets + elif type(loaded_wallet) is btcrecover.btcrpass.WalletMetamask: + if not loaded_wallet._mobileWallet: + loaded_wallet.opencl_context_pbkdf2_sha256 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_saltlist_init( + "sha256", len(b""), 32 + ) + ) + else: + loaded_wallet.opencl_context_pbkdf2_sha512 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_saltlist_init( + "sha512", len(b""), 32 + ) + ) + return + + # Seed Recovery for Cardano Wallets + elif type(loaded_wallet) is btcrecover.btcrseed.WalletCardano: + loaded_wallet.opencl_context_pbkdf2_sha512_saltlist = ( + loaded_wallet.opencl_algo.cl_pbkdf2_saltlist_init("sha512", 64, dklen=96) + ) + loaded_wallet.opencl_context_pbkdf2_sha512 = [] + for salt in loaded_wallet._derivation_salts: + loaded_wallet.opencl_context_pbkdf2_sha512.append( + loaded_wallet.opencl_algo.cl_pbkdf2_init( + "sha512", len(b"mnemonic" + salt), dklen + ) + ) + return + + # Passphrase Recovery for Cardano Wallets + elif type(loaded_wallet) is btcrecover.btcrpass.WalletCardano: + loaded_wallet.opencl_context_pbkdf2_sha512_saltlist = ( + loaded_wallet.opencl_algo.cl_pbkdf2_saltlist_init( + "sha512", len(loaded_wallet._mnemonic.encode()), dklen=64 + ) + ) + loaded_wallet.opencl_context_pbkdf2_sha512 = ( + loaded_wallet.opencl_algo.cl_pbkdf2_init("sha512", 33, dklen=96) + ) + return + + else: # Must a btcrseed.WalletBIP39 (Seed recovery for BIP39 or Electrum) + loaded_wallet.opencl_context_pbkdf2_sha512 = [] + for salt in loaded_wallet._derivation_salts: + loaded_wallet.opencl_context_pbkdf2_sha512.append( + loaded_wallet.opencl_algo.cl_pbkdf2_init( + "sha512", len(b"mnemonic" + salt), dklen + ) + ) + return diff --git a/btcrecover/romix-ar-kernel.cl b/btcrecover/romix-ar-kernel.cl deleted file mode 100644 index 3615af17c..000000000 --- a/btcrecover/romix-ar-kernel.cl +++ /dev/null @@ -1,473 +0,0 @@ -/* romix-ar-kernel.cl -- OpenCL implementation of Armory KDF - * Copyright (C) 2014, 2015 Christopher Gurnee - * - * This file is part of btcrecover. - * - * btcrecover is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * btcrecover is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - - * If you find this program helpful, please consider a small - * donation to the developer at the following Bitcoin address: - * - * 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 - * - * Thank You! - - * This is pretty much ROMix-SHA512 as specified in the original scrypt - * proposal at http://www.tarsnap.com/scrypt/scrypt.pdf (ROMix using SHA512 - * as the hash function instead of the recommended BlockMix-Salsa20/8), - * however it includes a few deviations from "standard" ROMix to match - * Armory's implementation. It also supports an optional space-time tradeoff - * to permit larger global work sizes despite the high memory requirements. - - * The SHA512 portions of this code are attributed to the authors of the - * JohnTheRipper software package, and in particular to Claudio André who - * developed the core OpenCL SHA512 functionality. It was pieced together - * from files downloaded from the JohnTheRipper source repository at the - * link below, and then lightly modified to suit the purposes of this - * software package, btcrecover. The original copyright notice attached - * to the SHA512 code is below. - - * https://github.com/magnumripper/JohnTheRipper/tree/bleeding-jumbo/src/opencl - - * Developed by Claudio André in 2012 - * - * Copyright (c) 2012-2015 Claudio André - * This program comes with ABSOLUTELY NO WARRANTY; express or implied. - * - * This is free software, and you are welcome to redistribute it - * under certain conditions; as expressed here - * http://www.gnu.org/licenses/gpl-2.0.html - */ - - -// From opencl_device_info.h - -//Copied from common-opencl.h -#define DEV_UNKNOWN 0 //0 -#define DEV_CPU (1 << 0) //1 -#define DEV_GPU (1 << 1) //2 -#define DEV_ACCELERATOR (1 << 2) //4 -#define DEV_AMD (1 << 3) //8 -#define DEV_NVIDIA (1 << 4) //16 -#define DEV_INTEL (1 << 5) //32 -#define PLATFORM_APPLE (1 << 6) //64 -#define DEV_AMD_GCN_10 (1 << 7) //128 -#define DEV_AMD_GCN_11 (1 << 8) //256 -#define DEV_AMD_GCN_12 (1 << 9) //512 -#define DEV_AMD_VLIW4 (1 << 12) //4096 -#define DEV_AMD_VLIW5 (1 << 13) //8192 -#define DEV_NV_C2X (1 << 14) //16384 -#define DEV_NV_C30 (1 << 15) //32768 -#define DEV_NV_C32 (1 << 16) //65536 -#define DEV_NV_C35 (1 << 17) //131072 -#define DEV_NV_C5X (1 << 18) //262144 -#define DEV_USE_LOCAL (1 << 20) //1048576 -#define DEV_NO_BYTE_ADDRESSABLE (1 << 21) //2097152 -#define DEV_MESA (1 << 22) //4M - -#define cpu(n) ((n & DEV_CPU) == (DEV_CPU)) -#define gpu(n) ((n & DEV_GPU) == (DEV_GPU)) -#define gpu_amd(n) ((n & DEV_AMD) && gpu(n)) -#define gpu_nvidia(n) ((n & DEV_NVIDIA) && gpu(n)) -#define gpu_intel(n) ((n & DEV_INTEL) && gpu(n)) -#define cpu_amd(n) ((n & DEV_AMD) && cpu(n)) -#define cpu_intel(n) ((n & DEV_INTEL) && cpu(n)) -#define amd_gcn_10(n) ((n & DEV_AMD_GCN_10) && gpu_amd(n)) -#define amd_gcn_11(n) ((n & DEV_AMD_GCN_11) && gpu_amd(n)) -#define amd_gcn_12(n) ((n & DEV_AMD_GCN_12) && gpu_amd(n)) -#define amd_gcn(n) (amd_gcn_10(n) || (amd_gcn_11(n)) || amd_gcn_12(n)) -#define amd_vliw4(n) ((n & DEV_AMD_VLIW4) && gpu_amd(n)) -#define amd_vliw5(n) ((n & DEV_AMD_VLIW5) && gpu_amd(n)) -#define nvidia_sm_2x(n) ((n & DEV_NV_C2X) && gpu_nvidia(n)) -#define nvidia_sm_3x(n) (((n & DEV_NV_C30) || (n & DEV_NV_C32) || (n & DEV_NV_C35)) && gpu_nvidia(n)) -#define nvidia_sm_5x(n) ((n & DEV_NV_C5X) && gpu_nvidia(n)) -#define no_byte_addressable(n) ((n & DEV_NO_BYTE_ADDRESSABLE)) -#define use_local(n) ((n & DEV_USE_LOCAL)) -#define platform_apple(p) (get_platform_vendor_id(p) == PLATFORM_APPLE) - -// From opencl_misc.h - -/* Note: long is *always* 64-bit in OpenCL */ -typedef uchar uint8_t; -typedef uint uint32_t; -typedef ulong uint64_t; - -// TODO: I've no recent NVIDIA hardware to test this, so it's disabled on NVIDIA for safety -//#if !gpu_nvidia(DEVICE_INFO) || SM_MAJOR >= 5 -#if !gpu_nvidia(DEVICE_INFO) -#define USE_BITSELECT 1 -#endif - -#if cpu(DEVICE_INFO) -#define HAVE_ANDNOT 1 -#endif - -// TODO: I've no recent NVIDIA hardware to test this, so it's disabled for safety -//#if SM_MAJOR >= 5 && (DEV_VER_MAJOR > 352 || (DEV_VER_MAJOR == 352 && DEV_VER_MINOR >= 21)) -//#define HAVE_LUT3 1 -//inline uint lut3(uint a, uint b, uint c, uint imm) -//{ -// uint r; -// asm("lop3.b32 %0, %1, %2, %3, %4;" -// : "=r" (r) -// : "r" (a), "r" (b), "r" (c), "i" (imm)); -// return r; -//} -//#endif - -#if USE_BITSELECT -#define SWAP64(n) bitselect( \ - bitselect(rotate(n, 24UL), \ - rotate(n, 8UL), 0x000000FF000000FFUL), \ - bitselect(rotate(n, 56UL), \ - rotate(n, 40UL), 0x00FF000000FF0000UL), \ - 0xFFFF0000FFFF0000UL) -#else -// You would not believe how many driver bugs variants of this macro reveal -#define SWAP64(n) \ - (((n) << 56) | (((n) & 0xff00) << 40) | \ - (((n) & 0xff0000) << 24) | (((n) & 0xff000000) << 8) | \ - (((n) >> 8) & 0xff000000) | (((n) >> 24) & 0xff0000) | \ - (((n) >> 40) & 0xff00) | ((n) >> 56)) -#endif - -// From opencl_sha2_common.h - -//Macros. -#ifdef USE_BITSELECT -#define Ch(x, y, z) bitselect(z, y, x) -#define Maj(x, y, z) bitselect(x, y, z ^ x) -#else - -#if HAVE_LUT3 && BITS_32 -#define Ch(x, y, z) lut3(x, y, z, 0xca) -#elif HAVE_ANDNOT -#define Ch(x, y, z) ((x & y) ^ ((~x) & z)) -#else -#define Ch(x, y, z) (z ^ (x & (y ^ z))) -#endif - -#if HAVE_LUT3 && BITS_32 -#define Maj(x, y, z) lut3(x, y, z, 0xe8) -#else -#define Maj(x, y, z) ((x & y) | (z & (x | y))) -#endif -#endif - -// From opencl_sha512.h - -//Macros. -#if (cpu(DEVICE_INFO)) -#define ror(x, n) ((x >> n) | (x << (64UL-n))) -#else -#define ror(x, n) (rotate(x, (64UL-n))) -#endif - -#define Sigma0(x) ((ror(x,28UL)) ^ (ror(x,34UL)) ^ (ror(x,39UL))) -#define Sigma1(x) ((ror(x,14UL)) ^ (ror(x,18UL)) ^ (ror(x,41UL))) -#define sigma0(x) ((ror(x,1UL)) ^ (ror(x,8UL)) ^ (x>>7)) -#define sigma1(x) ((ror(x,19UL)) ^ (ror(x,61UL)) ^ (x>>6)) - -// SHA512 constants -#define H0 0x6a09e667f3bcc908UL -#define H1 0xbb67ae8584caa73bUL -#define H2 0x3c6ef372fe94f82bUL -#define H3 0xa54ff53a5f1d36f1UL -#define H4 0x510e527fade682d1UL -#define H5 0x9b05688c2b3e6c1fUL -#define H6 0x1f83d9abfb41bd6bUL -#define H7 0x5be0cd19137e2179UL - -__constant uint64_t k[] = { - 0x428a2f98d728ae22UL, 0x7137449123ef65cdUL, 0xb5c0fbcfec4d3b2fUL, 0xe9b5dba58189dbbcUL, - 0x3956c25bf348b538UL, 0x59f111f1b605d019UL, 0x923f82a4af194f9bUL, 0xab1c5ed5da6d8118UL, - 0xd807aa98a3030242UL, 0x12835b0145706fbeUL, 0x243185be4ee4b28cUL, 0x550c7dc3d5ffb4e2UL, - 0x72be5d74f27b896fUL, 0x80deb1fe3b1696b1UL, 0x9bdc06a725c71235UL, 0xc19bf174cf692694UL, - 0xe49b69c19ef14ad2UL, 0xefbe4786384f25e3UL, 0x0fc19dc68b8cd5b5UL, 0x240ca1cc77ac9c65UL, - 0x2de92c6f592b0275UL, 0x4a7484aa6ea6e483UL, 0x5cb0a9dcbd41fbd4UL, 0x76f988da831153b5UL, - 0x983e5152ee66dfabUL, 0xa831c66d2db43210UL, 0xb00327c898fb213fUL, 0xbf597fc7beef0ee4UL, - 0xc6e00bf33da88fc2UL, 0xd5a79147930aa725UL, 0x06ca6351e003826fUL, 0x142929670a0e6e70UL, - 0x27b70a8546d22ffcUL, 0x2e1b21385c26c926UL, 0x4d2c6dfc5ac42aedUL, 0x53380d139d95b3dfUL, - 0x650a73548baf63deUL, 0x766a0abb3c77b2a8UL, 0x81c2c92e47edaee6UL, 0x92722c851482353bUL, - 0xa2bfe8a14cf10364UL, 0xa81a664bbc423001UL, 0xc24b8b70d0f89791UL, 0xc76c51a30654be30UL, - 0xd192e819d6ef5218UL, 0xd69906245565a910UL, 0xf40e35855771202aUL, 0x106aa07032bbd1b8UL, - 0x19a4c116b8d2d0c8UL, 0x1e376c085141ab53UL, 0x2748774cdf8eeb99UL, 0x34b0bcb5e19b48a8UL, - 0x391c0cb3c5c95a63UL, 0x4ed8aa4ae3418acbUL, 0x5b9cca4f7763e373UL, 0x682e6ff3d6b2b8a3UL, - 0x748f82ee5defb2fcUL, 0x78a5636f43172f60UL, 0x84c87814a1f0ab72UL, 0x8cc702081a6439ecUL, - 0x90befffa23631e28UL, 0xa4506cebde82bde9UL, 0xbef9a3f7b2c67915UL, 0xc67178f2e372532bUL, - 0xca273eceea26619cUL, 0xd186b8c721c0c207UL, 0xeada7dd6cde0eb1eUL, 0xf57d4f7fee6ed178UL, - 0x06f067aa72176fbaUL, 0x0a637dc5a2c898a6UL, 0x113f9804bef90daeUL, 0x1b710b35131c471bUL, - 0x28db77f523047d84UL, 0x32caab7b40c72493UL, 0x3c9ebe0a15c9bebcUL, 0x431d67c49c100d4cUL, - 0x4cc5d4becb3e42b6UL, 0x597f299cfc657e2aUL, 0x5fcb6fab3ad6faecUL, 0x6c44198c4a475817UL -}; - - -// From sha512_kernel.cl - -// Computes an SHA512 hash of a single 1024-bit block with a data length of 64 bytes -inline void sha512_len64(uint64_t* w) -{ - uint64_t a, b, c, d, e, f, g, h; - uint64_t t; - - // Assumes input length was 64 bytes - w[8] = 0x8000000000000000UL; // The appended "1" bit - #pragma unroll - for (int i = 9; i < 15; i++) - w[i] = 0; - w[15] = 512; // The length in bits - - a = H0; - b = H1; - c = H2; - d = H3; - e = H4; - f = H5; - g = H6; - h = H7; - - #pragma unroll - for (int i = 0; i < 16; i++) { - t = k[i] + w[i] + h + Sigma1(e) + Ch(e, f, g); - - h = g; - g = f; - f = e; - e = d + t; - t = t + Maj(a, b, c) + Sigma0(a); - d = c; - c = b; - b = a; - a = t; - } - - #pragma unroll - for (int i = 16; i < 80; i++) { - w[i & 15] = sigma1(w[(i - 2) & 15]) + sigma0(w[(i - 15) & 15]) + w[(i - 16) & 15] + w[(i - 7) & 15]; - t = k[i] + w[i & 15] + h + Sigma1(e) + Ch(e, f, g); - - h = g; - g = f; - f = e; - e = d + t; - t = t + Maj(a, b, c) + Sigma0(a); - d = c; - c = b; - b = a; - a = t; - } - - // Copy resulting SHA512 hash back into the input variable - w[0] = a + H0; - w[1] = b + H1; - w[2] = c + H2; - w[3] = d + H3; - w[4] = e + H4; - w[5] = f + H5; - w[6] = g + H6; - w[7] = h + H7; -} - - -// Not from JtR - -// A 512-bit SHA512 hash result -typedef struct { - ulong8 as_vector; -} hash_t; - -// The Armory salt is 32 bytes long -typedef struct { - ulong4 as_vector; -} salt_t; - -// A 1024-bit SHA512 hash block -typedef union { - uint64_t as_uint64[16]; // 1024 bits - uint32_t as_uint32[32]; // 1024 bits - hash_t hash; // 512 bits - struct { - uint64_t truncated_hash[4]; // 256 bits - salt_t salt; // 256 bits - }; -} hash_block_t; - -#define SWAP32(n) ( ((n) << 24) | (((n) & 0xff00) << 8) | \ - (((n) >> 8) & 0xff00) | ((n) >> 24) ) - - -/* Implements the first half of ROMix: the lookup table (V) generation. This - * generates an incremental portion of the table during each call starting at - * V_start and calculating count entries. As a space-time tradeoff, it only - * saves one out of every SAVE_EVERY table entries (SAVE_EVERY is a compile-time - * constant, so the optimizer should remove the space-time code when it is - * disabled by defining SAVE_EVERY=1). - * - * The V tables are split across four OpenCL buffers. OpenCL permits GPUs to - * enforce a maximum buffer size of 1/4th the total available global memory, - * so in order to use as much memory as possible we need four of them. - */ -__kernel -void kernel_fill_V(__global hash_t* pV_buffer0, - __global hash_t* pV_buffer1, - __global hash_t* pV_buffer2, - __global hash_t* pV_buffer3, - uint32_t V_start, - uint32_t count, - __global hash_t* pX_buffer, - uint8_t X_already_hashed) -{ - size_t global_id = get_global_id(0); - size_t global_size = get_global_size(0); - - // Store the address of this worker's ROMix lookup table in pV - __global hash_t* pV; - switch(global_id / (global_size / 4U)) { // global_size % 4 is always 0 - case 0: - pV = pV_buffer0 + ((V_LEN-1) / SAVE_EVERY + 1) * global_id; - break; - case 1: - pV = pV_buffer1 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/4U); - break; - case 2: - pV = pV_buffer2 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/2U); - break; - case 3: - pV = pV_buffer3 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/4U*3U); - } - - // Get this worker's starting hash and copy it into the local working variable - pX_buffer += global_id; - hash_block_t X; - X.hash = *pX_buffer; // X is the running hash - - // Special case for the first hash which might already be done - if (X_already_hashed) { - // Just need to convert it's endianness - #pragma unroll - for (int i = 0; i < 8; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - } else { - - // Special case for the beginning of each iteration which needs - // its hash truncated and the salt appended before it is hashed - if (V_start == 0) { - // Convert endianness of the first 32 bytes - #pragma unroll - for (int i = 0; i < 4; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - // Overwrite the following 32 bytes with the salt - X.salt.as_vector = (ulong4)(SALT0,SALT1,SALT2,SALT3); - } else { - - // Convert endianness (of the entire 64-byte hash) - #pragma unroll - for (int i = 0; i < 8; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - } - - sha512_len64(X.as_uint64); - } - - if (V_start % SAVE_EVERY == 0) // only save one out of every SAVE_EVERY in the lookup table - pV[V_start / SAVE_EVERY] = X.hash; - - // Fill the rest of the lookup table - count += V_start; - for (uint32_t i = V_start + 1; i < count; i++) { - - sha512_len64(X.as_uint64); - if (i % SAVE_EVERY == 0) // only save one out of every SAVE_EVERY in the lookup table - pV[i / SAVE_EVERY] = X.hash; - } - - // Convert endianness back and save X back to the I/O buffer - #pragma unroll - for (int i = 0; i < 8; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - *pX_buffer = X.hash; -} - - -/* Implements the second half of ROMix: continuing hash iterations while mixing in - * data based on table (V) lookups. This performs count of these iterations during - * each call. Because only one out of every SAVE_EVERY table entries were saved, - * unsaved entries must be lazily generated based on the prior saved entry. - */ -__kernel -void kernel_lookup_V(__global hash_t* pV_buffer0, - __global hash_t* pV_buffer1, - __global hash_t* pV_buffer2, - __global hash_t* pV_buffer3, - uint32_t count, - __global hash_t* pX_buffer) -{ - size_t global_id = get_global_id(0); - size_t global_size = get_global_size(0); - - // Store the address of this worker's ROMix lookup table in pV - __global hash_t* pV; - switch(global_id / (global_size / 4U)) { // global_size % 4 is always 0 - case 0: - pV = pV_buffer0 + ((V_LEN-1) / SAVE_EVERY + 1) * global_id; - break; - case 1: - pV = pV_buffer1 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/4U); - break; - case 2: - pV = pV_buffer2 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/2U); - break; - case 3: - pV = pV_buffer3 + ((V_LEN-1) / SAVE_EVERY + 1) * (global_id - global_size/4U*3U); - } - - // Get this worker's starting hash and copy it into the local working variable - hash_block_t X; - pX_buffer += global_id; - X.hash = *pX_buffer; // X is the running hash - - // Convert endianness - #pragma unroll - for (int i = 0; i < 8; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - - uint32_t j, mod; - - hash_block_t Vj; // Vj will be the j'th saved hash in the lookup table (V) - - // Do the lookups and continuing hash iterations - for (uint32_t i = 0; i < count; i++) { - - // This is how Armory implements Integerify to calculate the lookup index - // (note its endianness is swapped when compared to the Armory source code) - j = SWAP32(X.as_uint32[14]) % V_LEN; - - Vj.hash = pV[j / SAVE_EVERY]; - - // If the desired lookup index wasn't in the table, calculate it - mod = j % SAVE_EVERY; - for (uint32_t n = 0; n < mod; n++) - sha512_len64(Vj.as_uint64); - - // Calculate the next hash - X.hash.as_vector ^= Vj.hash.as_vector; - sha512_len64(X.as_uint64); - } - - // Convert endianness back and save X back to the I/O buffer - #pragma unroll - for (int i = 0; i < 8; i++) - X.as_uint64[i] = SWAP64(X.as_uint64[i]); - *pX_buffer = X.hash; -} diff --git a/btcrecover/success_alert.py b/btcrecover/success_alert.py new file mode 100644 index 000000000..a174758ba --- /dev/null +++ b/btcrecover/success_alert.py @@ -0,0 +1,354 @@ +"""Helpers for emitting an audible alert when recovery succeeds.""" + +from __future__ import annotations + +import atexit +import os +import shutil +import subprocess +import sys +import threading +import time +from typing import Iterable, Optional, Sequence, TextIO + +try: + import fcntl # type: ignore +except ImportError: # pragma: no cover - not expected on Windows + fcntl = None # type: ignore + +_KDMKTONE = 0x4B30 +_PC_SPEAKER_FREQUENCY_HZ = 880 +_PC_SPEAKER_DURATION_MS = 150 + +_beep_enabled = False +_success_beep_stop_event: Optional[threading.Event] = None +_success_beep_thread: Optional[threading.Thread] = None +_console_bell_stream: Optional[TextIO] = None +_console_open_attempted = False +_DEFAULT_CONSOLE_PATHS: tuple[str, ...] = ("/dev/console", "/dev/tty0", "/dev/vc/0") +PC_SPEAKER_DEFAULT_CONSOLE_PATHS: tuple[str, ...] = _DEFAULT_CONSOLE_PATHS +_ENV_CONSOLE_PATHS: tuple[str, ...] = tuple( + path + for path in os.environ.get("BTCRECOVER_CONSOLE_BELL", "").split(os.pathsep) + if path +) + +_console_bell_paths: tuple[str, ...] = _ENV_CONSOLE_PATHS or _DEFAULT_CONSOLE_PATHS +_initial_console_bell_paths: tuple[str, ...] = _console_bell_paths +_pcspeaker_available: Optional[bool] = None +_pcspeaker_forced = False +_write_lock = threading.Lock() +_beep_command_available: Optional[bool] = None +_beep_command_path: Optional[str] = None + + +def _console_bell_fd() -> Optional[int]: + stream = _ensure_console_bell_stream() + if stream is None: + return None + + try: + return stream.fileno() + except Exception: + return None + + +def _beep_command() -> Optional[str]: + global _beep_command_path + + if _beep_command_path: + return _beep_command_path + + path = shutil.which("beep") + if path: + _beep_command_path = path + return _beep_command_path + + +def _emit_beep_command(duration_ms: int, frequency_hz: int) -> bool: + """Fallback to the external ``beep`` utility when available.""" + + global _beep_command_available + + if _beep_command_available is False: + return False + + command_path = _beep_command() + if not command_path: + _beep_command_available = False + return False + + try: + subprocess.run( + [command_path, "-f", str(frequency_hz), "-l", str(duration_ms)], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except (OSError, subprocess.CalledProcessError): + _beep_command_available = False + return False + + _beep_command_available = True + return True + + +def _emit_pc_speaker_beep(duration_ms: int, frequency_hz: int) -> bool: + """Attempt to ring the internal PC speaker directly.""" + + global _pcspeaker_available + + if _pcspeaker_available is False and not _pcspeaker_forced: + return False + + if fcntl is None: + _pcspeaker_available = False + if _pcspeaker_forced: + return _emit_beep_command(duration_ms, frequency_hz) + return False + + fd = _console_bell_fd() + if fd is None: + _pcspeaker_available = False + if _pcspeaker_forced: + return _emit_beep_command(duration_ms, frequency_hz) + return False + + if duration_ms <= 0 or frequency_hz <= 0: + return False + + try: + fcntl.ioctl(fd, _KDMKTONE, (frequency_hz << 16) | duration_ms) + except OSError: + _pcspeaker_available = False + if _pcspeaker_forced: + return _emit_beep_command(duration_ms, frequency_hz) + return False + + _pcspeaker_available = True + return True + + +def configure_pc_speaker( + enable: bool, + *, + console_paths: Optional[Sequence[str]] = None, +) -> bool: + """Control whether the alert attempts to use the motherboard PC speaker. + + When ``enable`` is :data:`True`, the helper will try to drive the kernel PC + speaker interface via ``KDMKTONE`` ioctls against one of the provided + console device paths. The caller can provide ``console_paths`` to override + the default search order. When disabled, the original console configuration + (derived from ``BTCRECOVER_CONSOLE_BELL`` if set) is restored. + + The function returns :data:`True` if the configuration succeeded or + :data:`False` when the PC speaker could not be prepared. Callers may still + attempt playback even if ``False`` is returned as hardware support and + privileges vary between systems. + """ + + global _console_bell_paths, _pcspeaker_available, _pcspeaker_forced, _console_open_attempted + + _pcspeaker_forced = bool(enable) + + if enable: + if console_paths is not None: + _console_bell_paths = tuple(path for path in console_paths if path) + elif not _ENV_CONSOLE_PATHS: + _console_bell_paths = _DEFAULT_CONSOLE_PATHS + else: + _console_bell_paths = _initial_console_bell_paths + + # Reset cached state so we re-open the console with the new configuration. + _close_console_bell_stream() + _console_open_attempted = False + _pcspeaker_available = None + + global _beep_command_available + + if not enable: + _beep_command_available = None + return True + + # Try to open the console immediately to surface failures early. + stream = _ensure_console_bell_stream() + if stream is None: + if _beep_command(): + _beep_command_available = None + return True + return False + + fd = _console_bell_fd() + return fd is not None + + +def _close_console_bell_stream() -> None: + global _console_bell_stream + + stream = _console_bell_stream + _console_bell_stream = None + if stream is not None: + try: + stream.close() + except Exception: + pass + + +def _ensure_console_bell_stream() -> Optional[TextIO]: + """Return a handle that writes directly to the system console when possible.""" + + global _console_bell_stream, _console_open_attempted + + if _console_bell_stream is not None or _console_open_attempted: + return _console_bell_stream + + _console_open_attempted = True + + for console_path in _console_bell_paths: + if not console_path: + continue + + try: + stream = open(console_path, "w", encoding="utf-8", errors="ignore") + except OSError: + continue + + _console_bell_stream = stream + atexit.register(_close_console_bell_stream) + return _console_bell_stream + + return None + + +def _bell_streams(skip_console: bool = False) -> Iterable[TextIO]: + """Yield file-like objects that should receive BEL characters.""" + + for name in ("stdout", "stderr"): + stream = getattr(sys, name, None) + if stream is None: + continue + try: + if stream.isatty(): + yield stream + except Exception: + continue + + if not skip_console: + console_stream = _ensure_console_bell_stream() + if console_stream is not None: + yield console_stream + + +def _emit_beeps(count: int, spacing: float = 0.2) -> None: + """Emit ``count`` terminal bell characters with ``spacing`` seconds between them.""" + + for index in range(count): + with _write_lock: + pcspeaker = _emit_pc_speaker_beep( + _PC_SPEAKER_DURATION_MS, + _PC_SPEAKER_FREQUENCY_HZ, + ) + emitted = False + for stream in _bell_streams(skip_console=pcspeaker): + try: + stream.write("\a") + stream.flush() + emitted = True + except Exception: + continue + + if not emitted and not pcspeaker: + # Fall back to the default stdout behaviour even if it is not a TTY. + try: + sys.stdout.write("\a") + sys.stdout.flush() + except Exception: + pass + + if index + 1 < count: + time.sleep(spacing) + + +def set_beep_on_find(enabled: bool) -> None: + """Enable or disable the background success beep.""" + + global _beep_enabled + _beep_enabled = bool(enabled) + if not _beep_enabled: + stop_success_beep() + + +def start_success_beep() -> None: + """Begin a background thread that emits a two-tone alert roughly every ten seconds.""" + + global _success_beep_stop_event, _success_beep_thread + + if not _beep_enabled or _success_beep_thread is not None: + return + + _success_beep_stop_event = threading.Event() + + def _beep_loop() -> None: + while True: + _emit_beeps(2, spacing=1.5) + if _success_beep_stop_event.wait(10): + break + + _success_beep_thread = threading.Thread( + target=_beep_loop, + name="success_beep", + daemon=True, + ) + _success_beep_thread.start() + + +def stop_success_beep() -> None: + """Stop the background success beep thread if it is running.""" + + global _success_beep_stop_event, _success_beep_thread + + if _success_beep_stop_event is not None: + _success_beep_stop_event.set() + if _success_beep_thread is not None: + _success_beep_thread.join(timeout=0.1) + + _success_beep_stop_event = None + _success_beep_thread = None + + +def wait_for_user_to_stop(prompt: str = "\nPress Enter to stop the success alert and exit...") -> None: + """Wait for the user to press Enter before stopping the alert. + + The wait only occurs when the success beep is active and stdin is interactive. + """ + + if not _beep_enabled or _success_beep_thread is None: + return + + stdin = getattr(sys, "stdin", None) + if stdin is None: + return + + try: + is_interactive = stdin.isatty() + except AttributeError: + is_interactive = False + + if not is_interactive: + return + + try: + input(prompt) + except EOFError: + # Non-interactive consumers may close stdin unexpectedly; just stop beeping. + pass + + +def beep_failure_once() -> None: + """Emit a single terminal bell when a recovery attempt fails.""" + + if not _beep_enabled: + return + + _emit_beeps(1) diff --git a/btcrecover/test/test-addressdbs/addresses-BCH-Test.db b/btcrecover/test/test-addressdbs/addresses-BCH-Test.db new file mode 100644 index 000000000..9d405e0bb Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-BCH-Test.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-BTC-P2TR.db b/btcrecover/test/test-addressdbs/addresses-BTC-P2TR.db new file mode 100644 index 000000000..01ddf36c6 Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-BTC-P2TR.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-BTC-Test.db b/btcrecover/test/test-addressdbs/addresses-BTC-Test.db new file mode 100644 index 000000000..c1a78754b Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-BTC-Test.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-DGB-Test.db b/btcrecover/test/test-addressdbs/addresses-DGB-Test.db new file mode 100644 index 000000000..662004e98 Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-DGB-Test.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-LTC-Test.db b/btcrecover/test/test-addressdbs/addresses-LTC-Test.db new file mode 100644 index 000000000..c14e2a0c3 Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-LTC-Test.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-MONA-Test.db b/btcrecover/test/test-addressdbs/addresses-MONA-Test.db new file mode 100644 index 000000000..e0438d938 Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-MONA-Test.db differ diff --git a/btcrecover/test/test-addressdbs/addresses-VTC-Test.db b/btcrecover/test/test-addressdbs/addresses-VTC-Test.db new file mode 100644 index 000000000..4eabe2ef7 Binary files /dev/null and b/btcrecover/test/test-addressdbs/addresses-VTC-Test.db differ diff --git a/btcrecover/test/test-listfiles/BIP39PassphraseListTest.txt b/btcrecover/test/test-listfiles/BIP39PassphraseListTest.txt new file mode 100644 index 000000000..c79319270 --- /dev/null +++ b/btcrecover/test/test-listfiles/BIP39PassphraseListTest.txt @@ -0,0 +1,20 @@ +btcr +- +-btcr +btcr- +test +testbtcr +btcrtest +test- +-test +test-btcr +testbtcr- +-testbtcr +-btcrtest +btcrtest- +btcr-test +-- +--btcr +-btcr- +btcr-- +btcr-test-password \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/Seed-Transform-Base.txt b/btcrecover/test/test-listfiles/Seed-Transform-Base.txt new file mode 100644 index 000000000..3fde4e202 --- /dev/null +++ b/btcrecover/test/test-listfiles/Seed-Transform-Base.txt @@ -0,0 +1 @@ +1,2,3 \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/Seed-Transform-Trezor.txt b/btcrecover/test/test-listfiles/Seed-Transform-Trezor.txt new file mode 100644 index 000000000..ff84e7713 --- /dev/null +++ b/btcrecover/test/test-listfiles/Seed-Transform-Trezor.txt @@ -0,0 +1 @@ +able,across,age diff --git a/btcrecover/test/test-listfiles/SeedListTest.txt b/btcrecover/test/test-listfiles/SeedListTest.txt new file mode 100644 index 000000000..571c6569e --- /dev/null +++ b/btcrecover/test/test-listfiles/SeedListTest.txt @@ -0,0 +1,6 @@ +ocean hidden kidney famous rich season gloom husband spring boy attitude convince +ocean hidden kidney famous rich season gloom husband spring boy convince attitude +ocean hidden kidney famous rich season gloom husband spring attitude boy convince +ocean hidden kidney famous rich season gloom husband spring attitude convince boy +ocean hidden kidney famous rich season gloom husband spring convince boy attitude +ocean hidden kidney famous rich season gloom husband spring convince attitude boy \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/SeedListTest_pylist.txt b/btcrecover/test/test-listfiles/SeedListTest_pylist.txt new file mode 100644 index 000000000..d2393de97 --- /dev/null +++ b/btcrecover/test/test-listfiles/SeedListTest_pylist.txt @@ -0,0 +1,6 @@ +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'attitude', 'convince'] +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'convince', 'attitude'] +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'boy', 'convince'] +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'convince', 'boy'] +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'boy', 'attitude'] +['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'attitude', 'boy'] \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/SeedListTest_pytupe.txt b/btcrecover/test/test-listfiles/SeedListTest_pytupe.txt new file mode 100644 index 000000000..46ea1aed6 --- /dev/null +++ b/btcrecover/test/test-listfiles/SeedListTest_pytupe.txt @@ -0,0 +1,6 @@ +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'attitude', 'convince') +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'convince', 'attitude') +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'boy', 'convince') +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'convince', 'boy') +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'boy', 'attitude') +('ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'attitude', 'boy') \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/SeedTokenListTest.txt b/btcrecover/test/test-listfiles/SeedTokenListTest.txt new file mode 100644 index 000000000..492563feb --- /dev/null +++ b/btcrecover/test/test-listfiles/SeedTokenListTest.txt @@ -0,0 +1,12 @@ +^1^ocean +^2^hidden +^3^kidney +^4^famous +^5^rich +^6^season +^7^gloom +^8^husband +^9^spring +convince +attitude +boy \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/SeedTokenList_ElectrumV1.txt b/btcrecover/test/test-listfiles/SeedTokenList_ElectrumV1.txt new file mode 100644 index 000000000..350f2374e --- /dev/null +++ b/btcrecover/test/test-listfiles/SeedTokenList_ElectrumV1.txt @@ -0,0 +1,12 @@ +^1^straight +^2^subject +^3^wild +^4^ask +^5^clean +possible +age +hurt +squeeze +cost +stuck +softly \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/passwordListTest.txt b/btcrecover/test/test-listfiles/passwordListTest.txt new file mode 100644 index 000000000..991f9ce0e --- /dev/null +++ b/btcrecover/test/test-listfiles/passwordListTest.txt @@ -0,0 +1 @@ +youtube \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/tokenListTest.txt b/btcrecover/test/test-listfiles/tokenListTest.txt new file mode 100644 index 000000000..8ad48b6de --- /dev/null +++ b/btcrecover/test/test-listfiles/tokenListTest.txt @@ -0,0 +1,5 @@ +btcr +test +password +- +- \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/tokenlist-allpositional.txt b/btcrecover/test/test-listfiles/tokenlist-allpositional.txt new file mode 100644 index 000000000..4c41c18db --- /dev/null +++ b/btcrecover/test/test-listfiles/tokenlist-allpositional.txt @@ -0,0 +1,12 @@ +^1^elbow +^2^text +^3^print +^4^census +^5^battle +^6^push +^7^oyster +^8^team +^9^home +^10^april +^11^travel +^12^barrel \ No newline at end of file diff --git a/btcrecover/test/test-listfiles/tokenlist-tokenblocks.txt b/btcrecover/test/test-listfiles/tokenlist-tokenblocks.txt new file mode 100644 index 000000000..f152ab28d --- /dev/null +++ b/btcrecover/test/test-listfiles/tokenlist-tokenblocks.txt @@ -0,0 +1,3 @@ +^1^elbow,text,print +^2^census,battle,push +^3^oyster,team,home,april,travel,barrel \ No newline at end of file diff --git a/btcrecover/test/test-wallets/android-bitcoin-wallet-backup-2022 b/btcrecover/test/test-wallets/android-bitcoin-wallet-backup-2022 new file mode 100644 index 000000000..d432c227b --- /dev/null +++ b/btcrecover/test/test-wallets/android-bitcoin-wallet-backup-2022 @@ -0,0 +1,818 @@ +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 \ No newline at end of file diff --git a/btcrecover/test/test-wallets/armory-wallet.wallet b/btcrecover/test/test-wallets/armory-wallet.wallet deleted file mode 100644 index edee6d92a..000000000 Binary files a/btcrecover/test/test-wallets/armory-wallet.wallet and /dev/null differ diff --git a/btcrecover/test/test-wallets/bitcoincore-0.20.1-wallet.dat b/btcrecover/test/test-wallets/bitcoincore-0.20.1-wallet.dat new file mode 100644 index 000000000..26d0660b0 Binary files /dev/null and b/btcrecover/test/test-wallets/bitcoincore-0.20.1-wallet.dat differ diff --git a/btcrecover/test/test-wallets/bitcoincore-0.21.1-wallet.dat b/btcrecover/test/test-wallets/bitcoincore-0.21.1-wallet.dat new file mode 100644 index 000000000..e3bc3bbb7 Binary files /dev/null and b/btcrecover/test/test-wallets/bitcoincore-0.21.1-wallet.dat differ diff --git a/btcrecover/test/test-wallets/bitgo_keycard_userkey.json b/btcrecover/test/test-wallets/bitgo_keycard_userkey.json new file mode 100644 index 000000000..82870ac21 --- /dev/null +++ b/btcrecover/test/test-wallets/bitgo_keycard_userkey.json @@ -0,0 +1 @@ +{"iv":"ZG0B0bPyYIl6cD49i3uBlg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"l/A+yUyoDnA=","ct":"2MJDfwQ2yP9g3Vf9e5msp5ROQNmOa6iBMrFNvR/I6FzqFqObjfBWHShaCtS8c8s1CC2IvlI8DRTNJR2QXzZy/XIjOKlCRL1XACe1ZFmppY8jBy0IYBy35SVqoXxFil9hO+r4aauvrxzTXnDF1CLvyf1ZYYANv/s="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/block.io.change.json b/btcrecover/test/test-wallets/block.io.change.json new file mode 100644 index 000000000..40d1562b4 --- /dev/null +++ b/btcrecover/test/test-wallets/block.io.change.json @@ -0,0 +1,131 @@ +{ + "status": "success", + "data": { + "networks": [ + "DOGE", + "DOGETEST", + "BTC", + "BTCTEST", + "LTC", + "LTCTEST" + ], + "next_algorithm": { + "pbkdf2_iterations": 102400, + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "pbkdf2_hash_function": "SHA256", + "aes_cipher": "AES-256-GCM", + "aes_auth_data": "" + }, + "current_user_keys": [ + { + "network": "DOGE", + "user_key": { + "public_key": "0249fc764329fabb42ceaed70eba748e80e7b6c2aa2eea4bdf4ea9d9aabb4e437e", + "encrypted_passphrase": "FYrN5SAog/hbWivKzCWpCz64/ydy8R/RtVARsRniayHuG5Lia7RQzrjDeAL1bAYIzt5ipYUlXQqMmKx/tPTKaCCNWjvhO2+yb8YsByggQ/32BObbCWoUNFOWNhjEjoOqcpWw20F/B4gf7zCbL08R81LYqox1Lszh7skB2D0wzpA=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "cf7be3f39172519508044f4e", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "1b87a0b968f15c976d17c4feca78423e", + "aes_auth_data": "" + } + } + }, + { + "network": "DOGETEST", + "user_key": { + "public_key": "037e59338d3c7d59b2e13328bd001af1dbecfe717ade7ee2963c0e3aeeb7f91cc3", + "encrypted_passphrase": "6aUEjVFW4zYxQaPUtAhUP1OUOWUrW5CC7mv4bP11HOuOx+jI5rd2KlntbTQwzzQFJN5MSiO31Yg5vk6lBQbijyOgcrKgWPFqyNTC12+zFbtX3AxT6DfYnW+3P5LFVoj8ctLzcSk6lhqKWIazOoZXH5EsomlAX64fX0ckkyKKF6E=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "2920cf1fa9dcb49bd3e606a0", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "0908feeff0408211c298147191b48902", + "aes_auth_data": "" + } + } + }, + { + "network": "BTC", + "user_key": { + "public_key": "034b3251f1d6fc658a5c76fa073dab50a15b9f3a48391c995a596ea497f997cde4", + "encrypted_passphrase": "JTj4yZQWVffaCoUKlYfWXWbfqenGnMatGlpIr7qRMHFxDY4iWzloH3avMo53Yo62yegi/sxIxZdT7R8PAk/DQsqgZdxEr9/ixDyuwYGEf+ER0SBxKqq98NyLnOpx3J185jBpPH5vPBtN5QJQaQhInLL1bKqa6I3TJjfVhK3h01I=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "be3cefcda63301977a5633e5", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "eee10b6223c0f35d3fc52ddf5abdf3f7", + "aes_auth_data": "" + } + } + }, + { + "network": "BTCTEST", + "user_key": { + "public_key": "0269643baae6ed3391733c64f9552764bd126e95e2774dbafbdf5027fa89d683bd", + "encrypted_passphrase": "TTfemRHuALEEnHs7Jzd26+ggT9DtLKaIzFuIT+C6Kq0iETRh52YLwrdkPJoPUz/Jt/Cu5HtYAuivjAsUyPoeaepVEpQ2TDeX9nKKA0WVZP0Vs1GcwiL4uioVj6Y/wXYSYPsYqDvOHlg4KPJG+IeRyeEy/SUmXq4VGOrB8SgGegk=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "804b916460d9a2e73dd502c4", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "f40693341188a3d2ac5db64dd534b809", + "aes_auth_data": "" + } + } + }, + { + "network": "LTC", + "user_key": { + "public_key": "02821fa3597e5ca51a6edea60c215ab5e5550859acbcd5c24bf0ca292671a1cd9d", + "encrypted_passphrase": "ifYcmir21dzauV2pS31EX5xy4mBVYEHx9UjabFZkntAX7dzY1zwxHtzKpB7QK0SxFV2wRsLFLj6riQCWdMuq5+vg3I06n9fdEah/f1IKpH5i2p057P623di3PPTpd2/rY3Li0LwmKSOFLLas77pIc3cZhIU7iJ0mqk3kGzC4yUI=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "745d188a14b3cb3f22fb8922", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "e6f681cc86df2ba677749afa3149f539", + "aes_auth_data": "" + } + } + }, + { + "network": "LTCTEST", + "user_key": { + "public_key": "03b22072b1bf7e420a87a9ddf1d0a6681b176c83569e07858043b9a0a7ef1b059c", + "encrypted_passphrase": "gc3mf7K8E9bU8yxZlpS/vxMs7Xclf65l07hT9MWAuzr1QvdaITxQ2pkR3eIk+tc17mv8ReLbKGpMP5UpXQXp2upzh4+Uq7HqH9FIJ8cIJFkMWUsj3+C9R8Fr4ul7WJSeBxZcyqo45re+3+F3MwzlMy0CE47lOHoB1IEE9dDrO6k=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "bd561d94af23ee861056d93a", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "8614da4468f912a1f99773cdce33c7e3", + "aes_auth_data": "" + } + } + } + ] + } +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/block.io.request.json b/btcrecover/test/test-wallets/block.io.request.json new file mode 100644 index 000000000..3f2ee2000 --- /dev/null +++ b/btcrecover/test/test-wallets/block.io.request.json @@ -0,0 +1,21 @@ +{ + "status": "success", + "data": { + "user_key": { + "public_key": "0269643baae6ed3391733c64f9552764bd126e95e2774dbafbdf5027fa89d683bd", + "encrypted_passphrase": "TTfemRHuALEEnHs7Jzd26+ggT9DtLKaIzFuIT+C6Kq0iETRh52YLwrdkPJoPUz/Jt/Cu5HtYAuivjAsUyPoeaepVEpQ2TDeX9nKKA0WVZP0Vs1GcwiL4uioVj6Y/wXYSYPsYqDvOHlg4KPJG+IeRyeEy/SUmXq4VGOrB8SgGegk=", + "algorithm": { + "pbkdf2_salt": "b533822f70de28e77aabacf7c3f6f1f8", + "pbkdf2_iterations": 102400, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": "804b916460d9a2e73dd502c4", + "aes_cipher": "AES-256-GCM", + "aes_auth_tag": "f40693341188a3d2ac5db64dd534b809", + "aes_auth_data": "" + } + }, + "new_nonce": "fc889fc81cbbb99c00db2f28e44787ba" + } +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/block.io.request.legacy.json b/btcrecover/test/test-wallets/block.io.request.legacy.json new file mode 100644 index 000000000..69000bcf5 --- /dev/null +++ b/btcrecover/test/test-wallets/block.io.request.legacy.json @@ -0,0 +1,21 @@ +{ + "status": "success", + "data": { + "user_key": { + "public_key": "0330dc2ed36ec4b27952be9bf84cf97714d1cdd1fcd12d8a01dc214b8d265b4735", + "encrypted_passphrase": "wy6X5iAYZ1hqT4/d0P7BnlfY3/I5hJMzjfdPwUASmSLHYeqkt/0Q75xRlDFQXhe8e7CJdpkQ84zCnf2zhmokaVElhKTv98ovcZj3mbfI5/wqw9Q0/ZOGEgl1AOLVD7eyuWW1kPQ2DYBlcZEHCirSwWzCLSwoFrU9mFBQE7aIMR1L2uhDK9pHxC/U1fgVwONA", + "algorithm": { + "pbkdf2_salt": "", + "pbkdf2_iterations": 2048, + "pbkdf2_hash_function": "SHA256", + "pbkdf2_phase1_key_length": 16, + "pbkdf2_phase2_key_length": 32, + "aes_iv": null, + "aes_cipher": "AES-256-ECB", + "aes_auth_tag": null, + "aes_auth_data": null + } + }, + "new_nonce": "6c157f4f4c35c86e2aebf0800befec3b" + } +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v1-1 b/btcrecover/test/test-wallets/blockchain-github-v1-1 new file mode 100644 index 000000000..1cdfb2982 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v1-1 @@ -0,0 +1 @@ +VPXtmcNa7D9HhfmMT2KnTKOvoIhuQyh2vxZFvwwd24C2zKsUXLew1umUcV3ZGSXUO8Hmq7giuf3uYdFLXWOqAXsNlmyI9GL22xec8ZWEnYBiKd72fbY1EelcG4qhbBN1aaFKA9z3\/wkmkipBdHOyIT7xmHhLypm+8o53yv0tZcmFoNXVUcM3KIcuFjcPFSBWW1uCEClPXW54Xu76yY1Siyo6KLclA\/mQ7FWJ+F5unoRDJBUxRN5ebF1Vpyj+Cvs+ZTSRxiqzHAfHzSCdMs15LtKurS2AsLVTSkeX6MXYeofOVzh45dtqtnibYglBfMwoEZHjfRXwhps79jVemNtn0FrwFeFzzO4waC2G3yhaynVCNtc1IM0vhbh\/WXoTnZUYc3vaDT\/MstlARsV3XKPYzsCS0vgpEPeV\/FgCNpw8ADBFqpuMoS0aTmoybcHX1I09yTkuO3Ryo8DhD\/Z63oYDC5Q1VojLlYxAUioIgGwyivX\/NmC3IHXVyuVUHZGk0adIuyKvALGnuwOiC4liw1ENaEMg2mJFOdASWji+ryngoJbywmLt+vlfo3guHI\/viF\/6lCE+\/kakStAPcXI1a1gMmjXdBfXx5BCWBz+iofpb8dGXqh4znahY03vhlyesK7rG5NSZzNoFXSFjZh6uXfm1pVMhp23\/OBI9KdxnMnEH5IaldSH0fLUPMF\/EfHZmlThclh4ScWUtEIcjXZMUvQVYh5z4SzmUXB9\/JWvWp\/JY3tDd20y7J+gG\/vly\/Rlq1HTAx+Tl5bEDkBGHUGfok9kNtqymW\/w+Z86pR1CyA7Ummt5RSz+yQhRBR4890ajqiHIQAac8v+GIabh3AJXyPOZcclBnK\/W1ZP1VblVw8i9FtLhc2y9sbTQubjlhbhqbQFTdqebOUxER00hdeTtqNsBhVfVAtHDeEgGJHWoVaaw9vcr8TpqmjAz62UZIYPNOSZbLRiNjodx4VOKpCS2u\/XTkcDklHhusoEsYeZQp0LQqvnKyBq0q3ruy7zXdwBpOnC6tJ0kS3Xvc\/97wVSTIfA8wKVPNhbQ7TYPZFiAE6Y8bVb5wtCTfRC8i528eNmusZ\/RUO6pjRgA0YvA4np2QNx9x1AKrwC\/ujRopULMImHbGDKRuKX3HYRMHyfsg7KGgkimaYQG\/uYzM5s905sM5qPa0\/Z1Anm+b7JKMuLGO4\/XiCKkVhEGdkhxw1Tq7jZxFjm3HDuLEwVKbJprxlBzBWdYVV\/ucS+LklDgjeFuny\/aJdTGyWH6bXOwu8GJmIfQEjgf7 \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v1-1_main_dump.txt b/btcrecover/test/test-wallets/blockchain-github-v1-1_main_dump.txt new file mode 100644 index 000000000..e57a0a635 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v1-1_main_dump.txt @@ -0,0 +1,44 @@ +{ + "guid": "9ebb4d4f-f36e-40d6-9a3e-5a3cca5f83d6", + "sharedKey": "41cf823f-2dcd-4967-88d1-ef9af8689fc6", + "double_encryption": false, + "options": { + "fee_policy": 0, + "html5_notifications": false, + "logout_time": 600000, + "tx_display": 0, + "always_keep_local_backup": false + }, + "keys": [ + { + "addr": "1AN6WAdSrXATk7mcosqhK7vvPusC3fmw8p", + "priv": "XxkpqJqs1449nLHX1WcCP5QbmyJhjrwTis9hyvqGKJc", + "privkey_compressed": "KwV8RDmA2o9WXhiuEBkWAYh8y6cYp9UquXryaEPS1evXUSXJfg43", + "privkey_uncompressed": "5HsnBUMsz3kch2ycrCxAnDAYiFsCHaBqgzk3PwEh73FPmLfVrsP" + }, + { + "addr": "1Cp1xPTVdjePVcRAJHxesiLkNZbASgbkL", + "priv": "8uZW7qiz89NGtgwzdbqKZ9QDVPj1NeGV192RJUXdc946", + "privkey_compressed": "L1A4Ty9VVY5oF5vTWCUvSKHiLJ6PNosSVcNzWB7PTFgj4v56CJsn", + "privkey_uncompressed": "5Ji2EpAB5era8itzD449wDkfN8x9F1CRooz6KqxpgUoA5a3NY7c" + }, + { + "addr": "1MsA1JJhgNMkuke9uttaXQfFyNBvmCyeiR", + "priv": "BH2MMphxoWQkynoo7HM8ukLSw8aNKFT9sWV2nu9QmNqe", + "privkey_compressed": "L2LWiEUFSmZaJNeV2hEgnx4A4j3gHi9oQpyJq19kvwKNY21Pz1Nj", + "privkey_uncompressed": "5JyXkKur3DMyUxCfXHpWQYxYWPffoqHwvRv5wq4oCmUj33X1DrZ" + }, + { + "addr": "13m3hC95Pp3ZWth6XCmhr9qUVNZGqMchia", + "priv": "4TE4wCgz1fcjSPsxSUUig36a9JpNfPn9c2KFcT94hWtk", + "privkey_compressed": "KxwR9qWWd3jXyJirSuKZrasdQ5aa7WJH1zi1dXvn5PUNxksoDzrK", + "privkey_uncompressed": "5JCsnW7gnn2UruxFNjDN3AwWznu1htx7oSmBZkFuMUkMqTZ3n3G" + }, + { + "addr": "17thFxEzSsR3fn2CpQrAYrcsUve6VN8JuX", + "priv": "2YARBSUuHrsuuvtEHoeTXiDXGdiDuHtfdLvvYCKkADCm", + "privkey_compressed": "Kwz7Q6xJNTM9ooVmWwPfGairubgJF7ur8UphFCkMpn6fHFc1E4E3", + "privkey_uncompressed": "5HzM2cLKtXth4MWB5yH1Wp4bR9mFL4Nwdawm1VwgcJ1q6aKqoHG" + } + ] +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v1-1_main_privkeys.txt b/btcrecover/test/test-wallets/blockchain-github-v1-1_main_privkeys.txt new file mode 100644 index 000000000..5043f9414 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v1-1_main_privkeys.txt @@ -0,0 +1,11 @@ +Private Keys (For copy/paste in to Electrum) are below... +KwV8RDmA2o9WXhiuEBkWAYh8y6cYp9UquXryaEPS1evXUSXJfg43 +5HsnBUMsz3kch2ycrCxAnDAYiFsCHaBqgzk3PwEh73FPmLfVrsP +L1A4Ty9VVY5oF5vTWCUvSKHiLJ6PNosSVcNzWB7PTFgj4v56CJsn +5Ji2EpAB5era8itzD449wDkfN8x9F1CRooz6KqxpgUoA5a3NY7c +L2LWiEUFSmZaJNeV2hEgnx4A4j3gHi9oQpyJq19kvwKNY21Pz1Nj +5JyXkKur3DMyUxCfXHpWQYxYWPffoqHwvRv5wq4oCmUj33X1DrZ +KxwR9qWWd3jXyJirSuKZrasdQ5aa7WJH1zi1dXvn5PUNxksoDzrK +5JCsnW7gnn2UruxFNjDN3AwWznu1htx7oSmBZkFuMUkMqTZ3n3G +Kwz7Q6xJNTM9ooVmWwPfGairubgJF7ur8UphFCkMpn6fHFc1E4E3 +5HzM2cLKtXth4MWB5yH1Wp4bR9mFL4Nwdawm1VwgcJ1q6aKqoHG diff --git a/btcrecover/test/test-wallets/blockchain-github-v1-2 b/btcrecover/test/test-wallets/blockchain-github-v1-2 new file mode 100644 index 000000000..6669f505a --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v1-2 @@ -0,0 +1 @@ +4gen70mZdnKx0YRnjpPSHTqz2UOMiEUGev9ZaU0L5daWQpqCBqZUgCIqEEbyNzmBQk6DhaQsReAOkzJz4L8q8VMeKk+\/h5JXIQTqxOwz0M470ZiyGnmKe7DFtEQGft3oAQAvc\/SA89gdu\/50SASEQk6fQPyRigkPunIrjmnxzWuO+Mak040Lea3qoScJXBY3xZG4C4ukJaFFhcZUTi+e45JYAg7AtmyONFFdcVLNpGjwRTkDvpAPobGJqMfOfFLHkdxNos+51khXYuyxEp3grU8jvwZbl2pCVgC1Z50IWFSUvSaZGvKkZaK5Ohw0Tn7RF4T4oUA4IrRYGpHY2F8yLUpcSJ47ctC90UPT7GITpDHA\/eQNpzdhtIv7Inkkza\/Okd5blx+59he+x\/AQFTdyc5YZmsEgN+g\/RUN06UNibe78iyqEEN4q88RiLDAwFMoHY4cYtzyd25CSza0NP+yBFFLf+NbKA64Ck2pItMgr4JXkCVU8shQmtKnVKfO8mC3MQSF+kZE0mClr7URa8LIbMhmGgQ3o2vGNbRiutzdO7\/L52F7GWTHJzKEo5FdWK2K218Spd0L31TpBn3aKXg6BLgw6WiggztP4hP4pVf6LK21KvgPKf3NegF4Or7wfDz1\/mP4uiz+xf18trCuFHoityhXduwrrtA9bhhf2SwLE4+Md9R+m1KOQ6q63ynujM0oeIGVj6NIH4uJS6pz\/3iIKZTkr\/4\/5TdEw8uBlKHHxJHBHBuHPsYN3bpd4A+VMiNn3EPV02aD7xCq6anFPvoYbj8BHW0p6kUHP32fcuETciO7NF0NLGSIoE3iTNHRSpXt58BEHIU\/p4Tx4KczwLfUiVvARzjnkF8KQMPrgzhZysiXhH8cwdw3LPU1hi0zDuyW3RR7UQxuCTOa\/T8v0ZnGZhFNIFkocwSvcDStef807vg6bu2Fe4BewCLzHLQDEudTUHxiFsWWfzo4E4\/3pCTNgAkI4N0swvUR7aBTQHminudxrdSxC2f\/6pgBnd4TqBv+W16qcWLf31x\/yV1wHFyG2rfTpbpTx07W4r3G4zoDq8XHecpfuDy2H+GKgctSyUSDO9BTb6Q93+JyrMDvuw2o4ennAmll8gQ8C5WjxsQ78oES2\/\/yeCqTgKQiEx121Dwa2\/MiposMiRg82pwMXZ7lERx74CQnII54z4YAHGA2EqtQwwNdzdCufJqGV8oHaEPAXRNnGuAK6fbDUlM10GiSQoCpvzmDtv5n3RYlXXgmXAjmDb1CodLEFqCps\/lPyAvav \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v1-3 b/btcrecover/test/test-wallets/blockchain-github-v1-3 new file mode 100644 index 000000000..d2afdcc57 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v1-3 @@ -0,0 +1 @@ +Ckrf7U\/FCmVwvdr7t0BG3cahov9Tk7iAHndok\/3ViaDH8\/PKtxef1wJz4LhgWWNeyC\/PfjowGGpXLH41fQ2LClFSD2k\/mitsF0YKQEqJb6FPYqcr5sYp8hofQbUkzcyOZaRfj4Ip4ldUJfrNSg59igbGHUtqbCNCqRtoatwH2A7NivUeethsqsJ3NndABd7OiadBhHmSIDJdRbD\/XsDfk4RbD3zHqIGlOCXlhwoTIzpHif9ltb0XKnyibpmpFzUeFz340FpONK8QMDphJOBrypZhwdFwy9Oy\/QybjKpzJdgYZcHhjS8ZyKMNSEv85npoTx6PS5RHJidSMTFgntWhEuJqi\/WqDFMvj2P2OUHJHGhZbJ3YP18MSxgI8rDYPW9aZE6Z1w\/uHuO9rinqU8apnd7iFSZM9WyEouN9tfJqeZFPBQtYgmzro3\/Y1tJtcD6hLnNWEV+lDJwVzNcU1KHzpSuw7OdFO1dAKIysxOyFaMviSe3lKtiQdLxYJOSeVwFxH8QQOkG\/awVab\/q\/LbxP+YZdPSut8MTn6NRh6Zv4mnX0FNP0DtToV5rXICqaK\/FisNLizkUEHs6Tn5AVlwKx2xf00LsqNnSarTckRHaN9Q9eI\/yeV5sSrb1azfH8QM\/mvR9SyJVtH9L2QurJwom3jL2DoJSsUNdhZGNI5HMv7L9qjGeSS65vd\/hiFXJtP6+gw72Xj0m3Fk5YZImWxCLioxLtGBr1mKDFyyGFkYsGoapm1Cyg9xwGem9qazbl2g2J7DGrFMNEDBgPJzbBFLQ739DmxBnRwE5l6BZqwgqD54AqV1HSnCiB1nA2iWWJx2K6Y6ltAs7Ut5IemrGGHS2hObmegvenJN+YLAoE7Zd8zsq5RJFzDKXctRypCWA+Qi2n7kRS7mP9ycdSsoAmq6gzPuGnWl0igEQx5EjsXJCZ0xuU7iCEdr\/qrODeWzgsCGNzQLYliTX5yYicLqu6ewXOIJD8z8CoWA62ck6T68FioJLXkeLOIOQcs9UH3Pps99UKH8OqtTrvP3Ya77HNfAGMSWyzLd2dD+Mx7NPh7fEcpmnbJ1k\/ZahwHjJ\/NuCYHfrN1Q0znKbLoNwum\/I\/g2LLh2cz13X8csmCgD238cV96A26w54TdMP68N1QnmLG6pVQsVzyKKYRA03oDFNfX2pqYDLUO5fNxEz49O5caZyjPxC4uAIaKSOEpnaAv34bhWXYOAT3e1GzAWd+9FfR1agPqPY2yVIeOnLcqL5b18H1a3g144w+67ZgHOUI0fbGp+FexmKdKJ2yYKGKOPE3uuH6TQabTUxVSY8NYrb0n\/ZP+lcPpvQ+CLBzsnQvKYyOjQsU+UUvARJkOq9UZORlI4xQUzRFr5I5NjMixyp3x+NRC3j5lHcC6NjE3H9AuUGL\/SD0rSbpnNaXsA4vA1TIFWSNXANNPqjKADL++Q0\/qzuQvN\/zL\/pDabU87Hr9Ig4bZ6LQoxgN9kcFNq+NPvwXHGBcs5j8OgY4Gz4\/mN\/046vhr6r4ImL4\/OWxRQgx2lrTjDf9ff1EuXctwVqf6Gv85qzyK6Y\/\/vJtoHrxIZoMwdIY\/ojeBk0m3nVaDH42iJtifVMvA+5HCYSG03vKWPi9K+3rNIB245cq09ZWXB8CPs3mA0atVjb8JCrZxZg1aIHJRb4iyieDaLKrAlUAoMAnnQYarxPvFBUe\/9KfVt8GI9XuiiM= \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v2-1 b/btcrecover/test/test-wallets/blockchain-github-v2-1 new file mode 100644 index 000000000..bab3d553c --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v2-1 @@ -0,0 +1 @@ +{"pbkdf2_iterations":5000,"version":2,"payload":"iDQNpZ56UVZMiZ4Ack+SgHJFzEQk+3scybfil+zLNWBxrVyTQSCyV8xb769D/In5kO5N33ib+sJNMFGsMAWBQWYAyC7Ycp0ayQ/cmkQffO3vvsxSfXUKFrR7CfxXUZRa8J6wKcbfiKU4BFz+SLEcxf37uOgYojKfXWtGFxSBskn6qFBJaz5pJGBSfjxMCuKPHIheoNNkDASdnVxi15Q7F+ibUXwcThrMplETBf994mFn9vJamsJvbfe8WB7vBT6JFF89eTOff5gAKrg+9TrNcxrrmndy+/F7dO+0cM10tJdLk2jkBumGlqB/fH6c6SK/bOuwRQE2waMuMkhPcAdM2e9f4LYUCpE5NOcxCyA9IopSDj96byOT3iUTflLKpeZelKimSh9aljGHyadpopqoXK86uiE4JZBw2ihhR7UUiVOKw4RiO2PrI7PLIA9xL1+UHwG9FAvj9VrqsCCmv/mCdSk/cW5rB9N9VykrSJdp356HoAlY/ZdOEZZjI1UuJR3x184C/ygntqwupFZhbz53pWvTsj344mzYXjQcBCFD4fhdTGstAUjBpEsb1u7idn14a1Xuic1XvCHZMAX7xYl6dJ/3DLtfhpNk/xtSz9nBE7JlhPOeEA0PnT+x6uIOlrKNYxFUH+g+fhsgiIIJ6zf2d41T2dm6Qw4DqxEN8u3tuPvdEd6MwlE8IPGHWoG3fet6ru+4VfPoQVw4NjTpLUCngDeLHS29AQ3Ri4Kn1lkO6E1Kv3vdTY0vI0hunX7veSASv/NyeLa2Crq7b5K0TnnPPe4fCaNkE3i9YfLnmso/JMuSDpg8+p031IOzYY3s6I8d5bEO2gLLsqDFJxsVazaFmg=="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v2-2 b/btcrecover/test/test-wallets/blockchain-github-v2-2 new file mode 100644 index 000000000..1d40130c5 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v2-2 @@ -0,0 +1 @@ +{"pbkdf2_iterations":1000,"version":2,"payload":"nTru1Jhx1I3FpmvQa9d7Y8OmkCxLgBHFUP3VlSvv/nn3iirA6bJ3PjbktexpP5LYFOHgjAU5xoNRafeamv/pmQzjXywhDUrym0b0zaG807pOeA/MUCd9gV4RgcdDtcHImRwI47Sgmyxh5X1eVDci9Te+Q51rMioTrJUpO2+uY6GHk51b5LlMw8bUQLJ8avQjZ/vnojShfaQc4RWG33ct+l+bIAac1C4XHOYWXEN07ifuSd+LsOuyFYInC6uTVPHvzXkm/mlsaCD+eZacGWO7Dg/hgvp2RAUEmlhqbgkHRRGCtCbEUPVzzjARIbNRFi3zR2qgfChWxc2SQVSOvBTWmcQALkqlSeSBcp80O0rhN8JX+cPkbwmKePerXLebcRAApvz/Eu1nHvAKhXHtaX8jkQnbw3p2BnSsTsx0NURPQtvi8l11PoR8FrpmIa/h/EWFs8NJo54i14LucSb5aPIYwO+KH5oMjqzof3v0l1p4HYKtjopxnRk3EKyhOOgVNCtg/p5MtbffsTKVE1c8dq4/wmwQQUNZ9by4rVAcaHZJIELr+tkjq4PqrleFqn3BynGCXuPJOp668fLY3dhEKXD0ldGatP+OEar/ksqgygUnmUewZ9QtnGxLKhiFp2yaG8xuse41qFEdGjK4ysYezU7cJbjTgduq4mwg18Om5qDReOmVAt/srBMAbEHUt3mi/67TiRG0S569ykQjlSXfL0+olwLw2YBg50W4bLgUJo3ig4WILRhMGjWEASFluNCo/4RXMSsWr9qT9m+O25dDTk3Fr9ynp429MqICRLXmYm1LZrErZ5SFnAPbZkAnkctYvHLT8M9XqFBBjf85A4+SD2Hlug=="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v3-1 b/btcrecover/test/test-wallets/blockchain-github-v3-1 new file mode 100644 index 000000000..838d6bfcb --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v3-1 @@ -0,0 +1 @@ +{"pbkdf2_iterations":5000,"version":3,"payload":"tAAhCmDNFlZwKpJRcNBrb2dTTjVmu1NV2+WS9I+nz2d52M42VzhIoZpduo/pgj2A9oUjaItxLDNVEe7eGNcC6k2m07NWjYzxhCm5lJc7r3cQiwHWKsU4E8fOZVziYCHTZpnc20IjzVdqO3mbDtWVqAbS9Am3qFYqxGhhV+oeIicQiZjrPNhdcNihm9zhAm8wIU6f1ohFezB3T0GDc2v2PMpeYwVzl9X/fX1Cbdc52KMxxqqgA09R3SiOHxXBnKsRP8WsGEvyLa3X/VjXE3Z273aV7dQ0v8zKcYrifAW8MVZgGGhZcdCruArhbBYG7D8Lqf6+t42c2pa1M3qk2E4fRg43a6FWtNH95XCPPoZ9mVr5oETFkQzI8Th7zZI1jcvlE8WY1eQdk6cPXe4T4XKRFmWlXWVYPPk2RLDvGNHj9Om87pwBD31rNpX72i8lEOvWUsDBvuUYdK0DW27k8BhDXpWUEo2337EIRAqbWwOpK70t6UVSH5HhqqpdAxGIyJkamqPRuu/gwJY5owbPI9MPV2oUGcQPHGSYHhuWnG2E/uSo9fpqDTdPNlmCTYMsrdHKQkcNWBi/HPcNlOEKGusGGg7u+kMkrTP401pxwmpQ2ELXWSJPCwkOhb+ktQ4t0GRBC3P7eDcl8pQvzNEyijszG+nSHvJ9F7+OIsjsUaqOkDEgvk89GutaOz/xgj1WktdxOWw7rTkFFEIJd6So4CYWeb81ToKY9CKEiKhDTt+g/yRduCBA1fB8G4uK/N0zYWdFgNumNReIWCcfrMx4Aww5GD+1cd+R3EQtojTUC1H9qd2Tq2bXSgdWpkTGYyvpWH0FaIQwiI+su3GPwYh+35snjNja1CRGalGam/iTjDCoCO8+fGsUtq8tJJ1O9qTVa6NYhtuoDRsVvumlyFa1rXGLypBGJD5AK9wbBw+Upc6IMzHfzzMpnU5I6N/MPcaCME7uzxS2ZJ0pFvQ7NiANgfjj54ZiZJjI6d8qds7d5USdaHqtWBHTWm2C0mDOdn0JX2n1x1ixIlUlcYJq5tYbTKH6AGSdGibylTwcL3PssLZWC6AhtOWWwBsyRhJHbmYGUHzBJbQDRvQt462Nm3IwSBLocVANnzOj5mXEcm79/J+Uw97xA37KH1p6lwj2ly6+FkmK99vz9nDWI6FSpf46tjFt3BtSQvupW7UPsvgSCHoR3lQMoo4GqCONNCt+XF26iWmNI8VVY6oFLNwWbapZIS4xDR9A2iGL87q9icvEKxfUYBLp/BmmZl9wXdH1h0UiYgV8SBKblVfiT+MZcuRuIqpuqgtm8v4osD4JNN00oSQrGady8bPkp7sMo9yNm5oxRlMwQPs32swzqI6LsZoXGJu5dN4tseRx4ybci9G/SymhgKor0vi3kNwTFPKOfWbFJDUaob8rDLS+kSUOVDm+gAE25GNxja+Sd5tC/3ifrP+vsmxhO717X7ChLjIt5aisBAixAsR0GNKtpW1LWwbSNwHydgQH0s2Kd7MhNqJ3g2IJYmKiA8OwaCkiBPe6x/BNJVchnFcoIaFuKHjei+o2lI0LeJF4eKz2Vu0DXv2tCaIq+uYF0wJujAqg9FLyL49/SgrU499s2KR8wxMRtZyZTvERjkjRI6eGhyriGR3ztZRB8xLDR6I7VZevytymQXOwfd9zU/jjHOPAHuTAMH408nFCFQSxvfgOYjzptboyNPFugtg="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-v3-2 b/btcrecover/test/test-wallets/blockchain-github-v3-2 new file mode 100644 index 000000000..298db2fa4 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-v3-2 @@ -0,0 +1 @@ +{"pbkdf2_iterations":7520,"version":3,"payload":"mKN78QPyI/ipXzKshUNiEoieTpHJWrVo/E09To8wbg0AX37bVIzK5XbjyN/ZGcu7wOaboIdufwG3dK0tnH5CgAukyn+08Onl0GD8OM6iJ8NKMBFzadkPAW/LrQqzht1p3AOH2ALixg4nDZrWKidite7a04UvHx7RbLXNDmvRrsUgJSuIIQJ7TGIWWMy8C7fCvh+WEYHDxIgjUe6E4e9yQ8p6JpahFi5nn10ol1Pt9HBcgQfTWuFwGozz00aQUHQYOq7X57oxFYBwGor5HyjIgbE3lxAz/9yR2tOpA/ZFLPXbVppBTBOu6sR6blMtp7ZIOqYC/NXGYyVwlHlTZfZyRE6E7B1zRnWSS9NW/L2wHMaOZ6HpoyAtBUQo+Pu/ZiEPScAw4xvs8RSmNHGgDTxW4LGgUGDdOfc1Nivm9kAbYWZ11HOS2glLBxUkPpGCeUeQeB4tEd6q7BEAihWSv2sPETIJ21X2Dt6IdSQRAVfm4q8IGlBvSYbCEI1Ay2vFe2MdaZSAhM9ndhOcU3VMJe0+sHiI5oqdLhogPCpgVm0pnSVq5MerG7suqBd2KQgBczRp6ANkSY+O96lEKftSHV7N2UO3jIGiX5H/Txh61kJ00q79HSUcysUU9apuBipdYQUeNNYlDrUZLACP2U+Re7+GG8O5atFupDkJgYsLNkaZZpezPtu1gmp6DLxa2qtBHt2tzm5N0BlYCA/xUeiqRGP8B3Ea5Om/hS66zDVuqeoIn6NG060K9QLZ4OARSrDxStdY9Zagj6hQwy27+pCnSlAgBXHbecbsSO81irQz6AA8jcsnGgjKtItPghTYJTsaY9g3ZqDWF17OK2TNuZ1++aDbU2m0st3b0LDosGDjgMhc6HK0d9zvBvNsjk5Fd4FYjDWk7LMnQCDp06TQRT4ByJOqfQKNl2Pgyzp+QDZPljm+q8aTgASSSzuFmqobpXHEZO+/y0X9+TV43Pw+tXtXwVaLEFiETqoq6s7Kd4Tt3aML6zJ2UJfu7q2YcUqq8i9k+HDGpr0dcBGFSQt6DcUTdwddxzErgg6pRPTUP8y10rLy8Zd8O2A4WvWF88Dynv8ilNehwi5q47/pJNPiPABR1yiuwLw9/DJ7PIhzZGoJy62cJZ4nneJ0PwIPPtWC1nt+BrUqpP94uFOhAy2eP9bbXOpHshn82+hiP4ndkjeQvGVKJbfdSzZTd8i7UdzWm0bXhfdN3X+iAU6XwA09gUu0DtfN6sT3s6vanoSHB+S+ydZ804EZ9VuhKwnrWoWsLRkX/X5hPEPwW4hWlvxpTabKXO5XHQGi7A5l3weyKer9v3HDm1Q5sw/A8aLlJCYANjAkvRqp/ASTLeaKTt2S+lhuRbHZ6kTTfvWe0Injzejo3ElkbjQFpKyM4KAo2V2rU8uWWJoksAItKYTqDhqvddRz+VL3VPwLC4LWFpko1+87FVRhDApxNwNR0Ax+f3DfIA66K71TWZXDd7knCC6ZlUFRrLDsHGqPIBgXqykOX03rCeGAw9A7L7twqJ/r9ypy7B0ngv8ZM0Fpe8vyrojDM4tz0UUoepppmNez6I+rcDAk5N3E0c2eg7crxJVJwGapug8EKTF55c/jqUMx2xqtya8l8MnQmTIsn+aHeiQa4eHhF2llIfWE2sIalSS2X5fBHrfPm/6syCpwqkapLW2i9kb1H1MBgpV0lg6K2HrsriSeerNoM6M="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-github-wallet-data.json b/btcrecover/test/test-wallets/blockchain-github-wallet-data.json new file mode 100644 index 000000000..9a89d0ebd --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-github-wallet-data.json @@ -0,0 +1,314 @@ +// You will need to delete this comment block for this file to be valid JSON... +// A collection of blockchain.com wallet examples from https://github.com/blockchain/My-Wallet-V3-Android/ + +{ + "v3": [ + { + "extra_seed": "843865504b86dba66a9225963954e065d9f3ee558515232952943319ca8b29050c5613d80247f632b074d1d47d29325469cfdd9c5a81ba58b483628824c6b89d", + "payload": "{\"pbkdf2_iterations\":5000,\"version\":3,\"payload\":\"tAAhCmDNFlZwKpJRcNBrb2dTTjVmu1NV2+WS9I+nz2d52M42VzhIoZpduo/pgj2A9oUjaItxLDNVEe7eGNcC6k2m07NWjYzxhCm5lJc7r3cQiwHWKsU4E8fOZVziYCHTZpnc20IjzVdqO3mbDtWVqAbS9Am3qFYqxGhhV+oeIicQiZjrPNhdcNihm9zhAm8wIU6f1ohFezB3T0GDc2v2PMpeYwVzl9X/fX1Cbdc52KMxxqqgA09R3SiOHxXBnKsRP8WsGEvyLa3X/VjXE3Z273aV7dQ0v8zKcYrifAW8MVZgGGhZcdCruArhbBYG7D8Lqf6+t42c2pa1M3qk2E4fRg43a6FWtNH95XCPPoZ9mVr5oETFkQzI8Th7zZI1jcvlE8WY1eQdk6cPXe4T4XKRFmWlXWVYPPk2RLDvGNHj9Om87pwBD31rNpX72i8lEOvWUsDBvuUYdK0DW27k8BhDXpWUEo2337EIRAqbWwOpK70t6UVSH5HhqqpdAxGIyJkamqPRuu/gwJY5owbPI9MPV2oUGcQPHGSYHhuWnG2E/uSo9fpqDTdPNlmCTYMsrdHKQkcNWBi/HPcNlOEKGusGGg7u+kMkrTP401pxwmpQ2ELXWSJPCwkOhb+ktQ4t0GRBC3P7eDcl8pQvzNEyijszG+nSHvJ9F7+OIsjsUaqOkDEgvk89GutaOz/xgj1WktdxOWw7rTkFFEIJd6So4CYWeb81ToKY9CKEiKhDTt+g/yRduCBA1fB8G4uK/N0zYWdFgNumNReIWCcfrMx4Aww5GD+1cd+R3EQtojTUC1H9qd2Tq2bXSgdWpkTGYyvpWH0FaIQwiI+su3GPwYh+35snjNja1CRGalGam/iTjDCoCO8+fGsUtq8tJJ1O9qTVa6NYhtuoDRsVvumlyFa1rXGLypBGJD5AK9wbBw+Upc6IMzHfzzMpnU5I6N/MPcaCME7uzxS2ZJ0pFvQ7NiANgfjj54ZiZJjI6d8qds7d5USdaHqtWBHTWm2C0mDOdn0JX2n1x1ixIlUlcYJq5tYbTKH6AGSdGibylTwcL3PssLZWC6AhtOWWwBsyRhJHbmYGUHzBJbQDRvQt462Nm3IwSBLocVANnzOj5mXEcm79/J+Uw97xA37KH1p6lwj2ly6+FkmK99vz9nDWI6FSpf46tjFt3BtSQvupW7UPsvgSCHoR3lQMoo4GqCONNCt+XF26iWmNI8VVY6oFLNwWbapZIS4xDR9A2iGL87q9icvEKxfUYBLp/BmmZl9wXdH1h0UiYgV8SBKblVfiT+MZcuRuIqpuqgtm8v4osD4JNN00oSQrGady8bPkp7sMo9yNm5oxRlMwQPs32swzqI6LsZoXGJu5dN4tseRx4ybci9G/SymhgKor0vi3kNwTFPKOfWbFJDUaob8rDLS+kSUOVDm+gAE25GNxja+Sd5tC/3ifrP+vsmxhO717X7ChLjIt5aisBAixAsR0GNKtpW1LWwbSNwHydgQH0s2Kd7MhNqJ3g2IJYmKiA8OwaCkiBPe6x/BNJVchnFcoIaFuKHjei+o2lI0LeJF4eKz2Vu0DXv2tCaIq+uYF0wJujAqg9FLyL49/SgrU499s2KR8wxMRtZyZTvERjkjRI6eGhyriGR3ztZRB8xLDR6I7VZevytymQXOwfd9zU/jjHOPAHuTAMH408nFCFQSxvfgOYjzptboyNPFugtg=\"}", + "symbol_local": { + "symbol": "$", + "code": "USD", + "symbolAppearsAfter": false, + "name": "U.S. dollar", + "local": true, + "conversion": 172500.04312501 + }, + "payload_checksum": "7416cd440f7b15182beb15614a63d5e53b3a6f65634d2b160884c131ab336b01", + "war_checksum": "d1aeff0ddc48b949", + "language": "en", + "symbol_btc": { + "symbol": "BTC", + "code": "BTC", + "symbolAppearsAfter": true, + "name": "Bitcoin", + "local": false, + "conversion": 100000000 + }, + "storage_token": "8760ce3a41614a981b2a5bd776bb5603", + "sync_pubkeys": false + }, + { + "extra_seed": "e5b175f2d9d869ddf0caa6dd069f11268d45e7e7e97d965a710b12dced5ea3da7ae8cbea1a7ee1fca22826ebbe9c1a01b9dbd80e12034cee83510f57ed22a873", + "payload": "{\"pbkdf2_iterations\":7520,\"version\":3,\"payload\":\"mKN78QPyI/ipXzKshUNiEoieTpHJWrVo/E09To8wbg0AX37bVIzK5XbjyN/ZGcu7wOaboIdufwG3dK0tnH5CgAukyn+08Onl0GD8OM6iJ8NKMBFzadkPAW/LrQqzht1p3AOH2ALixg4nDZrWKidite7a04UvHx7RbLXNDmvRrsUgJSuIIQJ7TGIWWMy8C7fCvh+WEYHDxIgjUe6E4e9yQ8p6JpahFi5nn10ol1Pt9HBcgQfTWuFwGozz00aQUHQYOq7X57oxFYBwGor5HyjIgbE3lxAz/9yR2tOpA/ZFLPXbVppBTBOu6sR6blMtp7ZIOqYC/NXGYyVwlHlTZfZyRE6E7B1zRnWSS9NW/L2wHMaOZ6HpoyAtBUQo+Pu/ZiEPScAw4xvs8RSmNHGgDTxW4LGgUGDdOfc1Nivm9kAbYWZ11HOS2glLBxUkPpGCeUeQeB4tEd6q7BEAihWSv2sPETIJ21X2Dt6IdSQRAVfm4q8IGlBvSYbCEI1Ay2vFe2MdaZSAhM9ndhOcU3VMJe0+sHiI5oqdLhogPCpgVm0pnSVq5MerG7suqBd2KQgBczRp6ANkSY+O96lEKftSHV7N2UO3jIGiX5H/Txh61kJ00q79HSUcysUU9apuBipdYQUeNNYlDrUZLACP2U+Re7+GG8O5atFupDkJgYsLNkaZZpezPtu1gmp6DLxa2qtBHt2tzm5N0BlYCA/xUeiqRGP8B3Ea5Om/hS66zDVuqeoIn6NG060K9QLZ4OARSrDxStdY9Zagj6hQwy27+pCnSlAgBXHbecbsSO81irQz6AA8jcsnGgjKtItPghTYJTsaY9g3ZqDWF17OK2TNuZ1++aDbU2m0st3b0LDosGDjgMhc6HK0d9zvBvNsjk5Fd4FYjDWk7LMnQCDp06TQRT4ByJOqfQKNl2Pgyzp+QDZPljm+q8aTgASSSzuFmqobpXHEZO+/y0X9+TV43Pw+tXtXwVaLEFiETqoq6s7Kd4Tt3aML6zJ2UJfu7q2YcUqq8i9k+HDGpr0dcBGFSQt6DcUTdwddxzErgg6pRPTUP8y10rLy8Zd8O2A4WvWF88Dynv8ilNehwi5q47/pJNPiPABR1yiuwLw9/DJ7PIhzZGoJy62cJZ4nneJ0PwIPPtWC1nt+BrUqpP94uFOhAy2eP9bbXOpHshn82+hiP4ndkjeQvGVKJbfdSzZTd8i7UdzWm0bXhfdN3X+iAU6XwA09gUu0DtfN6sT3s6vanoSHB+S+ydZ804EZ9VuhKwnrWoWsLRkX/X5hPEPwW4hWlvxpTabKXO5XHQGi7A5l3weyKer9v3HDm1Q5sw/A8aLlJCYANjAkvRqp/ASTLeaKTt2S+lhuRbHZ6kTTfvWe0Injzejo3ElkbjQFpKyM4KAo2V2rU8uWWJoksAItKYTqDhqvddRz+VL3VPwLC4LWFpko1+87FVRhDApxNwNR0Ax+f3DfIA66K71TWZXDd7knCC6ZlUFRrLDsHGqPIBgXqykOX03rCeGAw9A7L7twqJ/r9ypy7B0ngv8ZM0Fpe8vyrojDM4tz0UUoepppmNez6I+rcDAk5N3E0c2eg7crxJVJwGapug8EKTF55c/jqUMx2xqtya8l8MnQmTIsn+aHeiQa4eHhF2llIfWE2sIalSS2X5fBHrfPm/6syCpwqkapLW2i9kb1H1MBgpV0lg6K2HrsriSeerNoM6M=\"}", + "symbol_local": { + "symbol": "$", + "code": "USD", + "symbolAppearsAfter": false, + "name": "U.S. dollar", + "local": true, + "conversion": 172028.21262687 + }, + "payload_checksum": "fc631f8434f45c43e7040f1192b6676a8bd49e0fd00fb4848acdc0dcaa665400", + "war_checksum": "d1aeff0ddc48b949", + "language": "en", + "symbol_btc": { + "symbol": "BTC", + "code": "BTC", + "symbolAppearsAfter": true, + "name": "Bitcoin", + "local": false, + "conversion": 100000000 + }, + "storage_token": "8760ce3a41614a981b2a5bd776bb5603", + "sync_pubkeys": false + } + ], + "v2": [ + { + "extra_seed": "c19301faaa4f3a6cfda5313e246f7c181d5a9994cb8b596da35e2e643b59101591131de7060b8aa0045a5a4839bb0910baa0057faf13880cfef86face1a25bc9", + "payload": "{\"pbkdf2_iterations\":5000,\"version\":2,\"payload\":\"iDQNpZ56UVZMiZ4Ack+SgHJFzEQk+3scybfil+zLNWBxrVyTQSCyV8xb769D/In5kO5N33ib+sJNMFGsMAWBQWYAyC7Ycp0ayQ/cmkQffO3vvsxSfXUKFrR7CfxXUZRa8J6wKcbfiKU4BFz+SLEcxf37uOgYojKfXWtGFxSBskn6qFBJaz5pJGBSfjxMCuKPHIheoNNkDASdnVxi15Q7F+ibUXwcThrMplETBf994mFn9vJamsJvbfe8WB7vBT6JFF89eTOff5gAKrg+9TrNcxrrmndy+/F7dO+0cM10tJdLk2jkBumGlqB/fH6c6SK/bOuwRQE2waMuMkhPcAdM2e9f4LYUCpE5NOcxCyA9IopSDj96byOT3iUTflLKpeZelKimSh9aljGHyadpopqoXK86uiE4JZBw2ihhR7UUiVOKw4RiO2PrI7PLIA9xL1+UHwG9FAvj9VrqsCCmv/mCdSk/cW5rB9N9VykrSJdp356HoAlY/ZdOEZZjI1UuJR3x184C/ygntqwupFZhbz53pWvTsj344mzYXjQcBCFD4fhdTGstAUjBpEsb1u7idn14a1Xuic1XvCHZMAX7xYl6dJ/3DLtfhpNk/xtSz9nBE7JlhPOeEA0PnT+x6uIOlrKNYxFUH+g+fhsgiIIJ6zf2d41T2dm6Qw4DqxEN8u3tuPvdEd6MwlE8IPGHWoG3fet6ru+4VfPoQVw4NjTpLUCngDeLHS29AQ3Ri4Kn1lkO6E1Kv3vdTY0vI0hunX7veSASv/NyeLa2Crq7b5K0TnnPPe4fCaNkE3i9YfLnmso/JMuSDpg8+p031IOzYY3s6I8d5bEO2gLLsqDFJxsVazaFmg==\"}", + "symbol_local": { + "symbol": "$", + "code": "USD", + "symbolAppearsAfter": false, + "name": "U.S. dollar", + "local": true, + "conversion": 172031.17204838 + }, + "payload_checksum": "110764d05c020d4818e2529ca28df9d8b96d50c694650348f885fc075f9366d5", + "war_checksum": "d1aeff0ddc48b949", + "language": "en", + "symbol_btc": { + "symbol": "BTC", + "code": "BTC", + "symbolAppearsAfter": true, + "name": "Bitcoin", + "local": false, + "conversion": 100000000 + }, + "storage_token": "f7331d04866aa41dbf5f57e7e28eb72e", + "sync_pubkeys": false + }, + { + "extra_seed": "224f165f53690ba05b7e2cefce4cd8990681d6624306b01d40fb041f14d1e6cf32c08132632fea25680a6b7dbf1b3fe7e16f99281cd21d7e008a265f8fdd06b1", + "payload": "{\"pbkdf2_iterations\":1000,\"version\":2,\"payload\":\"nTru1Jhx1I3FpmvQa9d7Y8OmkCxLgBHFUP3VlSvv/nn3iirA6bJ3PjbktexpP5LYFOHgjAU5xoNRafeamv/pmQzjXywhDUrym0b0zaG807pOeA/MUCd9gV4RgcdDtcHImRwI47Sgmyxh5X1eVDci9Te+Q51rMioTrJUpO2+uY6GHk51b5LlMw8bUQLJ8avQjZ/vnojShfaQc4RWG33ct+l+bIAac1C4XHOYWXEN07ifuSd+LsOuyFYInC6uTVPHvzXkm/mlsaCD+eZacGWO7Dg/hgvp2RAUEmlhqbgkHRRGCtCbEUPVzzjARIbNRFi3zR2qgfChWxc2SQVSOvBTWmcQALkqlSeSBcp80O0rhN8JX+cPkbwmKePerXLebcRAApvz/Eu1nHvAKhXHtaX8jkQnbw3p2BnSsTsx0NURPQtvi8l11PoR8FrpmIa/h/EWFs8NJo54i14LucSb5aPIYwO+KH5oMjqzof3v0l1p4HYKtjopxnRk3EKyhOOgVNCtg/p5MtbffsTKVE1c8dq4/wmwQQUNZ9by4rVAcaHZJIELr+tkjq4PqrleFqn3BynGCXuPJOp668fLY3dhEKXD0ldGatP+OEar/ksqgygUnmUewZ9QtnGxLKhiFp2yaG8xuse41qFEdGjK4ysYezU7cJbjTgduq4mwg18Om5qDReOmVAt/srBMAbEHUt3mi/67TiRG0S569ykQjlSXfL0+olwLw2YBg50W4bLgUJo3ig4WILRhMGjWEASFluNCo/4RXMSsWr9qT9m+O25dDTk3Fr9ynp429MqICRLXmYm1LZrErZ5SFnAPbZkAnkctYvHLT8M9XqFBBjf85A4+SD2Hlug==\"}", + "symbol_local": { + "symbol": "$", + "code": "USD", + "symbolAppearsAfter": false, + "name": "U.S. dollar", + "local": true, + "conversion": 172031.17204838 + }, + "payload_checksum": "31b162d3e1fd0b57d8b7dd1202c16604be221bde2fe0192fc0a4e7ce704d3446", + "war_checksum": "d1aeff0ddc48b949", + "language": "en", + "symbol_btc": { + "symbol": "BTC", + "code": "BTC", + "symbolAppearsAfter": true, + "name": "Bitcoin", + "local": false, + "conversion": 100000000 + }, + "storage_token": "f7331d04866aa41dbf5f57e7e28eb72e", + "sync_pubkeys": false + } + ], + "v1": [ + {"extra_seed":"fd38fe32d040f0381045bc5daf2a32da99af97700e2b710e1e1da4e528df85caad8faea46bb48b70787eab1347faa7f2c6b669b40b8737df6aae1b80ba1db804","auth_type":0,"initial_success":"Reminder: Verify your email.","real_auth_type":0,"payload":"VPXtmcNa7D9HhfmMT2KnTKOvoIhuQyh2vxZFvwwd24C2zKsUXLew1umUcV3ZGSXUO8Hmq7giuf3uYdFLXWOqAXsNlmyI9GL22xec8ZWEnYBiKd72fbY1EelcG4qhbBN1aaFKA9z3\/wkmkipBdHOyIT7xmHhLypm+8o53yv0tZcmFoNXVUcM3KIcuFjcPFSBWW1uCEClPXW54Xu76yY1Siyo6KLclA\/mQ7FWJ+F5unoRDJBUxRN5ebF1Vpyj+Cvs+ZTSRxiqzHAfHzSCdMs15LtKurS2AsLVTSkeX6MXYeofOVzh45dtqtnibYglBfMwoEZHjfRXwhps79jVemNtn0FrwFeFzzO4waC2G3yhaynVCNtc1IM0vhbh\/WXoTnZUYc3vaDT\/MstlARsV3XKPYzsCS0vgpEPeV\/FgCNpw8ADBFqpuMoS0aTmoybcHX1I09yTkuO3Ryo8DhD\/Z63oYDC5Q1VojLlYxAUioIgGwyivX\/NmC3IHXVyuVUHZGk0adIuyKvALGnuwOiC4liw1ENaEMg2mJFOdASWji+ryngoJbywmLt+vlfo3guHI\/viF\/6lCE+\/kakStAPcXI1a1gMmjXdBfXx5BCWBz+iofpb8dGXqh4znahY03vhlyesK7rG5NSZzNoFXSFjZh6uXfm1pVMhp23\/OBI9KdxnMnEH5IaldSH0fLUPMF\/EfHZmlThclh4ScWUtEIcjXZMUvQVYh5z4SzmUXB9\/JWvWp\/JY3tDd20y7J+gG\/vly\/Rlq1HTAx+Tl5bEDkBGHUGfok9kNtqymW\/w+Z86pR1CyA7Ummt5RSz+yQhRBR4890ajqiHIQAac8v+GIabh3AJXyPOZcclBnK\/W1ZP1VblVw8i9FtLhc2y9sbTQubjlhbhqbQFTdqebOUxER00hdeTtqNsBhVfVAtHDeEgGJHWoVaaw9vcr8TpqmjAz62UZIYPNOSZbLRiNjodx4VOKpCS2u\/XTkcDklHhusoEsYeZQp0LQqvnKyBq0q3ruy7zXdwBpOnC6tJ0kS3Xvc\/97wVSTIfA8wKVPNhbQ7TYPZFiAE6Y8bVb5wtCTfRC8i528eNmusZ\/RUO6pjRgA0YvA4np2QNx9x1AKrwC\/ujRopULMImHbGDKRuKX3HYRMHyfsg7KGgkimaYQG\/uYzM5s905sM5qPa0\/Z1Anm+b7JKMuLGO4\/XiCKkVhEGdkhxw1Tq7jZxFjm3HDuLEwVKbJprxlBzBWdYVV\/ucS+LklDgjeFuny\/aJdTGyWH6bXOwu8GJmIfQEjgf7","symbol_local":{"symbol":"$","code":"USD","symbolAppearsAfter":false,"name":"U.S. dollar","local":true,"conversion":164338.53738702},"guid":"9ebb4d4f-f36e-40d6-9a3e-5a3cca5f83d6","payload_checksum":"26c0477b045655bb7ba3e81fb99d7e8ce16f4571400223026169ba8e207677a4","war_checksum":"3642b083667c7f4d","language":"en","symbol_btc":{"symbol":"BTC","code":"BTC","symbolAppearsAfter":true,"name":"Bitcoin","local":false,"conversion":100000000.00000000},"sync_pubkeys":false}, + {"extra_seed":"7ffafcba53d3d014d13f7e33717bac748506fb1a2095d09a137851c628a3d27c584b3eb0f48c53f5e3b3f3fdf1a84d6ab91006def452e77127a42135b5c854f8","auth_type":0,"initial_success":"Reminder: Verify your email.","real_auth_type":0,"payload":"4gen70mZdnKx0YRnjpPSHTqz2UOMiEUGev9ZaU0L5daWQpqCBqZUgCIqEEbyNzmBQk6DhaQsReAOkzJz4L8q8VMeKk+\/h5JXIQTqxOwz0M470ZiyGnmKe7DFtEQGft3oAQAvc\/SA89gdu\/50SASEQk6fQPyRigkPunIrjmnxzWuO+Mak040Lea3qoScJXBY3xZG4C4ukJaFFhcZUTi+e45JYAg7AtmyONFFdcVLNpGjwRTkDvpAPobGJqMfOfFLHkdxNos+51khXYuyxEp3grU8jvwZbl2pCVgC1Z50IWFSUvSaZGvKkZaK5Ohw0Tn7RF4T4oUA4IrRYGpHY2F8yLUpcSJ47ctC90UPT7GITpDHA\/eQNpzdhtIv7Inkkza\/Okd5blx+59he+x\/AQFTdyc5YZmsEgN+g\/RUN06UNibe78iyqEEN4q88RiLDAwFMoHY4cYtzyd25CSza0NP+yBFFLf+NbKA64Ck2pItMgr4JXkCVU8shQmtKnVKfO8mC3MQSF+kZE0mClr7URa8LIbMhmGgQ3o2vGNbRiutzdO7\/L52F7GWTHJzKEo5FdWK2K218Spd0L31TpBn3aKXg6BLgw6WiggztP4hP4pVf6LK21KvgPKf3NegF4Or7wfDz1\/mP4uiz+xf18trCuFHoityhXduwrrtA9bhhf2SwLE4+Md9R+m1KOQ6q63ynujM0oeIGVj6NIH4uJS6pz\/3iIKZTkr\/4\/5TdEw8uBlKHHxJHBHBuHPsYN3bpd4A+VMiNn3EPV02aD7xCq6anFPvoYbj8BHW0p6kUHP32fcuETciO7NF0NLGSIoE3iTNHRSpXt58BEHIU\/p4Tx4KczwLfUiVvARzjnkF8KQMPrgzhZysiXhH8cwdw3LPU1hi0zDuyW3RR7UQxuCTOa\/T8v0ZnGZhFNIFkocwSvcDStef807vg6bu2Fe4BewCLzHLQDEudTUHxiFsWWfzo4E4\/3pCTNgAkI4N0swvUR7aBTQHminudxrdSxC2f\/6pgBnd4TqBv+W16qcWLf31x\/yV1wHFyG2rfTpbpTx07W4r3G4zoDq8XHecpfuDy2H+GKgctSyUSDO9BTb6Q93+JyrMDvuw2o4ennAmll8gQ8C5WjxsQ78oES2\/\/yeCqTgKQiEx121Dwa2\/MiposMiRg82pwMXZ7lERx74CQnII54z4YAHGA2EqtQwwNdzdCufJqGV8oHaEPAXRNnGuAK6fbDUlM10GiSQoCpvzmDtv5n3RYlXXgmXAjmDb1CodLEFqCps\/lPyAvav","symbol_local":{"symbol":"$","code":"USD","symbolAppearsAfter":false,"name":"U.S. dollar","local":true,"conversion":164338.53738702},"guid":"2ca9b0e4-6b82-4dae-9fef-e8b300c72aa2","payload_checksum":"57f97ace89c105c19c43a15f2d6e3091d457dec804243b15772d2062a32f8b7d","war_checksum":"3642b083667c7f4d","language":"en","symbol_btc":{"symbol":"BTC","code":"BTC","symbolAppearsAfter":true,"name":"Bitcoin","local":false,"conversion":100000000.00000000},"sync_pubkeys":false}, + {"extra_seed":"efd75f2cbfb5a83bb6a9056d04b3f427e5c2547ec9b181d2bc719dafc3bb9f469cfa5c9935af1d09efe86091aaab674cad5f2e78b154032a76ee271c3d2c2f17","auth_type":0,"initial_success":"Reminder: Verify your email.","real_auth_type":0,"payload":"Ckrf7U\/FCmVwvdr7t0BG3cahov9Tk7iAHndok\/3ViaDH8\/PKtxef1wJz4LhgWWNeyC\/PfjowGGpXLH41fQ2LClFSD2k\/mitsF0YKQEqJb6FPYqcr5sYp8hofQbUkzcyOZaRfj4Ip4ldUJfrNSg59igbGHUtqbCNCqRtoatwH2A7NivUeethsqsJ3NndABd7OiadBhHmSIDJdRbD\/XsDfk4RbD3zHqIGlOCXlhwoTIzpHif9ltb0XKnyibpmpFzUeFz340FpONK8QMDphJOBrypZhwdFwy9Oy\/QybjKpzJdgYZcHhjS8ZyKMNSEv85npoTx6PS5RHJidSMTFgntWhEuJqi\/WqDFMvj2P2OUHJHGhZbJ3YP18MSxgI8rDYPW9aZE6Z1w\/uHuO9rinqU8apnd7iFSZM9WyEouN9tfJqeZFPBQtYgmzro3\/Y1tJtcD6hLnNWEV+lDJwVzNcU1KHzpSuw7OdFO1dAKIysxOyFaMviSe3lKtiQdLxYJOSeVwFxH8QQOkG\/awVab\/q\/LbxP+YZdPSut8MTn6NRh6Zv4mnX0FNP0DtToV5rXICqaK\/FisNLizkUEHs6Tn5AVlwKx2xf00LsqNnSarTckRHaN9Q9eI\/yeV5sSrb1azfH8QM\/mvR9SyJVtH9L2QurJwom3jL2DoJSsUNdhZGNI5HMv7L9qjGeSS65vd\/hiFXJtP6+gw72Xj0m3Fk5YZImWxCLioxLtGBr1mKDFyyGFkYsGoapm1Cyg9xwGem9qazbl2g2J7DGrFMNEDBgPJzbBFLQ739DmxBnRwE5l6BZqwgqD54AqV1HSnCiB1nA2iWWJx2K6Y6ltAs7Ut5IemrGGHS2hObmegvenJN+YLAoE7Zd8zsq5RJFzDKXctRypCWA+Qi2n7kRS7mP9ycdSsoAmq6gzPuGnWl0igEQx5EjsXJCZ0xuU7iCEdr\/qrODeWzgsCGNzQLYliTX5yYicLqu6ewXOIJD8z8CoWA62ck6T68FioJLXkeLOIOQcs9UH3Pps99UKH8OqtTrvP3Ya77HNfAGMSWyzLd2dD+Mx7NPh7fEcpmnbJ1k\/ZahwHjJ\/NuCYHfrN1Q0znKbLoNwum\/I\/g2LLh2cz13X8csmCgD238cV96A26w54TdMP68N1QnmLG6pVQsVzyKKYRA03oDFNfX2pqYDLUO5fNxEz49O5caZyjPxC4uAIaKSOEpnaAv34bhWXYOAT3e1GzAWd+9FfR1agPqPY2yVIeOnLcqL5b18H1a3g144w+67ZgHOUI0fbGp+FexmKdKJ2yYKGKOPE3uuH6TQabTUxVSY8NYrb0n\/ZP+lcPpvQ+CLBzsnQvKYyOjQsU+UUvARJkOq9UZORlI4xQUzRFr5I5NjMixyp3x+NRC3j5lHcC6NjE3H9AuUGL\/SD0rSbpnNaXsA4vA1TIFWSNXANNPqjKADL++Q0\/qzuQvN\/zL\/pDabU87Hr9Ig4bZ6LQoxgN9kcFNq+NPvwXHGBcs5j8OgY4Gz4\/mN\/046vhr6r4ImL4\/OWxRQgx2lrTjDf9ff1EuXctwVqf6Gv85qzyK6Y\/\/vJtoHrxIZoMwdIY\/ojeBk0m3nVaDH42iJtifVMvA+5HCYSG03vKWPi9K+3rNIB245cq09ZWXB8CPs3mA0atVjb8JCrZxZg1aIHJRb4iyieDaLKrAlUAoMAnnQYarxPvFBUe\/9KfVt8GI9XuiiM=","symbol_local":{"symbol":"$","code":"USD","symbolAppearsAfter":false,"name":"U.S. dollar","local":true,"conversion":164338.53738702},"guid":"4077b6d9-73b3-4d22-96d4-9f8810fec435","payload_checksum":"a4b67f406268dced75ac5c628da854898c9a3134b7e3755311f199723d426765","war_checksum":"3642b083667c7f4d","language":"en","symbol_btc":{"symbol":"BTC","code":"BTC","symbolAppearsAfter":true,"name":"Bitcoin","local":false,"conversion":100000000.00000000},"sync_pubkeys":false} + ], + + "v3_credentials": [ + { + "guid": "e5eba801-c8bc-4a64-99ba-094e12a80766", + "password": "SomeTestPassword", + "iterations": 5000, + "sharedKey": "db81b289-8cac-46a9-ac0d-0c0658ec0dfa", + "version": 3, + "double_encryption": false + }, + { + "guid": "e5eba801-c8bc-4a64-99ba-094e12a80766", + "password": "SomeTestPassword", + "iterations": 7520, + "sharedKey": "db81b289-8cac-46a9-ac0d-0c0658ec0dfa", + "version": 3, + "double_encryption": false + } + ], + "v2_credentials": [ + { + "guid": "5f071985-01b5-4bd4-9d5f-c7cf570b1a2d", + "password": "SomeTestPassword", + "iterations": 5000, + "sharedKey": "ecfc1a81-cccb-4085-a260-ef7cb74b847e", + "version": 2, + "double_encryption": false + }, + { + "guid": "5f071985-01b5-4bd4-9d5f-c7cf570b1a2d", + "password": "SomeTestPassword", + "iterations": 1000, + "sharedKey": "ecfc1a81-cccb-4085-a260-ef7cb74b847e", + "version": 2, + "double_encryption": false + } + ], + "v1_credentials": [ + { + "guid": "9ebb4d4f-f36e-40d6-9a3e-5a3cca5f83d6", + "password": "mypassword", + "iterations": 10, + "mode": "CBC", + "padding": "Iso10126", + "sharedKey": "41cf823f-2dcd-4967-88d1-ef9af8689fc6", + "version": 1, + "double_encryption": false + }, + { + "guid": "2ca9b0e4-6b82-4dae-9fef-e8b300c72aa2", + "password": "mypassword", + "iterations": 10, + "mode": "CBC", + "padding": "Iso10126", + "sharedKey": "e8553981-b196-47cc-8858-5b0d16284f61", + "version": 1, + "double_encryption": false + }, + { + "guid": "4077b6d9-73b3-4d22-96d4-9f8810fec435", + "password": "mypassword", + "secondpassword": "mysecondpassword", + "iterations": 1, + "mode": "CBC", + "padding": "Iso10126", + "sharedKey": "fa1beb37-5836-41d1-9f73-09f292076eb9", + "version": 1, + "double_encryption": true + } + ], + + "v3_payload":[ + { + "guid": "e5eba801-c8bc-4a64-99ba-094e12a80766", + "sharedKey": "db81b289-8cac-46a9-ac0d-0c0658ec0dfa", + "double_encryption": false, + "options": { + "pbkdf2_iterations": 5000, + "fee_per_kb": 10000, + "html5_notifications": false, + "logout_time": 600000 + }, + "address_book": [], + "tx_notes": {}, + "tx_names": [], + "keys": [], + "hd_wallets": [ + { + "seed_hex": "bfb70136ef9f973e866dff00817b8070", + "passphrase": "", + "mnemonic_verified": false, + "default_account_idx": 0, + "accounts": [ + { + "label": "My Bitcoin Wallet", + "archived": false, + "xpriv": "xprv9xvLaqAsee2mgFsgMQsVLCTh858tA559kD9wczD5nYGJMa4M56MvLgYGGn75MSdDFZSBYeYeCgAZdqKQitXux3ebiTi67eYH1a1VS2rdKZW", + "xpub": "xpub6BugzLhmV1b4tjx9TSQVhLQRg6yNZXo17S5YRNchLsoHENPVcdgAtUrk82X5LNuaViWoxsqMhCd3UBxhQRHvyrUeqqA7tupvSpkoC73nhL1", + "address_labels": [], + "cache": { + "receiveAccount": "xpub6EwJFaULAi1c8MRJH7mGSbWUzfQEuspEH1nqCCQXAcWKjStsFJrQXwzGVUnkWwxfegDWr5DDXKLrYDWTFdkq9qFxK1n3LUxphLtTcUn4bfD", + "changeAccount": "xpub6EwJFaULAi1cCCYB2SiQy25KKFs4SRkWu1kwgrGqnFUbEDWvAMWuLiErSkUcRBUR6eTcU14cxYSPQ8scuHeWrL79fppGaVxrwy38yRvbVXz" + } + } + ] + } + ] + }, + { + "guid": "e5eba801-c8bc-4a64-99ba-094e12a80766", + "sharedKey": "db81b289-8cac-46a9-ac0d-0c0658ec0dfa", + "double_encryption": false, + "options": { + "pbkdf2_iterations": 7520, + "fee_per_kb": 10000, + "html5_notifications": false, + "logout_time": 600000 + }, + "address_book": [], + "tx_notes": {}, + "tx_names": [], + "keys": [], + "hd_wallets": [ + { + "seed_hex": "bfb70136ef9f973e866dff00817b8070", + "passphrase": "", + "mnemonic_verified": false, + "default_account_idx": 0, + "accounts": [ + { + "label": "My Bitcoin Wallet", + "archived": false, + "xpriv": "xprv9xvLaqAsee2mgFsgMQsVLCTh858tA559kD9wczD5nYGJMa4M56MvLgYGGn75MSdDFZSBYeYeCgAZdqKQitXux3ebiTi67eYH1a1VS2rdKZW", + "xpub": "xpub6BugzLhmV1b4tjx9TSQVhLQRg6yNZXo17S5YRNchLsoHENPVcdgAtUrk82X5LNuaViWoxsqMhCd3UBxhQRHvyrUeqqA7tupvSpkoC73nhL1", + "address_labels": [], + "cache": { + "receiveAccount": "xpub6EwJFaULAi1c8MRJH7mGSbWUzfQEuspEH1nqCCQXAcWKjStsFJrQXwzGVUnkWwxfegDWr5DDXKLrYDWTFdkq9qFxK1n3LUxphLtTcUn4bfD", + "changeAccount": "xpub6EwJFaULAi1cCCYB2SiQy25KKFs4SRkWu1kwgrGqnFUbEDWvAMWuLiErSkUcRBUR6eTcU14cxYSPQ8scuHeWrL79fppGaVxrwy38yRvbVXz" + } + } + ] + } + ] + } + ], + "v2_payload":[ + { + "guid": "5f071985-01b5-4bd4-9d5f-c7cf570b1a2d", + "sharedKey": "ecfc1a81-cccb-4085-a260-ef7cb74b847e", + "double_encryption": false, + "options": { + "pbkdf2_iterations": 5000, + "fee_per_kb": 10000, + "html5_notifications": false, + "logout_time": 600000 + }, + "address_book": [], + "tx_notes": {}, + "tx_names": [], + "keys": [ + { + "addr": "13CeRoCTgksr5A9276bLsAiM79i9hjMVLF", + "priv": "7ZefoHSaGKCspUAfdvLUrRZ2cQKb85381zd7rfKYTxki", + "tag": 0, + "created_time": 1471949131308, + "created_device_name": "javascript_web", + "created_device_version": "3.0" + } + ] + }, + { + "guid": "5f071985-01b5-4bd4-9d5f-c7cf570b1a2d", + "sharedKey": "ecfc1a81-cccb-4085-a260-ef7cb74b847e", + "double_encryption": false, + "options": { + "pbkdf2_iterations": 1000, + "fee_per_kb": 10000, + "html5_notifications": false, + "logout_time": 600000 + }, + "address_book": [], + "tx_notes": {}, + "tx_names": [], + "keys": [ + { + "addr": "13CeRoCTgksr5A9276bLsAiM79i9hjMVLF", + "priv": "7ZefoHSaGKCspUAfdvLUrRZ2cQKb85381zd7rfKYTxki", + "tag": 0, + "created_time": 1471949131308, + "created_device_name": "javascript_web", + "created_device_version": "3.0" + } + ] + } + ], + "v1_payload":[ + {"guid":"9ebb4d4f-f36e-40d6-9a3e-5a3cca5f83d6","sharedKey":"41cf823f-2dcd-4967-88d1-ef9af8689fc6","double_encryption":false,"options":{"fee_per_kb":10000,"html5_notifications":false,"logout_time":600000},"address_book":[],"tx_notes":{},"tx_names":[],"keys":[{"addr":"1AN6WAdSrXATk7mcosqhK7vvPusC3fmw8p","priv":"XxkpqJqs1449nLHX1WcCP5QbmyJhjrwTis9hyvqGKJc","tag":0},{"addr":"1Cp1xPTVdjePVcRAJHxesiLkNZbASgbkL","priv":"8uZW7qiz89NGtgwzdbqKZ9QDVPj1NeGV192RJUXdc946","tag":0},{"addr":"1MsA1JJhgNMkuke9uttaXQfFyNBvmCyeiR","priv":"BH2MMphxoWQkynoo7HM8ukLSw8aNKFT9sWV2nu9QmNqe","tag":0},{"addr":"13m3hC95Pp3ZWth6XCmhr9qUVNZGqMchia","priv":"4TE4wCgz1fcjSPsxSUUig36a9JpNfPn9c2KFcT94hWtk","tag":0},{"addr":"17thFxEzSsR3fn2CpQrAYrcsUve6VN8JuX","priv":"2YARBSUuHrsuuvtEHoeTXiDXGdiDuHtfdLvvYCKkADCm","tag":0}]}, + {"guid":"2ca9b0e4-6b82-4dae-9fef-e8b300c72aa2","sharedKey":"e8553981-b196-47cc-8858-5b0d16284f61","double_encryption":false,"options":{"fee_per_kb":10000,"html5_notifications":false,"logout_time":600000},"address_book":[],"tx_notes":{},"tx_names":[],"keys":[{"addr":"18oiLmwwRmw25jK3Go72cieUaNsq5eZq2a","priv":"7nkCC1DvFBoWQd8EVUmSEMRawwnmMZVH6ZSmJM1Rog4r","tag":0},{"addr":"1PFf4ruwpU8LoNPyzFLNX7aLFtT3AhbQxW","priv":"dnuY1XXVPSL78Ths3q5vQwZWrmbsqr3yiuknAUWYVaX","tag":0},{"addr":"13JEiKew1Ee3H8K255RNuxd3yYMsqyXHtz","priv":"ExsEQwQpCsMunoBMReUzQ4kQFPKm4KTBnH9sym4wCCAT","tag":0},{"addr":"15bhQZEbL3815K93ceHYBf5rekd2GEThEK","priv":"8rGeLoELr8xLpuNHTA8XtYawY7QJGHQrzvjF9PKN581r","tag":0},{"addr":"1JNwKdMZJAeonq2uUFcV5NuU8tSpHXb3bD","priv":"Gsc8jrwa11pCuEzvhuABWC9XePQa44jz78ZZJkKyVUDu","tag":0}]}, + {"guid":"4077b6d9-73b3-4d22-96d4-9f8810fec435","sharedKey":"fa1beb37-5836-41d1-9f73-09f292076eb9","double_encryption":true,"dpasswordhash":"015f7b18029812bc45f437a515a915e9ebec2b700181585c99074d79495a8746","options":{"fee_per_kb":10000,"html5_notifications":false,"logout_time":600000},"address_book":[],"tx_notes":{},"tx_names":[],"keys":[{"addr":"1BqsBxRsk7J4oEs4cnvTLHQ6eoDZwTsApT","priv":"UgHv/muoXWWxdEsnWQcTV93Pi/GG2W05sDq/mJVqtGCUOAbfjXNhBPkmjEeYxoDxNzOoGeWyQiZiA6oob9xRPQ==","tag":0},{"addr":"1MfHdTCHUX3f7aeFTgF4KhCC1yTZgDiDDt","priv":"s3pfJhVPWxNrkpLhrxXDCXGxvFDsu5kccI5SbBDkTK6dDJj1drFSfuiZe7iN3GTG7qEQUWh9b83Hr7zTck7EIA==","tag":0},{"addr":"16NjzqM9X8qzhHkdLLVZbCwWKqLK5UmdH4","priv":"32VZvkqH2CQxIAKntDZUaGUKBIa4YMqhti/uGa5FpXuqknaGxEJ1LsnkIwduTOBSvySW6IJgjT7u3FMm21NvzQ==","tag":0},{"addr":"1PQj2cst9Bnd5cXp84XQffCDZEmE6KczPf","priv":"Uwnoi10UxKURitflyokc3Zjz53FlXvfosuk8jEhNm12lzKzZgiosgBZRp+vMsfgz7xR+JnAknlsCSGF/VttHlQ==","tag":0},{"addr":"149aV6sNHGYSCreUmu7KVDdou5udyjC9rK","priv":"f5Y7nfO2noqxuYoxWvByNE25TFcGafjib+C49XCxH+DgmC3cEkvjtjODI5TcbVm9Ixv7b2OVJZJTDL0EEp/aVw==","tag":0}]} + ] +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v0.0-Jan2014-wallet.aes.json b/btcrecover/test/test-wallets/blockchain-v0.0-Jan2014-wallet.aes.json new file mode 100644 index 000000000..8e8359af1 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v0.0-Jan2014-wallet.aes.json @@ -0,0 +1 @@ +ajZ3Vm8hUYkmBVBqdAQl2iIRSFe7k8rCUEvtrcVWOPaap9onSyLUG3j4xALbuSWdrI0nsi0hBulC2otaO6Im9Ath1708z2C7WAXvjqhtmrdRm5in+cbni3ghgxT/toG2V3Hd2d+QLrgUEaPNxc4v/LwMl/UdXWi2lBdFY5k0PH1WpOdAj5ya3HDvNu/Tte+7phTsmixCw1AMNMOGMQUJIznJ58d7PDjV8VLIH5Cx/KVc275lCN0gM5TX6XWXOqNFv36XLGMJIrkDVe4+MaKhlfAlrYz9GBvPo4AJUQdnbV0KEa7VB2X4jEUqk72po8HE5yFrKwV0LCmcUfnMAs6Le4q/7B2D6wWqhh1jAAxGj8kAYLiMbHO/xCtURWyUw9fnIfnB4Ldc4txG1ud2K63iHJcBtUrN2pwE07reM6yJO4JEGVYeEoIgYUEsNcEBjLqUaIHOC8IjH763jqn53CP9oA== \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v0.0-march2012-uncompressed-wallet.aes.json b/btcrecover/test/test-wallets/blockchain-v0.0-march2012-uncompressed-wallet.aes.json new file mode 100644 index 000000000..f913ef2b8 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v0.0-march2012-uncompressed-wallet.aes.json @@ -0,0 +1 @@ +Tex1TnplnL/WmUoNRYDBDdwfK9g7WwBqixcqu8PwSh6G9wRqHfLQ9yWQ6+aDmqSfwVlJvkTrQPwdfOhtDl9hctGffmlclGOeJs/G+QmnbGosgTViqQs/LLOroSm658w/PaQx5Eo/AQ5LSj42X+HcCzGWjNHf6tJM92WO68tWq0gzc/cRDS4yO6t+Rj2Fnm+AiAtrkGi7DHjKlCdDUMqdzF5MsPfJ6TdRoz4xDVHrDoNkDHQVn8I5xRC8YMGCL4TjsLK8idN/gTIous2yiVlkTG3Jf5a97VZEUgYLsd9HHarInDLl5n1i1QP7/ATz3ViAVegZx8/J \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_main_dump.txt b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_main_dump.txt new file mode 100644 index 000000000..03e3a8568 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_main_dump.txt @@ -0,0 +1,25 @@ +{ + "guid": "9bb4c672-563e-4806-9012-a3e8f86a0eca", + "sharedKey": "728e769e-4c26-4301-a0b8-2db25c564c5a", + "double_encryption": true, + "dpasswordhash": "2de3fba5e1bce771e7425686b30970a70b6a5cac1aff5acbc9e1b3bca365f47a", + "options": { + "pbkdf2_iterations": 10000, + "fee_policy": 0, + "html5_notifications": false, + "logout_time": 600000, + "tx_display": 0, + "always_keep_local_backup": false, + "transactions_per_page": 30, + "additional_seeds": [] + }, + "keys": [ + { + "addr": "1BUoAfr3N6RcH3JArntzoFGfk9hrgU4i5a", + "priv": "3BxRTG45Rf1v4V0zJLb1SdUULBIlAPPvTzo2lkMHbusI0MdaMO+wspiT7ZSorervEHq722+liMEu5azs6DUvsQ==", + "created_time": 0, + "created_device_name": "javascript_web", + "created_device_version": "1.0" + } + ] +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_dump.txt b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_dump.txt new file mode 100644 index 000000000..665863c92 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_dump.txt @@ -0,0 +1,28 @@ +{ + "guid": "9bb4c672-563e-4806-9012-a3e8f86a0eca", + "sharedKey": "728e769e-4c26-4301-a0b8-2db25c564c5a", + "double_encryption": true, + "dpasswordhash": "2de3fba5e1bce771e7425686b30970a70b6a5cac1aff5acbc9e1b3bca365f47a", + "options": { + "pbkdf2_iterations": 10000, + "fee_policy": 0, + "html5_notifications": false, + "logout_time": 600000, + "tx_display": 0, + "always_keep_local_backup": false, + "transactions_per_page": 30, + "additional_seeds": [] + }, + "keys": [ + { + "addr": "1BUoAfr3N6RcH3JArntzoFGfk9hrgU4i5a", + "priv": "3BxRTG45Rf1v4V0zJLb1SdUULBIlAPPvTzo2lkMHbusI0MdaMO+wspiT7ZSorervEHq722+liMEu5azs6DUvsQ==", + "created_time": 0, + "created_device_name": "javascript_web", + "created_device_version": "1.0", + "priv_decrypted": "8V3qAkjVPsWgWASdvyXSSvkZ9A3obdEzwfMpavJ8MD5A", + "privkey_compressed": "KzwrWpHpu5zyZXDDsV4MWkDhobhji46kk2YYpWr4vwDxYLHa3NXs", + "privkey_uncompressed": "5JfFqtx1tDv1aef7YNmG52BaiNTSzLHFttZ53A2bsjeNeXKKkUc" + } + ] +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_privkeys.txt b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_privkeys.txt new file mode 100644 index 000000000..b00d36a35 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_privkeys.txt @@ -0,0 +1,3 @@ +Private Keys (For copy/paste in to Electrum) are below... +KzwrWpHpu5zyZXDDsV4MWkDhobhji46kk2YYpWr4vwDxYLHa3NXs +5JfFqtx1tDv1aef7YNmG52BaiNTSzLHFttZ53A2bsjeNeXKKkUc diff --git a/btcrecover/test/test-wallets/blockchain-v3.0-Jan2021-Android.json b/btcrecover/test/test-wallets/blockchain-v3.0-Jan2021-Android.json new file mode 100644 index 000000000..924292293 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v3.0-Jan2021-Android.json @@ -0,0 +1 @@ +{"payload":"rEFcfGan5iUD6CRaAnMKUw45OPMI+S20U0VpMeC+9fYX9STLS/tNdD3RCJh7uOzf9u0A5XNN6xqjK8S+BSgJ6GpAOS0PwCM0QnWzuPVmVL0VxdAcd1W/6w/Yo2z8ljNtdEfqZ2+Lenq4FgUSxwt60QaS7+AbJrVLXd6gUtEs3NfS6Y2sBXVDrPUUuqGAHNKp39dtNaQlG/QBwIkN0HhpYCX/phOMwb080d2OzkWF7EiMvKRLPGOOQAWUCUr8H6soIDk5qB2Mq2NskZ1SF1HYNxC2wDTCQJcJmXU1B0bYBy4RVHqD5iQqSeVohNCq1d5DK4BVRw0rR9r26tBqWwacgNZ84010sz06wBHcs9IDfyIvXS4fItKSD61mb7HuEZ3d9q0gPOrwXeMMLJqYW52x7DrSttv87N5fSfBddvbvs/v2/Gd2Ncm5JOHVHtU0+UcpbImxpgAleWBqia/OrQO8D3YH1TqGmr82c9JEViR9iFRMkLzt9s06mzcBE45ojS8kUrAvk6/kaiZRLdQZdoKdAr9UnPZ91aQYALwJlJuCJCXV7hM8wd/XL/n0yRVfe0SihmO63d6wL8zNMHfAJf031EaVZ3C+mWSMrpppjj/NqB8seG4fFSM9jIXJU6dcH8vN01uA5mkDCGhz8nsPnBxJuXMThS/BsudCh7LMLFeNexpO9kD4YSRHMQcMXpG8EGHotDTsmcqq77Hl0NeRMa1FNP+Rykc73r4AD9Cz95oYJuUYRrmeUWm48YaDULUIZUDnDoPlNhLp5G6WK1mN+y8FcfFQRPqUZ4b0cHHbOI4S+MLh7xY0Qr6UBfviKxFDXM2zQ7HJOy6YEg4ln+MvASC2Vy+ahJVIHHU5xNEyIAnInTlfsXs7i4dbKnKeiwM/ruWq1TnRNHDBvpONxTehh97ZagVwT8JImq+aM9KOm1+hjrv512uF0OW483RbOlgXcBHxOyIuNz7wyFf4I5zXp91rjQ==","pbkdf2_iterations":5000,"version":3} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json b/btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json new file mode 100644 index 000000000..a1e4ca1ec --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json @@ -0,0 +1 @@ +{"pbkdf2_iterations":5000,"version":3,"payload":"4kWTa0woqHOaCOfbwFm4TgOhuRvmvkNkpvBsGXKIyyRXzKUBoc1/Wv8hckcLwt8E1lfTVn9Gmm0wjYRe1mgzINHBC8WAWIkQbc3LPoql3CTfUYrDqi6MP+WaeAAwWIIPCXHcQQeTPyav1rgdcnGylyRrZ7nsvqhncTqEBpx4BJQsL7zj8iEn+iKynkPLmoZxBBOeHuJhL5TwLoAt37EzwzdXT5fMybgTMkj97V8YXZ0x9gZM0/ROoGAHiIjYIWMfZofHAzD37YBfo8hla24YXaXUqzHzBtuuU9oJznl6cn5O4lQNxzoN1jafFysxIkkExe4g1fNZkRC0qRFCxXsFFF7l7jMI1gtDEx73YMpEnFXBax9xz2mdlifgGzbSkDXCxI8gqZ4AlA/axzniYoGWpdHRv4wfqiyPi3VsiZXWrS+QNror/xRR6ECSuImNfenvhNPmQdR6Q6LhILYUGkZ/hSFJz5FKn058p3kKY95BCy4+3MJI5KP6Bqbm6AvtiAnF1cAMTL8FJzI9WglzJtb29shbaFo+wj9viH77lCaucap8/KixyQvD+1cMNQ6bFI0nGF+pk7NjLeVM8q5OHgFeCKeBkdVRyOreE9m493d+TYs3bD6QuLaPBUi0UE0uTW5v+ts/mKCGaqr4mF41f5vtpEAOfVAu97bX5vrd5Yrg7mYxqKzRca7hlmvrfjcdJ1kUi6mTPFoqz0872+9R4Frl7xAc8mQsrD2HSctkzLHA6OG/NBNRdLdI12I2e6qu9o3dByfQi+pyjzWLjW+54QEM2Op2pZPWMUkryaTUTQWbfZEAG5nF304Qi3XVF6KilfdAObRpxF0HaTQhilFEfSnoYINf42Hwzq7LEramtZ96STZS6dMkIbUoRdqODdkZwMFB2gXoOn5esW3YYyr48QhrHi+hS5v6y1LvtwWnv7VoWmRaJHqZTIOdrptRACOBFQQv3x93olyV3043GVmauBKld7jWRL4xzmb/YfmCRgQr6ILuIj2ckuayf/O3lshvDSLIRpbHpFlnqvQHF5miAjCzWc+cu/ZOBJ3X3kS9wpGRIFdHPFzTm5WaVnZEt7ai9zJurvoeVBO2pEjyddVHuib8UvrNvGojQUI2VziFz7yvUyNHsDKSHI9bxSpAtgYSMbrQZfeeLJLKuAbGa6K4enQ4s2vtzajarCv+wAkdUTxdn4tsZ/TyDaKiP6gxe1hgZ9L6/tN1K5DGoTNUEWYgKJwXJP6gjAMEu49F9y3n7JVLQM/hjKElQKHaPc05S8/JR/AyaLiySW0h1dvAIRuHv1PI8D44twhJpggqM9eq1kPOh4nyHrxbm2dYpNi84TjaTAQu"} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v4.0-wallet.aes.json b/btcrecover/test/test-wallets/blockchain-v4.0-wallet.aes.json new file mode 100644 index 000000000..4255607d8 --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v4.0-wallet.aes.json @@ -0,0 +1 @@ +{"pbkdf2_iterations":5000,"version":4,"payload":"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"} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/blockchain-v4.0-wallet_2025.aes.json b/btcrecover/test/test-wallets/blockchain-v4.0-wallet_2025.aes.json new file mode 100644 index 000000000..7ab1c63bb --- /dev/null +++ b/btcrecover/test/test-wallets/blockchain-v4.0-wallet_2025.aes.json @@ -0,0 +1 @@ +{"payload":"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","pbkdf2_iterations":5000,"version":4} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/btc_com_parsed_wallet_data_v3_random.json b/btcrecover/test/test-wallets/btc_com_parsed_wallet_data_v3_random.json new file mode 100644 index 000000000..865169c05 --- /dev/null +++ b/btcrecover/test/test-wallets/btc_com_parsed_wallet_data_v3_random.json @@ -0,0 +1,17 @@ +{ + "WalletNotes": "Random unused wallet backup found online", + "walletVersion": 3, + "walletIdentifier": "mywallet-855b31b8f801efe4", + "backupMnemonic": "strategy crucial dice crumble satisfy shy floor claw latin curve column income video brown guard now poem breeze behind regret ahead fun code army", + "encryptedPrimaryMnemonic": "library fall pear quarter daughter rival pyramid flavor length amount abandon abuse lady inherit lottery time appear guide pupil lumber argue wet balance morning gasp praise crime exhibit evidence lazy tragic desk burden empty when domain exist sort frost engine series expand huge marine void industry course bridge ridge identify salon case bonus unaware glove crisp come behave satoshi shove", + "encryptedRecoverySecretMnemonic": "library expect grant outer average accident weird behave remember session abandon absorb change episode oak atom stereo once syrup keep walnut disagree injury shrimp once dragon renew sketch accident jacket sniff kingdom balcony round cram own venue perfect access sample page shed orphan adult tube earth torch creek elegant wise pave kitchen quick laugh ride column sell middle supreme dad", + "passwordEncryptedSecretMnemonic": "library extend miracle peace found alarm heavy borrow tray session abandon acid improve uphold bless vital uncle chronic over seven author deny industry stone bird extra destroy soon bitter asset option blanket tail predict stove equip faith area hedgehog device ill sample frozen vacuum drop brain digital return paper exhibit salmon amateur online endorse tissue improve priority bachelor actual doll", + "blocktrailKeys": [ + { + "keyIndex": 0, + "pubkey": "xpub687DeMmb3SM2WnJgZZhCj31iMJNj8KLiE9GAvYkTw3jhKkvMopSyRiqBERto3c2kU24CPUMsbF8EABL1kF1adP4ypwpV7fdJMvGS5pMfw56 " + } + ], + "walletVersionSelect": "3", + "correct_password": "santacruzbolivia" +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/coinomi.wallet.android b/btcrecover/test/test-wallets/coinomi.wallet.android new file mode 100644 index 000000000..3afc329f8 Binary files /dev/null and b/btcrecover/test/test-wallets/coinomi.wallet.android differ diff --git a/btcrecover/test/test-wallets/coinomi.wallet.desktop b/btcrecover/test/test-wallets/coinomi.wallet.desktop new file mode 100644 index 000000000..dea7e50d0 Binary files /dev/null and b/btcrecover/test/test-wallets/coinomi.wallet.desktop differ diff --git a/btcrecover/test/test-wallets/coinomi.wallet.desktop.privkeys.txt b/btcrecover/test/test-wallets/coinomi.wallet.desktop.privkeys.txt new file mode 100644 index 000000000..f5416d1ee --- /dev/null +++ b/btcrecover/test/test-wallets/coinomi.wallet.desktop.privkeys.txt @@ -0,0 +1,4 @@ +Private Keys (BIP39 seed and BIP32 Root Key) are below... +BIP39 Mnemonic: refuse enrich brisk special hand display feed more stove elder bracket skin impact domain ready evolve age south idea suffer family retire pair kiwi (BIP39 Passphrase In Use, if you don't have it use BIP32 root key to recover wallet) + +BIP32 Root Key: xprv9s21ZrQH143K486Bs1fmVBXA563h8yezak7YNmJqvDE7xwBVDnAdhK71CaAiVSjXqm7a1DmyPeJwrYP8g6Aj2TUzcDTu8WE9sW9Pqk76sSb \ No newline at end of file diff --git a/btcrecover/test/test-wallets/coinvault.io.wallet.aes.json b/btcrecover/test/test-wallets/coinvault.io.wallet.aes.json new file mode 100644 index 000000000..79ddd2ceb --- /dev/null +++ b/btcrecover/test/test-wallets/coinvault.io.wallet.aes.json @@ -0,0 +1 @@ +{"Salt":"+x96glAI7c0EHOROOowc1Rv9AQsYw9a3EyXjyvA6t9Yg4YhSgNUASTgL6eiEoZFcrMIuEFP8tv8aAkOTa7ArYw==","Iterations":64000,"EncryptedKey":"amZtL5/dshn2U12hmgrfgY3RZLWTQ5GkaLaGwaDCQYVEnjQkJZLYgSyRUdhVzYApr/wrcICcJFRT0Njd9cWywSGhit0Q/sFVw9fX6b/WafOqr7v55iWf1+DoVLUJ+wkjCHIwYlfe70CC0b0ZvOWk1Q==","EncryptedWalletPassword":"WgYh5NvJO8XPmd7ScQtTFD6nLctCyZtB/dHJfyFV5Vgl3rBQfBVSbGTBz+fj8+jw","EncryptedIdentifier":"v8RDaOo1oJK7+j0+UYx4qymcmPNtmsdaZS6lw0zBRgm1ss7ozpL8bIcbyEGS7y/NpaNqE4TZeUH004wJfEu/fA==","Version":"1.2"} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json b/btcrecover/test/test-wallets/dogechain.wallet.aes.json new file mode 100644 index 000000000..01840c339 --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json @@ -0,0 +1,7 @@ +{ + "payload" : "jJzIUd6i9DMEgCFG9JQ1/z4xSamItXAiQnV4AeJ0BwdC5Vbc3WuZNx3suAZ/J4tncwIlwsOYcTFc4bHKsG9fZBLiDGN80Yb7g4z88+mbqraUT/ZhnE1ClCmXgL8quBEn/LUaEWyXoV+qELd8IRRpbaHx4fgRIUjzqii+5ERsXiFWxg/DxLDLxYvOF1y0FBDep4HtuSGjb1ionh87+DkxFyxV9G8f4a/dwRz/ldSMJ3z2ZlKlLRzk4UceNzGW4k+TH4GCd3qXJoozYD6+WERjtVIW3WIQy8ZQHBE2jprSjARC5kdMLsslf1DEAOqJMVd1", + "salt" : "Gc55/tevRG/ODxUN0ENiRg==", + "email" : "steve@cryptoguide.tips", + "pbkdf2_iterations" : 5000, + "two_fa_method" : "0" +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc new file mode 100644 index 000000000..4ddb06b1a --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-cbc @@ -0,0 +1,8 @@ +{ + "guid": "52500558-b3fa-4318-b6a3-3c55835c6575", + "payload": "jJzIUd6i9DMEgCFG9JQ1/z4xSamItXAiQnV4AeJ0BwdC5Vbc3WuZNx3suAZ/J4tncwIlwsOYcTFc4bHKsG9fZBLiDGN80Yb7g4z88+mbqraUT/ZhnE1ClCmXgL8quBEn/LUaEWyXoV+qELd8IRRpbaHx4fgRIUjzqii+5ERsXiFWxg/DxLDLxYvOF1y0FBDep4HtuSGjb1ionh87+DkxFyxV9G8f4a/dwRz/ldSMJ3z2ZlKlLRzk4UceNzGW4k+TH4GCd3qXJoozYD6+WERjtVIW3WIQy8ZQHBE2jprSjARC5kdMLsslf1DEAOqJMVd1", + "salt": "Gc55/tevRG/ODxUN0ENiRg==", + "pbkdf2_iterations": 5000, + "cipher": "AES-CBC", + "version": 1 +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-encrypted.2022-01 b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-encrypted.2022-01 new file mode 100644 index 000000000..3387267e1 --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-encrypted.2022-01 @@ -0,0 +1,7 @@ +{ + "guid": "52500558-b3fa-4318-b6a3-3c55835c6575", + "payload": "Dw5gN5A0eMzK0aFzkn+sIymgcy5ncpGt7wSyJLFbCJvTJnbA0rNRPWz6LQRZeaoqOFugYjXqlj/HMjOGTlAmUWjZSbOCkngzQBwZe1jKY0iVWIKI7SCHE7yI6YQN0bV9HoAo4zl+nDxYC/EN+ue11OqWOYZNhDxwyn/poyRd7bcigeuvcjam292+pglc2koAmOJeuOg0m+NvdvS7Mw07mAGCyLVrxLmj5efDtIHWrdrGWv5SgmTUUr1pUEOJG5Ps;LzZwl72onfBNml5KA22HBJMEJwyfSUWxOyXlA36kWJA6FAdVYadYX7Ln3+Xe9BG2662d6lLI+b3SAK9njFtGe0O0DOnS+HIyVCBvPAkMsj874dNB2J9bJfpm5/kuh58EFkZoJXpXywscBlPHu7TlbTieb8B1g/SPpkLrXvQL22XM6NVI927wB1s081KFjcT8IBdMmMZIp/qVtlkyoqKfQBwqtmGb1sOYsa0r2n8aBcI+RFP6O7Md6LCMfZeRYIlU;RHpIRAm+DoUA1oribmXhxJ44LiXL+BpOdanK/P3jFfs+FVPagu//ItCgfUMlmHgt5x7Qhlu7JZMWJU3WdQOeixdxbpI1EEseAdOtRG/sIwv2Vf4vo16effuZuQm/MO8xBngeKE56splt6A39FHQUG4v3YtlDb1w6yVphyDBrhIFxUsdJyCxjzWappyE00FDdG+OOnkRKtBGRtN/yF1UIaUvs699ssM88FnUNK4xGc0WmPpJpL8ToLKIKTmD9c6VJ;U63oZYYqy9EvT85dOAuIR0TaQmRACJvzETyH3laoUXLGv9ktjYd9WdXJX/7G9gQnmzwCd1J65/GsitvbI9Ch0OHeCW3iIZqjVGGg4sU91RWR3BVgl4d5XLNxmHrUVHkOrEXMmJRgrF9oWXeYSsmNMQiCk5StgJppBE9WMc6x//tDPMVWMOWICmM6E1Qn+zlIrjtizPIWjGdckTgaKw7a415Yi1yFyh211ueMczycIvshhEKFGJO6f0UuwLej11Rv;su0DZr+7yKjxsF0guCzmBUERevBP18gRrJQclCExf86CuM5KrBXZAijCy8J53g8eITNCiCFQORMD5gDRHcfbypJ/hGSh0VBdCcxJ8CQCc3K8Ok+BJ0le7XsxL23oC1XoU/ORNZjN7yg7O21Ez+RArDGItEfdHHjMpox9rNyRHyjOExDFyz55Zb3rAF8PrTUL2dnOmo0BF8uvW45sZzxJdFztFwg/FyeycFRbwhQ0i/TfzW679dVvQkdOpiG31c4X", + "salt": "Gc55/tevRG/ODxUN0ENiRg==", + "pbkdf2_iterations": 5000, + "cipher": "AES-CBC" +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm new file mode 100644 index 000000000..073ba2474 --- /dev/null +++ b/btcrecover/test/test-wallets/dogechain.wallet.aes.json.2024-gcm @@ -0,0 +1 @@ +{"guid": "52500558-b3fa-4318-b6a3-3c55835c6575","salt": "nF8i+D3CNdRDGlnrEoC9Aw==","payload": "rrP7xWZDH29oAhDUj1v2jG1vkO4kBtvKLdRQhzv9/ZtV40aptbfE9cHBhN77QkvslPIf6Wk52dxaURXcCps6JdWwHE32moV2fHOqVGK2EBGkQXh6i3iqjY13htZdessiYCIiq5Q1MlcAqovpUeerhmyXM6NkliCClieDnk6QEVLIpj7vJps6lN4tzeTGvRGIWrVqzXd3MDWgz4DucUCdUfyRh/9kT98SOxDPWNHZziEm3ik5B24hCPDWPjt3Dof7/ygzQkHMq88EeoSf76/U+3677WDwfU+gj1BwnkWLUBhKfX804vhKz/g1mMqhumnrRAE=","cipher": "AES-GCM","pbkdf2_iterations": 100000} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogecoin-wallet-android-backup b/btcrecover/test/test-wallets/dogecoin-wallet-android-backup new file mode 100644 index 000000000..166b7c7c4 --- /dev/null +++ b/btcrecover/test/test-wallets/dogecoin-wallet-android-backup @@ -0,0 +1,412 @@ +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 +E1SWuY4rbtel/kIosg== \ No newline at end of file diff --git a/btcrecover/test/test-wallets/dogecoincore-1.14.2-wallet.dat b/btcrecover/test/test-wallets/dogecoincore-1.14.2-wallet.dat new file mode 100644 index 000000000..498f32726 Binary files /dev/null and b/btcrecover/test/test-wallets/dogecoincore-1.14.2-wallet.dat differ diff --git a/btcrecover/test/test-wallets/electrum27-wallet-updated b/btcrecover/test/test-wallets/electrum27-wallet-updated new file mode 100644 index 000000000..2513fa605 --- /dev/null +++ b/btcrecover/test/test-wallets/electrum27-wallet-updated @@ -0,0 +1,134 @@ +{ + "addr_history": { + "12VqwWt2XyTZY7rSu7SDCS254NrVPbsfgg": [], + "13UQns1yHS6thM6KoEWnsAZpX7zDTRjqTZ": [], + "13w2qbsj4oHbZFjLPsAkp561zB49vYsdK2": [], + "1593NKys34QSm2isZKdqe3MaXbFUjkHbVs": [], + "161PLrKXq1xHryWZg5XkjXA5Co35hG6XNa": [], + "16BSTJTRfovaJ4jsAJFWHiZ8iV7LmhLgsa": [], + "17JtjaGDTajomzheJTfJNmLkJ1ynnKbYjE": [], + "17dJE5R5dAMj58UEqvtftcenpd9r32nSsg": [], + "17tMenzSS3mkysP2prrGSzEYc9C4HttEY3": [], + "1AV6DBWBFM1uRcvLLfgkQ72JDDFKHoVRxt": [], + "1AYfPqU3Ka4me1nRrJsPVWyuKjRmE6vvMq": [], + "1FcwsFxNkGXnVE9FzRds3w8rrR9wyG18Qe": [], + "1G1iAxv9Bi6dKRs4rG56oa5KvCXWYvjLwv": [], + "1GBsPjBHvuVMMQe73THssKjtTmDNupFEV3": [], + "1GQmeUKq33gcB6ES9sqLqpSd8jw5pQ4kyT": [], + "1GYu25FAAZfFvE7BWKbZXL18wZg2DUuLEi": [], + "1H9TRTLPE5hCYSQTZf7uwHrfKAsXkss3V4": [], + "1HNK1FK1bdmnH1HKVcT57xd9KN7GmPMm99": [], + "1HQLvFeRMDbTwufLQWmN6tctc6QcZxHdae": [], + "1HQrNUBEsEqwEaZZzMqqLqCHSVCGF7dTVS": [], + "1HZgwCdqGLKcoimdxgrEMRYG2Tyf2ATD8u": [], + "1JWkUc8j9R84Y335jXnEMQ1nemzXpYPCu2": [], + "1JgjWwEKfamvwPsQ817v4sDbXvUkUQ4cWw": [], + "1LKY5ZYT4Xrysy7a6bw1ykxrYX3XnJXW4a": [], + "1LKv7UyPu2wLMTNgCRLcEFM5N7LjNCPsX": [], + "1M4CTkAf3JEuhYg5EW3fWV2pJ8iSRoz21q": [], + "1Mvuof28zxADGJGowyKheCppYcyXdFfduA": [], + "1NCVK7qXWdaGQ87HpAWKje45HTT4qqvfg6": [], + "1NyTngdJ4RLypF9fHFoNhamLA2CLxY3XQU": [], + "1PWuEg3sXCPVZhBp2hSDKN8hMBzboRs9F4": [] + }, + "addresses": { + "change": [ + "1NyTngdJ4RLypF9fHFoNhamLA2CLxY3XQU", + "1GYu25FAAZfFvE7BWKbZXL18wZg2DUuLEi", + "1GQmeUKq33gcB6ES9sqLqpSd8jw5pQ4kyT", + "1HQLvFeRMDbTwufLQWmN6tctc6QcZxHdae", + "17tMenzSS3mkysP2prrGSzEYc9C4HttEY3", + "1NCVK7qXWdaGQ87HpAWKje45HTT4qqvfg6", + "1PWuEg3sXCPVZhBp2hSDKN8hMBzboRs9F4", + "1593NKys34QSm2isZKdqe3MaXbFUjkHbVs", + "1HZgwCdqGLKcoimdxgrEMRYG2Tyf2ATD8u", + "1LKv7UyPu2wLMTNgCRLcEFM5N7LjNCPsX" + ], + "receiving": [ + "1G1iAxv9Bi6dKRs4rG56oa5KvCXWYvjLwv", + "1HNK1FK1bdmnH1HKVcT57xd9KN7GmPMm99", + "13UQns1yHS6thM6KoEWnsAZpX7zDTRjqTZ", + "13w2qbsj4oHbZFjLPsAkp561zB49vYsdK2", + "1HQrNUBEsEqwEaZZzMqqLqCHSVCGF7dTVS", + "1GBsPjBHvuVMMQe73THssKjtTmDNupFEV3", + "1AV6DBWBFM1uRcvLLfgkQ72JDDFKHoVRxt", + "1H9TRTLPE5hCYSQTZf7uwHrfKAsXkss3V4", + "17JtjaGDTajomzheJTfJNmLkJ1ynnKbYjE", + "1AYfPqU3Ka4me1nRrJsPVWyuKjRmE6vvMq", + "16BSTJTRfovaJ4jsAJFWHiZ8iV7LmhLgsa", + "1JWkUc8j9R84Y335jXnEMQ1nemzXpYPCu2", + "17dJE5R5dAMj58UEqvtftcenpd9r32nSsg", + "161PLrKXq1xHryWZg5XkjXA5Co35hG6XNa", + "1M4CTkAf3JEuhYg5EW3fWV2pJ8iSRoz21q", + "1JgjWwEKfamvwPsQ817v4sDbXvUkUQ4cWw", + "1LKY5ZYT4Xrysy7a6bw1ykxrYX3XnJXW4a", + "1Mvuof28zxADGJGowyKheCppYcyXdFfduA", + "1FcwsFxNkGXnVE9FzRds3w8rrR9wyG18Qe", + "12VqwWt2XyTZY7rSu7SDCS254NrVPbsfgg" + ] + }, + "channel_backups": {}, + "channels": {}, + "fiat_value": {}, + "invoices": {}, + "keystore": { + "derivation": "m", + "root_fingerprint": "fe00eeb3", + "seed": "BQClHhKNaINSUiVClrHPbk+u9feRVqpAgtixZQaOc9yOBE4H1VWPW4Exsx4W8Fm1eOgYiZZrV9lv4EhxKAS8IVHR47ZTkU1X0slBxdILKB0hWcxau1om5cEshnErkS5G", + "type": "bip32", + "xprv": "nQBe06JMQVDOPOphpbOaLeUROcdGSCBhi1VGZ4//J1OdCD2Lg53nHgnV8m9q0N6mHpgFrLswoablpHOL0gpYKYQeh2FxTWhiraPIh8ehT+zF/AMMPi5cQVCMo00k0ivUPugblgfVs+ZD/SJZ2YI+7z0Y+Sq+4tOcHptUchAfM7Q=", + "xpub": "xpub661MyMwAqRbcGt6qtQ19Ttwvo5Dbf2cQdA2GMf9Xkjth8NqYXXordg3gLK1npATRm9Fr7d7fA5ziCwqEVMmzeRezofp8CEaru8pJ57zV8hN" + }, + "labels": {}, + "payment_requests": {}, + "prevouts_by_scripthash": {}, + "pubkeys": { + "change": [ + "026a5b9422e26dfa2ef88f8336f1d774324341cf012572d39bdc3fb9c92686e4b9", + "0360f6fd4090d02bf3df8e29a9c37e798010f4ff3cf5ef4744496a9143f2e6eaf0", + "031826a3b350068446fc9ade62819ce5d956cbbd58cafad99934a5deaaa4a4fc16", + "02b17d685535c5ec2c56306a44fd928b85ad3ec1663ea850ee3fc0b0eaf9eaa5c5", + "03dc3f7979e23c6e10670294b1bf213f0f382e81466aedbb1b102d4e8fc7556374", + "030c1059e8e988bf527d555729d5e1422850292f02def6856521ffcf73e03d2521" + ], + "receiving": [ + "0377487e1b3974ae97f3c96812f92cd910dc4ab4dd53230f622e02a64fd281e112", + "0202c6eb2158decd029aa9f5c302a147c2b9732f2fe77eb37843ec3f939304e8fb", + "02cfdf9d5bea6e4f6a24a11f556ef6e8e78fcceb7b96687deca9cc87c65add19b3", + "020c1c9c1710d7c772b966286e920defd579f0c81acb83bc433b292224551286a0", + "03d4161e8aa7689b47d16f81e5091475ed5f8603994f87cf07c2de2e9902a24939", + "02ab1e293f50de2144e57784ed5749ea86cdcbe2a4e22e3fb3cfb4e266c44182fa", + "03f5d88d975b5742c9be5eaa7a95c418c558673922e45710ac8fa4a9215193d39d", + "02b3fe99027498196a4ee66570ed57f8846e22fb48ed22e95a7f263a03b851a1ca", + "035411f09fd89dda477786d000dc4e622abb8762449cde3c2d1c1c923b48aeac2a", + "03d822c86a696dfedffc71a04d073d3e7143d184105cde3093a80ccd8d9d0a5214", + "0290e3c1397c77da45859fb2271a369291554623746f7299bbcc5fb34a74173c77", + "02a63c14ea8eda97985ab864675d3a2da83e942cde8c39e4bee1b990a9ad6080d9", + "025d129196eddd9736361657538d022af198db0cb3cb9a296c4165e6ab6c5c85fd", + "02bca51a18469663696bb177e46b6e747d2681b399298ad2870f228a9817094f75", + "02c1f064e697f03e9375e501d963248d7508c040c724eda61c306aac6d091dcd02", + "03a2be987f81ed5b69cb955da52fac06a23e90448231df787462211dd9647ba7a8", + "03b93de6e6c1d75923f513825dfa460cb4737e38cefe53f197810be4d69be75f97", + "02f80f07c1284cffd7c1b3a8f97131bd7194133454f7e9a6cb3cc4696018479e06", + "0369e2f3c525ade168984db47ab610a83637f54c26d6cfa1c74fb901205058f2dc", + "03cb7d6d1d31e0e5cbd574c3ec164b206efd4b0d5b88aa0853f73c24ee301d2e58" + ] + }, + "qt-console-history": [], + "seed_version": 33, + "spent_outpoints": {}, + "stored_height": 664211, + "transactions": {}, + "tx_fees": {}, + "txi": {}, + "txo": {}, + "use_encryption": true, + "verified_tx3": {}, + "wallet_type": "standard", + "winpos-qt": [ + 1148, + 304, + 1141, + 686 + ] +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/electrum28-100kbwallet b/btcrecover/test/test-wallets/electrum28-100kbwallet new file mode 100644 index 000000000..3fae95054 --- /dev/null +++ b/btcrecover/test/test-wallets/electrum28-100kbwallet @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/btcrecover/test/test-wallets/electrum41-wallet b/btcrecover/test/test-wallets/electrum41-wallet new file mode 100644 index 000000000..51f72f67a --- /dev/null +++ b/btcrecover/test/test-wallets/electrum41-wallet @@ -0,0 +1 @@ +QklFMQNnzDOUgsceKpC19yysENtI8Gd0fNBLdixa+vQ9ydX9qERh45Q4eIlfyGZVpWN856cGgWu3YEKF55erRl2HHcI9UyYFX9vz06myOqqwySt51GnPS20EsEqdakiGyS6udCdFJp/UZlOfqh2nkPV6EZeL488OYM2UjJTFrzanMfECqOGdEyxUncWkclc50J84oTtSChGa/oTxY4f56L5HdpB6SRCfTdmjg4An8XA5pSuW7coOSVlGr+MYUNlP4r+112xmUR4CYlnjNiJ1TWZEvX7kK9z+iJk+zULfdUCUjH1E15yDt+6sDRo9HLzrmxrtOb/vDwCbdQDiUnKa812VrslX+eyATEAoymd13MeJwZst4phzdmj8uaDDmqaa15ZVTe6Ogc0+S7c22tD6soqrilFsUiXJ32H8hHRZkoTcvGY1NC3kQpB4LYZByFS2++rIMm5iTpbCc9nYut4WYcsIO5zOdnvXxAPUZF4sluYC9IYM0mCHC4FE4HTBokPRBWfCaD/4sDkc/LDPteP+g+PoXqVERo4BpPEpEF4Izfy2q34Dpfn+nxmD4ismPcOneFAcPsb8fY42+c02fWCkaBUfHaiky7zV/08Kr/R35OtJbbXs9cNPlWzmfVeg7zaT/1SBX+vVQPGiSoxFrqpl6tpI4tcFMefnTkciqrF7rcJIIkCmHXI03wymw7uXVt1R/H0+XtTD+95UsX/6o4FOjVfa2kR9tlp3q7T34yMv8l2bxtLR3Yjtc3i/wj+pfSZJM/bsNl8jy5veu8NyEqqi/Ov7x+pPHXCFWA0hnlYowO4qWgNjRfMFcrNyGCnx0ubuMqja7hTs0efuJc5ZLya1EEbtjpFVPoWLnJKmHCXXb6OyRJw3dXrlg4OlmY6DKazBaV911vfIMEo5Kyex1SGhaoLKBbzen7mtDsa0+iYCSqOJ3cWICqJP3+ry6/12OqSP7DfTJnmnyr2dOFAw8aluz+kUhWCMwaOFoqzyQBUsMQRMKjqEQZS+2znbpHG7ScG1O4Hg0psCkKe+ZFWD0pP15ya3aByWHURoe/73L14P+wAyVi6pHWaTKbVEF1jhZExuZgoA8k32iicajxZJAB6MkPghoK+SUUCv8m6yxo8djhQ4bpI1ZAV6j/+7CmQRILJmc5KGJQFNIBVIN6RKU51AAHqcTE4baV4JlSt8dPMRyPE+rn0iV6s75EVX4BRyArFi1TWSA0wHVhT4zk96pVRxTqJJX34ZVaXSdd/3LLZlLLbpuAE0da7o4Cr7iVW8de//TULiw/l6bCr1wGznwhobewpRInE+ORWjyiCC9aw34mYPv+0hsmoqvVJ/oBMM/VKIDQzvpZPlFjKd9jlnOkm8aUflTmfuO8gOx8ky5w7KkH2d0//GBGnnHKqRO5wyA15KeNjnOLad1EMI3s0v0XtP7eMxjdboGazM0F0FZTtXo/ebXsa/ppYoj2BK6Nkg/CzbOVUmYX+gFtNUdlqgczYVe+Pppb22WI/ZODVpaZgVy/tRV5BXOIJiO+iTToJyt0yz3pF7JFc2R7KMvzVlIo0Rs395AGTsNKAFsUIO09GEeTvFKNwt3aD+b8uelpscvQdMIaxIOuxXTLFoJD6sPZ7IZtQs7t7LOuNs5A+Lq05wbk93XZc+rpM5xFOdpG40pJDc5EwH6MGo/7B65cciIfUdjJyvxCb13zBGOxJkUabo35k6f93U94mg2s5CBNFwKAgWSeMgEnNz+Wn6YyQWzahsi76rFsDq8qOND5xGtKaOPB+f+X9sBaKJDLho4fV4IOmhL6Ld/uLRl1n4ZiNpLdlZOc5oy6xe5nUXc2xvL9ngtqgl3mQLQcb56yzSt4X5yek1j2JQ46UXgkdVpfe9J/tgeALcHDK+A4Z5XpYL2MoUlpFhbzng2TDlzg4vZa3MO7t63OoURS3ZJiDiX+qEZmwdsqKAPKx5bvdDU3PlKW3hXiLqk3/LKOBJzwfsKy3qu4G/8SRNidehjTYFnt8C0fmS8frf2vMQfPGK9/dgko9ZKOR2OzHrpDOl4OTmwLvuxL1CaA8Nz83v72/ApzXqsF3yCR88tmiIhm66EOOtTfKOBl3LCuNort5XsB9l/tbm/bz/qvG5EF2PR7MX3+HQ72ItUQKw8aOjLKTnE5/eQjxgfRlFdB4lmaVxL/ow6gBaYegj/acpqDNmQkdu7Jp43qJZrJTtqoDaJLUUIDbgTVPrNZYF9yrdVnx0WuqTj2gGO90hSN6sry4QI93ebcoVpRn+3EGFiFZKqurUaKa9xDdxvDMmLs2fxOuuE/+jKJoIW5jWX3+OWUsZZXqihkGdt6Ka3Y+WDTQ122VLyUMhNQCaO3PkuuB1lQlPWL1FyVNN5UJ54pnQ87wYMUo+vI5Ox1hp/9KcT8IQQf99MdRpLp8t/u8rfN9dsrq9Sxi3EupIiBL6D/JcYfA= \ No newline at end of file diff --git a/btcrecover/test/test-wallets/electrum4_4_3_unencrypted b/btcrecover/test/test-wallets/electrum4_4_3_unencrypted new file mode 100644 index 000000000..01a6eeb24 --- /dev/null +++ b/btcrecover/test/test-wallets/electrum4_4_3_unencrypted @@ -0,0 +1,107 @@ +{ + "addr_history": { + "bc1q0fhvy203qdsxeczmg3egz064jtntk8jhgarhvp": [], + "bc1q0ya8y2f0qe9rsfdch5e7srv6e4x2rtzwlc3kne": [], + "bc1q38tpfcxwjxewe49mplfprn2xskyhhgruc3pvf7": [], + "bc1q44gv5r42r65wzc8hlvc9w43dsl5heqwmcdswlw": [], + "bc1q665lh5ezjull2l05evmkzv9q6heq7nzddel6er": [], + "bc1q6qnd0t4vd8psd5xrqgvs4hgqrfyelz6kw3aa5k": [], + "bc1q6s5uq952jlmx3p25unjt376hdtnz26vy23kyn6": [], + "bc1q74e3pdt9zd3x78drpaj2lmumwhh9j48n3sj56f": [], + "bc1q7cezg0z6npgzd8cg7cwfxnlumn476xmchmlxt9": [], + "bc1qa0p8r4jul70xfl9c8u2rf6a0er9l9cpxmxwjtc": [], + "bc1qavqgrv6v927nnnmdcp2kymjfns42jn4w3kzhgk": [], + "bc1qc236rqm03q6mw8g4aarejkpazyvymh3qq7cfg3": [], + "bc1qd2redklhnvcxzkjz30l0xdkyn0l93wjzpqvz03": [], + "bc1qdy39qmsl9v00gv6ml9ezq4rdmgn4xfmx6nnvn3": [], + "bc1qfaw93ff4ttu3pxhawye8hs29p2dhxn53tnmfkl": [], + "bc1qhwxt7tgcz3hs4r6vslng5tkfy9feeu4crnupth": [], + "bc1qjl3s8ya0shase44dea6e0ykxy0ss4n7yuaf59j": [], + "bc1qlc7n5fp9028ech5s05d6rtdhz97p78hce88eda": [], + "bc1qmdxnfz53l6k9syvxnnunk04cl852stkmp0vveg": [], + "bc1qnkly6nx5jdqek2alq33n3nza9dfumq2684s04t": [], + "bc1qsfehq233hsgfhkk2l99qc7gn7p857uuhzwymxn": [], + "bc1qsz0dhew357cj6eznmcawgyf44jzpgcax2lvfvm": [], + "bc1qtmyjtu3w2j237s72m8y9tclull9tcmg76awxda": [], + "bc1qtrspe9pq2tfkvy3zamm893xxez8jwqgxe8wuq0": [], + "bc1qtsqkx27qdxk53zquq6mzxfntxhqqdmwqd7ps9k": [], + "bc1qtw2p5fr4l078lup582m22scpqkp39gf8faww47": [], + "bc1qu9ydr7dfm9t0cgs7tg8qmg0ug8l5hp3fckwe7c": [], + "bc1qv5ej5nj4et2lqpjml7wnwr8stlw0jx5axevatf": [], + "bc1qvsd5029x6mny8nuxpmavvep2l9wf2ykxt2qzdu": [], + "bc1qx3cd7vtz7sslywszh668jl8wayww46zd57q5wp": [] + }, + "addresses": { + "change": [ + "bc1q6qnd0t4vd8psd5xrqgvs4hgqrfyelz6kw3aa5k", + "bc1q44gv5r42r65wzc8hlvc9w43dsl5heqwmcdswlw", + "bc1qtsqkx27qdxk53zquq6mzxfntxhqqdmwqd7ps9k", + "bc1qx3cd7vtz7sslywszh668jl8wayww46zd57q5wp", + "bc1qtmyjtu3w2j237s72m8y9tclull9tcmg76awxda", + "bc1qdy39qmsl9v00gv6ml9ezq4rdmgn4xfmx6nnvn3", + "bc1qmdxnfz53l6k9syvxnnunk04cl852stkmp0vveg", + "bc1qtw2p5fr4l078lup582m22scpqkp39gf8faww47", + "bc1qvsd5029x6mny8nuxpmavvep2l9wf2ykxt2qzdu", + "bc1qlc7n5fp9028ech5s05d6rtdhz97p78hce88eda" + ], + "receiving": [ + "bc1qsz0dhew357cj6eznmcawgyf44jzpgcax2lvfvm", + "bc1q7cezg0z6npgzd8cg7cwfxnlumn476xmchmlxt9", + "bc1q665lh5ezjull2l05evmkzv9q6heq7nzddel6er", + "bc1qa0p8r4jul70xfl9c8u2rf6a0er9l9cpxmxwjtc", + "bc1q0ya8y2f0qe9rsfdch5e7srv6e4x2rtzwlc3kne", + "bc1qnkly6nx5jdqek2alq33n3nza9dfumq2684s04t", + "bc1qu9ydr7dfm9t0cgs7tg8qmg0ug8l5hp3fckwe7c", + "bc1q38tpfcxwjxewe49mplfprn2xskyhhgruc3pvf7", + "bc1q6s5uq952jlmx3p25unjt376hdtnz26vy23kyn6", + "bc1qfaw93ff4ttu3pxhawye8hs29p2dhxn53tnmfkl", + "bc1qv5ej5nj4et2lqpjml7wnwr8stlw0jx5axevatf", + "bc1qavqgrv6v927nnnmdcp2kymjfns42jn4w3kzhgk", + "bc1qhwxt7tgcz3hs4r6vslng5tkfy9feeu4crnupth", + "bc1qjl3s8ya0shase44dea6e0ykxy0ss4n7yuaf59j", + "bc1qsfehq233hsgfhkk2l99qc7gn7p857uuhzwymxn", + "bc1qc236rqm03q6mw8g4aarejkpazyvymh3qq7cfg3", + "bc1q74e3pdt9zd3x78drpaj2lmumwhh9j48n3sj56f", + "bc1qtrspe9pq2tfkvy3zamm893xxez8jwqgxe8wuq0", + "bc1qd2redklhnvcxzkjz30l0xdkyn0l93wjzpqvz03", + "bc1q0fhvy203qdsxeczmg3egz064jtntk8jhgarhvp" + ] + }, + "db_metadata": { + "creation_timestamp": 1718669375, + "first_electrum_version_used": "4.4.3" + }, + "fiat_value": {}, + "frozen_coins": {}, + "imported_channel_backups": {}, + "invoices": {}, + "keystore": { + "derivation": "m/0h", + "pw_hash_version": 1, + "root_fingerprint": "4d5b9b9e", + "seed": "M9F5SbHb27QlyedS5ix3ZZp5VGEwPRibufvFmsl23oXAr8HwJ07ZCAns3boddkQbfpBpm+qtPZRV6SYnaJiXPkI0iRJbtrJnV+QTreV6ufSCxfGKJeo2kQhnoH+1W+Xa", + "seed_type": "segwit", + "type": "bip32", + "xprv": "PRGms7vnFpayU0/9jd9PKlwi39bnI8le8rBuy0NCVT5lnEhPSvTqimC8EIue8IpeVYFOYQZFvlp127TuPpi9Iz8lpG3URuOXP2tAMsyXXY4FXYb+RFCizAwLzZ4dR9KYURH3yh2hHe1rENu4CV9E6Acl56NWMGAT6E4CVH4RhFg=", + "xpub": "zpub6n81C2JeAicxQ2F9otpGBwkmmm4ZGq4N1JHUUwi4WfZnJWmvcSS4yWED4xZSYRWveTAfzPzQeWkQtwvcYiRxE3ywUUCBQvba4nx7k2SaPo3" + }, + "labels": {}, + "lightning_payments": {}, + "lightning_preimages": {}, + "lightning_xprv": "zprvAc2AK4nJLDj2tCaQAqifwiETYuj5KQqYwc5ANYTs6YrcuJYrzkTqBcB3DBzkUaqg36BeaPqqAw9ScMU3RJ2zsKEVTdFfNx9Uzg29Hx8DuzR", + "num_parents": {}, + "onchain_channel_backups": {}, + "payment_requests": {}, + "prevouts_by_scripthash": {}, + "seed_type": "segwit", + "seed_version": 52, + "spent_outpoints": {}, + "submarine_swaps": {}, + "transactions": {}, + "tx_fees": {}, + "txi": {}, + "txo": {}, + "use_encryption": true, + "verified_tx3": {}, + "wallet_type": "standard" +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/imtoken-identity.json b/btcrecover/test/test-wallets/imtoken-identity.json new file mode 100644 index 000000000..3bbeb8852 --- /dev/null +++ b/btcrecover/test/test-wallets/imtoken-identity.json @@ -0,0 +1,44 @@ +{ + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "7450eebcf0b0aafd56c83eb12498077f" + }, + "ciphertext": "07a06f1eabdf3fec6fb8cc9b9f5547e4da48cf774f5ab06ef1c46ae293b05214cdec7c64ec19aa70b8af7127b66f85b7f539c56930eea261027a2d944b8785e4a78ab7c5c9ff62c20365a47a6e308db73dcf9a167b956c993d0a10e1ba36d714835cd60d2550f7c32d9021d88e1aef", + "kdf": "pbkdf2", + "kdfparams": { + "c": 10240, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "21731cc92558832b715dc0da606d636e5e3682702086a5e0678ee52ed8fed1a4" + }, + "mac": "aed570a44c6026b2acc3090c4976c77ffb4d6b097e5ce2bc343ad332eb1250cd" + }, + "id": "4099a26b-12e6-4296-bdea-547cc7a422c3", + "version": 10000, + "encAuthKey": { + "encStr": "61e7e0a6bb62fa965b991d803f998fb1ba2d04f9b48107bd0d1f1e7e9e260f7f", + "nonce": "f1c6b730d166d4121d3caa46e19baa9a" + }, + "encKey": "d80744e42f048b1a7b7682899a3bd96a171e372cf74c99af35f4f6e3a1c06dc6", + "encMnemonic": { + "encStr": "9490bf63a05599a4aa67f83d854bb0d80d8eac73a070c476494f379f7492cc7c6b0f8ce05a8a1b02d0de92a5cb629b21fdc4ba94a0a1cc944927b09bd1090ac61483c384d1662edf3a540f", + "nonce": "8bdacecc0041b233ef6aa09c68fdb412" + }, + "identifier": "im14x5DughyxdGsdeseqZtAwE6tWbm2oL1Tst6m", + "ipfsId": "QmSso53y1p1o9fiUsDd1H7eap8h5ehUnGzymXGBFNEnuea", + "walletIDs": [ + "0b95fb1a-e703-49cf-99f8-7c3904281b6a", + "bbd56b95-9fa9-4d47-a537-baf228b9b38e", + "2a05a9f6-41d5-461c-bc26-b11802815b68" + ], + "imTokenMeta": { + "backup": [], + "mode": "NORMAL", + "name": "btcrecover", + "network": "MAINNET", + "passwordHint": "btcr-test-password", + "source": "NEW_IDENTITY", + "timestamp": 1682470437 + } +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/litecoincore-0.18.1-wallet.dat b/btcrecover/test/test-wallets/litecoincore-0.18.1-wallet.dat new file mode 100644 index 000000000..50c8b5540 Binary files /dev/null and b/btcrecover/test/test-wallets/litecoincore-0.18.1-wallet.dat differ diff --git a/btcrecover/test/test-wallets/metamask.9.8.4_firefox_vault b/btcrecover/test/test-wallets/metamask.9.8.4_firefox_vault new file mode 100644 index 000000000..4452699e3 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask.9.8.4_firefox_vault @@ -0,0 +1 @@ +{"data":"bB5JP1EW0xwBmWZ9vI/iwx4gkDrdVWqBQukmDYMPBTxZ9endym5rtjVPBnEkPmhVtVB+TCCJ5OISXwlfWhJw1HMLPDM+E3RqcOKtB7hLStbKq4P86wMyOgML6PZUVMZZRLfbQd+8TbKHtFKPRG+UeaF1++dwZ963sEQpCtC0QpGfBuzWKb+vwkhnXgVNebWvTpbDaZXwuDvh/hEC8WIKDPKfC1NOrJGR5KMGnxL84ZSgAyHLYtgLbk8WkOBTV0c=","iv":"0hGSitGz2sjrAkI2t3wpTA==","salt":"Otbm5EMX/SRfYw7/ZGSJcVVZCm+5SMJ7e/udfTC6Hc4="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/metamask.android.persist-root b/btcrecover/test/test-wallets/metamask.android.persist-root new file mode 100644 index 000000000..443668f83 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask.android.persist-root @@ -0,0 +1 @@ +{"collectibles":"{\"favorites\":{}}","engine":"{\"backgroundState\":{\"AccountTrackerController\":{\"accounts\":{\"0x66F9C09118B1C726BC24811a611baf60af42070A\":{\"balance\":\"0x0\"}},\"_U\":0,\"_V\":0,\"_W\":null,\"_X\":null},\"AddressBookController\":{\"addressBook\":{}},\"AssetsContractController\":{},\"CollectiblesController\":{\"allCollectibleContracts\":{},\"allCollectibles\":{},\"ignoredCollectibles\":[],\"collectibleContracts\":[],\"collectibles\":[]},\"CurrencyRateController\":{\"conversionDate\":1650127160.54,\"conversionRate\":3025.29,\"nativeCurrency\":\"ETH\",\"currentCurrency\":\"usd\",\"pendingCurrentCurrency\":null,\"pendingNativeCurrency\":null,\"usdConversionRate\":3025.29},\"KeyringController\":{\"keyrings\":[],\"vault\":\"{\\\"cipher\\\":\\\"pzGHRQw3aj//zSjUNAA83WC1EttUHvm4eNNd7YpdoA+bLsU1FXecXYckE3KGWcBIHIdQjdPp2OEgUms0D4XmoWFoAKVnuEUiokWMHy0jpI9z6QTpkSET7xaUY1ZAEWxas1HDBjUBFMhTsuOPiXD1k69tQkxTEe8bHcFFhFsEKfh743b0YypfFbTcOC5HdiuzwEkrtO+nhwBeHBzjzVyGgjQVlqFQiWzbg7JdVPvozbnB5KaongYSEn19o4EbxWiIx7RQWsQ88LR79fWS/nzT0udjxjj9Auu58lGAyvpYTHZzt6n1yapQVXRoHAV44h1AH30f8LY+c2yDKRcPtgc8qqTZXDWzwm4UeFf1BiwUXA6qjN9dFDa0F9Gvl8ugL9HbboGQHQcgQtIKbQnjQSmYucR7kUrZVn/i5jShNglt5lMb4WO28wBaMebBl0tIPPw8xa16tZ3C1Z7RmJrdr3HXDTrwSm5ybqkcngeIOLQwqIOT0WGkSAg6BAce/0oeAp1kgaZWj5BNqnGAsTzaHK1VggFbbQjoX/CQG65vEZBGQXsmdJSfd3m+sTAO113QCpiovvOQFAwcn5eQXQgIv57k2UCrE+SuQcahfd0cZ9GerjI=\\\",\\\"iv\\\":\\\"8fc2c478acc10f6428355312fce293de\\\",\\\"salt\\\":\\\"QW4ie0WH8XP7ust40yy7pg==\\\",\\\"lib\\\":\\\"original\\\"}\"},\"PersonalMessageManager\":{\"unapprovedMessages\":{},\"unapprovedMessagesCount\":0},\"NetworkController\":{\"network\":\"loading\",\"isCustomNetwork\":false,\"provider\":{\"type\":\"mainnet\",\"chainId\":\"1\"},\"properties\":{}},\"PreferencesController\":{\"featureFlags\":{},\"frequentRpcList\":[],\"identities\":{\"0x66F9C09118B1C726BC24811a611baf60af42070A\":{\"address\":\"0x66F9C09118B1C726BC24811a611baf60af42070A\",\"name\":\"Account 1\",\"importTime\":1650114145762}},\"ipfsGateway\":\"https://cloudflare-ipfs.com/ipfs/\",\"lostIdentities\":{},\"selectedAddress\":\"0x66F9C09118B1C726BC24811a611baf60af42070A\",\"useStaticTokenList\":true,\"useCollectibleDetection\":false,\"openSeaEnabled\":false,\"_U\":0,\"_V\":0,\"_W\":null,\"_X\":null},\"TokenBalancesController\":{\"contractBalances\":{}},\"TokenRatesController\":{\"contractExchangeRates\":{}},\"TokensController\":{\"allTokens\":{},\"allIgnoredTokens\":{},\"ignoredTokens\":[],\"suggestedAssets\":[],\"tokens\":[]},\"TransactionController\":{\"methodData\":{},\"transactions\":[],\"internalTransactions\":[],\"swapsTransactions\":{}},\"TypedMessageManager\":{\"unapprovedMessages\":{},\"unapprovedMessagesCount\":0},\"GasFeeController\":{\"gasFeeEstimates\":{},\"estimatedGasFeeTimeBounds\":{},\"gasEstimateType\":\"none\"},\"TokenDetectionController\":{},\"CollectibleDetectionController\":{},\"TokenListController\":{\"tokensChainsCache\":{}},\"SwapsController\":{\"quotes\":{},\"quoteValues\":{},\"fetchParams\":{\"slippage\":0,\"sourceToken\":\"\",\"sourceAmount\":0,\"destinationToken\":\"\",\"walletAddress\":\"\"},\"fetchParamsMetaData\":{\"sourceTokenInfo\":{\"decimals\":0,\"address\":\"\",\"symbol\":\"\"},\"destinationTokenInfo\":{\"decimals\":0,\"address\":\"\",\"symbol\":\"\"}},\"topAggSavings\":null,\"approvalTransaction\":null,\"quotesLastFetched\":0,\"error\":{\"key\":null,\"description\":null},\"topAggId\":null,\"isInPolling\":false,\"pollingCyclesLeft\":3,\"quoteRefreshSeconds\":null,\"usedGasEstimate\":null,\"usedCustomGas\":null},\"PhishingController\":{}}}","privacy":"{\"privacyMode\":true,\"thirdPartyApiMode\":true,\"approvedHosts\":{}}","bookmarks":"[]","recents":"[]","browser":"{\"activeTab\":null,\"history\":[],\"whitelist\":[],\"tabs\":[]}","modals":"{\"networkModalVisible\":false,\"accountsModalVisible\":false,\"collectibleContractModalVisible\":false,\"receiveModalVisible\":false,\"dappTransactionModalVisible\":false,\"approveModalVisible\":false}","settings":"{\"searchEngine\":\"DuckDuckGo\",\"primaryCurrency\":\"ETH\",\"useBlockieIcon\":true,\"hideZeroBalanceTokens\":false,\"lockTime\":30000}","alert":"{\"isVisible\":false,\"autodismiss\":null,\"content\":null,\"data\":null}","transaction":"{\"selectedAsset\":{},\"transaction\":{}}","user":"{\"loadingMsg\":\"Deleting current wallet\",\"loadingSet\":false,\"backUpSeedphraseVisible\":false,\"protectWalletModalVisible\":false,\"gasEducationCarouselSeen\":false,\"nftDetectionDismissed\":false,\"appTheme\":\"os\",\"passwordSet\":true,\"seedphraseBackedUp\":true,\"userLoggedIn\":true}","wizard":"{\"step\":0}","notification":"{\"notifications\":[]}","swaps":"{\"1\":{\"isLive\":true},\"isLive\":true,\"hasOnboarded\":false}","fiatOrders":"{\"selectedCountry\":\"US\",\"orders\":[]}","infuraAvailability":"{\"isBlocked\":false}","navigation":"{\"currentRoute\":\"WalletView\"}","networkOnboarded":"{\"networkOnboardedState\":[],\"networkState\":{\"showNetworkOnboarding\":false,\"nativeToken\":\"\",\"networkType\":\"\",\"networkUrl\":\"\"},\"switchedNetwork\":{\"networkUrl\":\"\",\"networkStatus\":false}}","_persist":"{\"version\":10,\"rehydrated\":true}"} diff --git a/btcrecover/test/test-wallets/metamask.ios.persist-root b/btcrecover/test/test-wallets/metamask.ios.persist-root new file mode 100644 index 000000000..c3ff0a37e --- /dev/null +++ b/btcrecover/test/test-wallets/metamask.ios.persist-root @@ -0,0 +1 @@ +{"collectibles":"{\"favorites\":{}}","engine":"{\"backgroundState\":{\"AccountTrackerController\":{\"accounts\":{\"0x66F9C09118B1C726BC24811a611baf60af42070A\":{\"balance\":\"0x0\"}},\"_U\":0,\"_V\":0,\"_W\":null,\"_X\":null},\"AddressBookController\":{\"addressBook\":{}},\"AssetsContractController\":{},\"CollectiblesController\":{\"allCollectibleContracts\":{},\"allCollectibles\":{},\"ignoredCollectibles\":[],\"collectibleContracts\":[],\"collectibles\":[]},\"CurrencyRateController\":{\"conversionDate\":1649988414.582,\"conversionRate\":3019.01,\"nativeCurrency\":\"ETH\",\"currentCurrency\":\"usd\",\"pendingCurrentCurrency\":null,\"pendingNativeCurrency\":null,\"usdConversionRate\":3019.01},\"KeyringController\":{\"vault\":\"{\\\"cipher\\\":\\\"oyvzsghbruyPSrkcEv0B+qfkODclFQNIPWuvWHSgIMr26dcVDcsj+sBjzMrJ0mWP0jZQ1xhKt1bAp3v8+rLPauI8OsJ/1uoImqAcBRBu1ORUHmwk2kYIiYLC+qgqvf6OHZGmhF3OAT4Fpt4TSZIP5kVI12DHLNO0yLZDkBliXhQSmuhmY8CCpE13IaTrBWipe/E5lblr5zQWvgfz6lBzrxzE87eY4bAGJ5bh4P/5Nxc=\\\",\\\"iv\\\":\\\"9ec290a383a4c4cf5fcae9137455dc72\\\",\\\"salt\\\":\\\"ywv4d3UyjBtjVQ0gip0Swg==\\\",\\\"lib\\\":\\\"original\\\"}\"},\"PersonalMessageManager\":{\"unapprovedMessages\":{},\"unapprovedMessagesCount\":0},\"NetworkController\":{\"network\":\"1\",\"isCustomNetwork\":false,\"provider\":{\"type\":\"mainnet\",\"chainId\":\"1\"},\"properties\":{\"isEIP1559Compatible\":true}},\"PreferencesController\":{\"featureFlags\":{},\"frequentRpcList\":[],\"identities\":{\"0x66F9C09118B1C726BC24811a611baf60af42070A\":{\"address\":\"0x66F9C09118B1C726BC24811a611baf60af42070A\",\"name\":\"Account 1\",\"importTime\":1649815458524}},\"ipfsGateway\":\"https://cloudflare-ipfs.com/ipfs/\",\"lostIdentities\":{},\"selectedAddress\":\"0x66F9C09118B1C726BC24811a611baf60af42070A\",\"useStaticTokenList\":true,\"useCollectibleDetection\":false,\"openSeaEnabled\":false,\"_U\":0,\"_V\":0,\"_W\":null,\"_X\":null},\"TokenBalancesController\":{\"contractBalances\":{}},\"TokenRatesController\":{\"contractExchangeRates\":{}},\"TokensController\":{\"allTokens\":{},\"allIgnoredTokens\":{},\"ignoredTokens\":[],\"suggestedAssets\":[],\"tokens\":[]},\"TransactionController\":{\"methodData\":{},\"transactions\":[{\"blockNumber\":\"10717310\",\"id\":\"5edf6880-e552-11ea-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1598195253000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x9aa65464b4cfbe3dc2bdb3df412aee2b3de86687\",\"gas\":\"0x5208\",\"gasPrice\":\"0x156ba09800\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x3593\",\"to\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"value\":\"0x5b471c5b794400\"},\"transactionHash\":\"0x1331372b3750105d06a4d6e89a17cbd148b71a7c521f96ba3099c46457e5caef\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"10717318\",\"id\":\"a6fe8b00-e552-11ea-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1598195374000,\"transaction\":{\"data\":\"0xa9059cbb000000000000000000000000da511c376ad5dbcc5e7ffccc5fb9396f91271f1d0000000000000000000000000000000000000000000000056bc75e2d63100000\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x1036b\",\"gasPrice\":\"0x1ac688c51f\",\"gasUsed\":\"0x9d97\",\"nonce\":\"0x0\",\"to\":\"0xbe9375c6a420d2eeb258962efb95551a5b722803\",\"value\":\"0x0\"},\"transactionHash\":\"0xa75468b70d963d11dcfbe96213c1511e37c3e3aeb6fd676bc6ac51868626d302\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":true},{\"blockNumber\":\"10717322\",\"id\":\"c39ac300-e552-11ea-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1598195422000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x5208\",\"gasPrice\":\"0x165a0bc000\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x1\",\"to\":\"0x36f403aa625e7122c1455c23624abf7378b5c80b\",\"value\":\"0x43a204cc0afbb7\"},\"transactionHash\":\"0xf0b86dfd64fbd28f446ac185c3cae4a7228aecad5fa77ac0551d5d9d7a768b6f\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14305381\",\"id\":\"e8d97e80-99de-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646194321000,\"transaction\":{\"data\":\"0x\",\"from\":\"0xeb2629a2734e272bcc07bda959863f316f4bd4cf\",\"gas\":\"0x5208\",\"gasPrice\":\"0xa23f6685e\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x49b32a\",\"to\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"value\":\"0x8d500fae96000\"},\"transactionHash\":\"0x106f49a9205a3c1d636b5d47766c948b54cd18180d4fe956520c5395c4393daa\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14305384\",\"id\":\"f858c780-99de-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646194347000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x5208\",\"gasPrice\":\"0x1382de8600\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x2\",\"to\":\"0x108b48bae093cea505fdd725c7bef561abda3f1b\",\"value\":\"0x294799d093000\"},\"transactionHash\":\"0xa26fa2604f27eecd2fb89e843ad034950b3aa2e5615b32138ea52930dbbc484b\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14305392\",\"id\":\"68674d80-99df-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646194535000,\"transaction\":{\"data\":\"0x\",\"from\":\"0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511\",\"gas\":\"0x5208\",\"gasPrice\":\"0xc6d980a03\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x5d6a22\",\"to\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"value\":\"0x81f1ad9f4e000\"},\"transactionHash\":\"0xc98bfc1edbd1b36734466915ceccacb22b8024ed76a6911225df209bc737698e\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14305393\",\"id\":\"77e69680-99df-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646194561000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x5208\",\"gasPrice\":\"0x156d6a5b80\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x3\",\"to\":\"0x63889c748ec8d558b90f8bf93d9540851f01e74c\",\"value\":\"0x141635d530400\"},\"transactionHash\":\"0xcb35054a37268a871ca74e827fa6c7333de3a62102718acafc37cf7d99987312\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14305487\",\"id\":\"c21d4701-99e2-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646195974000,\"transaction\":{\"data\":\"0xa9059cbb0000000000000000000000006d0c7d96c2cb4105a25301df2a1d102cc71cf8ae00000000000000000000000000000000000000000002bacd5bc40aa9c6800000\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0xcab7\",\"gasPrice\":\"0xdd2f15172\",\"gasUsed\":\"0xb7f7\",\"nonce\":\"0x4\",\"to\":\"0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce\",\"value\":\"0x0\"},\"transactionHash\":\"0x33c2a4c9f47072600ad15c6e48e8d2685046b96b6e1152871ef63de279cb1eb5\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":true},{\"blockNumber\":\"14305487\",\"id\":\"c21d4700-99e2-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1646195974000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x6d0c7d96c2cb4105a25301df2a1d102cc71cf8ae\",\"gas\":\"0x5208\",\"gasPrice\":\"0xdd2f15172\",\"gasUsed\":\"0x5208\",\"nonce\":\"0xb\",\"to\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"value\":\"0xaf25434c52c7e\"},\"transactionHash\":\"0x3b7d33cd9878b2aa9dd9c4b025bffc49211c0a4b10a65c24e07a4e5c5bef103f\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14370492\",\"id\":\"62d5e880-a1d3-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1647068981000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x5208\",\"gasPrice\":\"0x296352580\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x5\",\"to\":\"0x012e97041ed39dba037307055a8180327fee15b6\",\"value\":\"0x2f01750aed80\"},\"transactionHash\":\"0xd68054922a1053f4bcc340d548677eaa28efd0ceccf548e3c1b5f123f02eb8e5\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14410592\",\"id\":\"9aa1cc00-a6bc-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1647608952000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x4976a4a02f38326660d17bf34b431dc6e2eb2327\",\"gas\":\"0x32918\",\"gasPrice\":\"0xa0168e5cf\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x10532c\",\"to\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"value\":\"0x17bc0cc54afb000\"},\"transactionHash\":\"0x4c6171d65d38c54c53c5a649f4fadb21670af9d383c2fdeceaa5487c1554624a\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false},{\"blockNumber\":\"14410593\",\"id\":\"a1c8da00-a6bc-11ec-b269-29db90905b87\",\"networkID\":\"1\",\"chainId\":\"1\",\"time\":1647608964000,\"transaction\":{\"data\":\"0x\",\"from\":\"0x66f9c09118b1c726bc24811a611baf60af42070a\",\"gas\":\"0x5208\",\"gasPrice\":\"0x3bff249faa4\",\"gasUsed\":\"0x5208\",\"nonce\":\"0x6\",\"to\":\"0xe067e61d1adaff1c56ec89a31697102622be2a93\",\"value\":\"0x482731101752e0\"},\"transactionHash\":\"0xafd3a07905262628e5311ba4222e86adbff5dacde459aff74da7a78618b0104f\",\"verifiedOnBlockchain\":true,\"status\":\"confirmed\",\"toSmartContract\":false}],\"internalTransactions\":[],\"swapsTransactions\":{}},\"TypedMessageManager\":{\"unapprovedMessages\":{},\"unapprovedMessagesCount\":0},\"GasFeeController\":{\"gasFeeEstimates\":{},\"estimatedGasFeeTimeBounds\":{},\"gasEstimateType\":\"none\"},\"TokenDetectionController\":{},\"CollectibleDetectionController\":{},\"TokenListController\":{\"tokensChainsCache\":{}},\"SwapsController\":{\"quotes\":{},\"quoteValues\":{},\"fetchParams\":{\"slippage\":0,\"sourceToken\":\"\",\"sourceAmount\":0,\"destinationToken\":\"\",\"walletAddress\":\"\"},\"fetchParamsMetaData\":{\"sourceTokenInfo\":{\"decimals\":0,\"address\":\"\",\"symbol\":\"\"},\"destinationTokenInfo\":{\"decimals\":0,\"address\":\"\",\"symbol\":\"\"}},\"topAggSavings\":null,\"approvalTransaction\":null,\"quotesLastFetched\":0,\"error\":{\"key\":null,\"description\":null},\"topAggId\":null,\"isInPolling\":false,\"pollingCyclesLeft\":3,\"quoteRefreshSeconds\":null,\"usedGasEstimate\":null,\"usedCustomGas\":null},\"PhishingController\":{}}}","privacy":"{\"approvedHosts\":{},\"privacyMode\":true,\"thirdPartyApiMode\":true}","bookmarks":"[]","recents":"[]","browser":"{\"history\":[],\"whitelist\":[],\"tabs\":[],\"activeTab\":null}","modals":"{\"networkModalVisible\":false,\"accountsModalVisible\":false,\"collectibleContractModalVisible\":false,\"receiveModalVisible\":false,\"dappTransactionModalVisible\":false,\"approveModalVisible\":false}","settings":"{\"searchEngine\":\"DuckDuckGo\",\"primaryCurrency\":\"ETH\",\"lockTime\":30000,\"useBlockieIcon\":true,\"hideZeroBalanceTokens\":false}","alert":"{\"isVisible\":false,\"autodismiss\":null,\"content\":null,\"data\":null}","transaction":"{\"selectedAsset\":{},\"transaction\":{}}","user":"{\"loadingMsg\":\"\",\"loadingSet\":false,\"passwordSet\":true,\"seedphraseBackedUp\":true,\"backUpSeedphraseVisible\":false,\"protectWalletModalVisible\":false,\"gasEducationCarouselSeen\":false,\"nftDetectionDismissed\":false,\"userLoggedIn\":true,\"appTheme\":\"os\"}","wizard":"{\"step\":3}","notification":"{\"notifications\":[]}","swaps":"{\"1\":{\"isLive\":true},\"isLive\":true,\"hasOnboarded\":false}","fiatOrders":"{\"orders\":[],\"selectedCountry\":\"US\"}","infuraAvailability":"{\"isBlocked\":false}","navigation":"{\"currentRoute\":\"WalletView\"}","networkOnboarded":"{\"networkOnboardedState\":[],\"networkState\":{\"showNetworkOnboarding\":false,\"nativeToken\":\"\",\"networkType\":\"\",\"networkUrl\":\"\"},\"switchedNetwork\":{\"networkUrl\":\"\",\"networkStatus\":false}}","_persist":"{\"version\":10,\"rehydrated\":true}"} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/metamask.privkeys.txt b/btcrecover/test/test-wallets/metamask.privkeys.txt new file mode 100644 index 000000000..49f5184df --- /dev/null +++ b/btcrecover/test/test-wallets/metamask.privkeys.txt @@ -0,0 +1 @@ +[{"type":"HD Key Tree","data":{"mnemonic":"ocean hidden kidney famous rich season gloom husband spring convince attitude boy","numberOfAccounts":1,"hdPath":"m/44'/60'/0'/0"}}]U2H*oP \ No newline at end of file diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000005.ldb b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000005.ldb new file mode 100644 index 000000000..d06c6615e Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000005.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000007.ldb b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000007.ldb new file mode 100644 index 000000000..4830e435e Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000007.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000009.ldb b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000009.ldb new file mode 100644 index 000000000..e6922e59a Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000009.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000011.ldb b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000011.ldb new file mode 100644 index 000000000..5dfe6a2e9 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000011.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000012.log b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000012.log new file mode 100644 index 000000000..bb282756d Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000012.log differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000013.ldb b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000013.ldb new file mode 100644 index 000000000..79e18e0b4 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/000013.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/CURRENT b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/CURRENT new file mode 100644 index 000000000..7ed683d17 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/CURRENT @@ -0,0 +1 @@ +MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/Extension files from Binance Chain Extension.txt b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/Extension files from Binance Chain Extension.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/LOCK b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/LOCK new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/LOG b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/LOG new file mode 100644 index 000000000..7b4130b14 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/LOG @@ -0,0 +1,17 @@ +2021/08/30-21:35:43.781 7938 Creating DB C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\fhbohimaelbohpjbbldcngcnapndodjp since it was missing. +2021/08/30-21:35:43.785 7938 Reusing MANIFEST C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\fhbohimaelbohpjbbldcngcnapndodjp/MANIFEST-000001 +2021/08/30-21:39:48.798 b040 Level-0 table #5: started +2021/08/30-21:39:48.817 b040 Level-0 table #5: 1435867 bytes OK +2021/08/30-21:39:48.819 b040 Delete type=0 #3 +2021/08/30-21:39:59.387 6834 Level-0 table #7: started +2021/08/30-21:39:59.400 6834 Level-0 table #7: 1301896 bytes OK +2021/08/30-21:39:59.401 6834 Delete type=0 #4 +2021/08/30-21:40:08.353 b040 Level-0 table #9: started +2021/08/30-21:40:08.372 b040 Level-0 table #9: 1302086 bytes OK +2021/08/30-21:40:08.373 b040 Delete type=0 #6 +2021/08/30-21:41:02.995 6834 Level-0 table #11: started +2021/08/30-21:41:03.011 6834 Level-0 table #11: 1188650 bytes OK +2021/08/30-21:41:03.012 6834 Delete type=0 #8 +2021/08/30-21:41:11.765 b040 Level-0 table #13: started +2021/08/30-21:41:11.779 b040 Level-0 table #13: 1302197 bytes OK +2021/08/30-21:41:11.780 b040 Delete type=0 #10 diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/MANIFEST-000001 b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/MANIFEST-000001 new file mode 100644 index 000000000..c3cd06da3 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/MANIFEST-000001 differ diff --git a/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/current password =BTCR-test-passw0rd previous passwords=P@ssw0rd!.txt b/btcrecover/test/test-wallets/metamask/fhbohimaelbohpjbbldcngcnapndodjp/current password =BTCR-test-passw0rd previous passwords=P@ssw0rd!.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/000003.log b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/000003.log new file mode 100644 index 000000000..19d25d05d Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/000003.log differ diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/CURRENT b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/CURRENT new file mode 100644 index 000000000..7ed683d17 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/CURRENT @@ -0,0 +1 @@ +MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/Extension files from Ronin Wallet.txt b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/Extension files from Ronin Wallet.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/LOCK b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/LOCK new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/LOG b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/LOG new file mode 100644 index 000000000..3372e9c20 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/LOG @@ -0,0 +1,2 @@ +2021/08/30-21:35:30.460 b13c Creating DB C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\fnjhmkhhmkbjkkabndcnnogagogbneec since it was missing. +2021/08/30-21:35:30.463 b13c Reusing MANIFEST C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\fnjhmkhhmkbjkkabndcnnogagogbneec/MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/MANIFEST-000001 b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/MANIFEST-000001 new file mode 100644 index 000000000..18e5cab72 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/MANIFEST-000001 differ diff --git a/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/current password =btcr-test-password previous passwords=password.txt b/btcrecover/test/test-wallets/metamask/fnjhmkhhmkbjkkabndcnnogagogbneec/current password =btcr-test-password previous passwords=password.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/000003.log b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/000003.log new file mode 100644 index 000000000..fb19b6115 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/000003.log differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/CURRENT b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/CURRENT new file mode 100644 index 000000000..7ed683d17 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/CURRENT @@ -0,0 +1 @@ +MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/LOCK b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/LOCK new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/LOG b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/LOG new file mode 100644 index 000000000..9ca146273 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/LOG @@ -0,0 +1,2 @@ +2022/03/29-09:52:59.764 6c48 Creating DB C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn since it was missing. +2022/03/29-09:52:59.769 6c48 Reusing MANIFEST C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/MANIFEST-000001 b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/MANIFEST-000001 new file mode 100644 index 000000000..18e5cab72 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3/MANIFEST-000001 differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000005.ldb b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000005.ldb new file mode 100644 index 000000000..bdd3f29dc Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000005.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000014.log b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000014.log new file mode 100644 index 000000000..54d1aca2b Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000014.log differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000016.ldb b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000016.ldb new file mode 100644 index 000000000..ebb0edf52 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/000016.ldb differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/CURRENT b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/CURRENT new file mode 100644 index 000000000..7ed683d17 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/CURRENT @@ -0,0 +1 @@ +MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/LOCK b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/LOCK new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/LOG b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/LOG new file mode 100644 index 000000000..3418c0f43 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/LOG @@ -0,0 +1,29 @@ +2024/03/18-06:48:42.105 170 Creating DB C:\Users\crypto\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn since it was missing. +2024/03/18-06:48:42.109 170 Reusing MANIFEST C:\Users\crypto\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 +2024/03/18-06:52:44.112 b0 Level-0 table #5: started +2024/03/18-06:52:44.130 b0 Level-0 table #5: 2523551 bytes OK +2024/03/18-06:52:44.132 b0 Delete type=0 #3 +2024/03/18-06:52:52.047 b0 Level-0 table #7: started +2024/03/18-06:52:52.064 b0 Level-0 table #7: 2524042 bytes OK +2024/03/18-06:52:52.065 b0 Delete type=0 #4 +2024/03/18-06:53:04.453 b0 Level-0 table #9: started +2024/03/18-06:53:04.472 b0 Level-0 table #9: 2526103 bytes OK +2024/03/18-06:53:04.474 b0 Delete type=0 #6 +2024/03/18-06:53:09.554 b0 Level-0 table #11: started +2024/03/18-06:53:09.573 b0 Level-0 table #11: 2527469 bytes OK +2024/03/18-06:53:09.575 b0 Delete type=0 #8 +2024/03/18-06:53:50.523 b0 Level-0 table #13: started +2024/03/18-06:53:50.543 b0 Level-0 table #13: 2526942 bytes OK +2024/03/18-06:53:50.545 b0 Delete type=0 #10 +2024/03/18-06:53:59.945 b0 Level-0 table #15: started +2024/03/18-06:53:59.961 b0 Level-0 table #15: 2527193 bytes OK +2024/03/18-06:53:59.963 b0 Delete type=0 #12 +2024/03/18-06:53:59.963 b0 Compacting 4@0 + 1@1 files +2024/03/18-06:54:00.003 b0 Generated table #16@0: 1 keys, 1263638 bytes +2024/03/18-06:54:00.003 b0 Compacted 4@0 + 1@1 files => 1263638 bytes +2024/03/18-06:54:00.004 b0 compacted to: files[ 0 1 1 0 0 0 0 ] +2024/03/18-06:54:00.004 b0 Delete type=2 #7 +2024/03/18-06:54:00.004 b0 Delete type=2 #9 +2024/03/18-06:54:00.004 b0 Delete type=2 #11 +2024/03/18-06:54:00.004 b0 Delete type=2 #13 +2024/03/18-06:54:00.004 b0 Delete type=2 #15 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/MANIFEST-000001 b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/MANIFEST-000001 new file mode 100644 index 000000000..f32afeaf0 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1/MANIFEST-000001 differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/000003.log b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/000003.log new file mode 100644 index 000000000..678cb6567 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/000003.log differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/CURRENT b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/CURRENT new file mode 100644 index 000000000..7ed683d17 --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/CURRENT @@ -0,0 +1 @@ +MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/Extension files from Metamask.txt b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/Extension files from Metamask.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/LOCK b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/LOCK new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/LOG b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/LOG new file mode 100644 index 000000000..b911e6d2f --- /dev/null +++ b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/LOG @@ -0,0 +1,2 @@ +2021/08/30-21:35:11.314 b13c Creating DB C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn since it was missing. +2021/08/30-21:35:11.477 b13c Reusing MANIFEST C:\Users\CryptoGuide\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 new file mode 100644 index 000000000..18e5cab72 Binary files /dev/null and b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/MANIFEST-000001 differ diff --git a/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/current password =btcr-test-password previous passwords=password.txt b/btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn/current password =btcr-test-password previous passwords=password.txt new file mode 100644 index 000000000..e69de29bb diff --git a/btcrecover/test/test-wallets/metamask_vault_v11_12_1.txt b/btcrecover/test/test-wallets/metamask_vault_v11_12_1.txt new file mode 100644 index 000000000..8f6feb08b --- /dev/null +++ b/btcrecover/test/test-wallets/metamask_vault_v11_12_1.txt @@ -0,0 +1 @@ +{"data":"Kxj05Qf3witT0tIBopkUcO9nzzgUunEjIC7NUGUI0mqYVI94lOnBb2uDJhRNgXyog7KYG7/e7ZU9/xg6bVmucpvstEGgWfa2jM0vdGKhh8e9QsxOnAAt9fMXpqhXO8Bo+GUWtX2ykmfqATp1bgpstTzVGZUK+fFn9vpgstCLcLDJlOheR61V7ZkZYsTzhdida/hGan3Dgc0jNDeQDCbf4sd84fFSJiszNQxcvM7tJmy6ARN8zsuxsMGIjySe3UlYxX7anec7rUnG8uduuDs9E1FuP6nYMpWmk4g6KJzVC2dtK7+VKkDs8DJkxdMKhob0Tt5HWWzRYs97uz9c+xPlkQmq10PD/DZqWRvjmQLTwr3es/BIsywCcU8D6646xz4nhu9x1STWeKFep325YXHK+MAHlPX87eWtDb/PGFYe2s+ku7rp6Gh4tRysq7TwRb1VTqlZ85VpCP0aLStHLs8vgtvZ2Au9NztWesaq8cjgXSkGgOxAFMsT4xOHsVI1bB6Suu9fmBKzi0lw3mr4JswSJMb7EBEDeDeXRN5PG88OZm/QGiO6XV+b2TXvA3LzcuAAg5EZd04NhUujuudxRTG1ql2jPUSsqTFUXlBUMZf6TkJfAkp+kcGSbaKfSrd59wzmR/6bPIvepyX68vamKOZ7XAmOsc6C85i6AluH+dpObHMC0lAV4Mv0vlkLMGO9OBPeAUlAAYtoWiVohv7erHLvzKabC8MVSvPDJxZzDrOaojU39aP5RexIM07dYp2PXlWM8/LZkz+GZfGWxw8UcIeq/1vbu+L+SsY=","iv":"IbxXQknyfC6AW3yDKX8kKw==","keyMetadata":{"algorithm":"PBKDF2","params":{"iterations":600000}},"salt":"QU1D7Egd9J+2E+CyPFiZ7Rl8BVAsTX1jESAV2/J4/+A="} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/multibit-wallet.key.privkeys.txt b/btcrecover/test/test-wallets/multibit-wallet.key.privkeys.txt new file mode 100644 index 000000000..0f600eb5f --- /dev/null +++ b/btcrecover/test/test-wallets/multibit-wallet.key.privkeys.txt @@ -0,0 +1 @@ +L3BdsUEuHb15LgPp8ZpEhckMdRj4bPnrj352RgqgUUVKvngnBDph diff --git a/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.clear b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.clear new file mode 100644 index 000000000..628d659ef Binary files /dev/null and b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.clear differ diff --git a/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted new file mode 100644 index 000000000..6cc510909 Binary files /dev/null and b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted differ diff --git a/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted.txt b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted.txt new file mode 100644 index 000000000..28a2b6cf2 --- /dev/null +++ b/btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted.txt @@ -0,0 +1 @@ +KwYiuLdeNhZVHs4spw6b1aptpgyGQDeoh59paKVyG25F57qMzYZK diff --git a/btcrecover/test/test-wallets/multibithd-v0.5.0/mbhd.wallet.aes.privkeys.txt b/btcrecover/test/test-wallets/multibithd-v0.5.0/mbhd.wallet.aes.privkeys.txt new file mode 100644 index 000000000..b87ae27f6 --- /dev/null +++ b/btcrecover/test/test-wallets/multibithd-v0.5.0/mbhd.wallet.aes.privkeys.txt @@ -0,0 +1 @@ +BIP39 Seed: laundry foil reform disagree cotton hope loud mix wheel snow real board \ No newline at end of file diff --git a/btcrecover/test/test-wallets/multidoge-wallet.key b/btcrecover/test/test-wallets/multidoge-wallet.key new file mode 100644 index 000000000..0016d3c70 --- /dev/null +++ b/btcrecover/test/test-wallets/multidoge-wallet.key @@ -0,0 +1,9 @@ +U2FsdGVkX1+XYw8DdcDvq5l81RShnH+oFq/jxXOQu6wQ/5NLF8fPisny/edJM1vWkcxCEGrqUoYY +8J7SRWoIrR+y5sTiloLo9sBMFSrnGjyoWjghTD4cvwENXkm3B8LI1CR74JMlvu8q/wPcZMCK/Pwk +tszTs5aFB9pAZH8Y7JKfyXDwF6WCEqfphVi7BP0yL1hMvs431vyNV5IrO2h8wQfUMight2bKEjJV +8fBW90M33J0AnigH2sE2uM7SI34JM3VM8CIl+/yoXJVvwxt3F58q8+PYzswsoOPru3UkKUmOU33U +XB86fmPz07s7lq30eKvjz2cUttTwqdM+Fi9c8sDBeM2jEjG3Kucs6YKwkTLXseIUwnFDg5CSUS6n +X/FqPbr0UB8HLtLGgFjCt1Ngw9qurpy/C2e2rILf2tBPHRxlwD5WIBYevXk7CfhgI0SYPk4ghlPu +yVx4IYODpUVHAGR4l+/UxIXF2ML1+VyHscl3TQb1KPF7GUChcX0tpgX63F7FonOpE1e8kZJlASLX +ncPBwL4l7ZKUK5jSikrckOMpAYBrl1uityYkEetvgZl0IsyISLzcPLh7MqkIQlH+TmAMFteu4dSu +rH4IClB5VIY= diff --git a/btcrecover/test/test-wallets/multidoge-wallet.key.privkeys.txt b/btcrecover/test/test-wallets/multidoge-wallet.key.privkeys.txt new file mode 100644 index 000000000..a3ccb84fe --- /dev/null +++ b/btcrecover/test/test-wallets/multidoge-wallet.key.privkeys.txt @@ -0,0 +1,6 @@ +QPHRxyb5yP8GvDtDpbPv8wyXgQr7UDKS4g6pWN9MWUW233WNYhPB +QQGSXDD4FAtVdUWvyEKni97JuCWCJZFZzLBR3rQUtxhfxnyV9pbL +QSTbM8e67a5cv2srq3NUVoaVni5gihg5tHmwshEAFfHV1xQsqKiq +QP2mZsM8jzwmPyRRBvAK2ZyJf6s2JE8hsfkMEW5E4wASdDPLQaYb +QTRwukQVa6oWTM64778Homu1pDmZ8K4u8VvezYXrrbYZvcr776js +QV9Y2UPYyhrpu7rtdHbK7kRVnA1hsiw8rB2sw8ovNqieicxNdjgP diff --git a/btcrecover/test/test-wallets/tenup-1.1.0.4-wallet.dat b/btcrecover/test/test-wallets/tenup-1.1.0.4-wallet.dat new file mode 100644 index 000000000..84d141aa7 Binary files /dev/null and b/btcrecover/test/test-wallets/tenup-1.1.0.4-wallet.dat differ diff --git a/btcrecover/test/test-wallets/toastwallet.txt b/btcrecover/test/test-wallets/toastwallet.txt new file mode 100644 index 000000000..9a2f73695 --- /dev/null +++ b/btcrecover/test/test-wallets/toastwallet.txt @@ -0,0 +1 @@ +c69d22de{"walletversion":"1.0","pindata":{"salt":"9b4d7d8f15f777d12e35fcff5c06d0d37e86c715","hash":"0943956dfca37cff893300aa"},"ppdata":{"salt1":"e3711c08951dec555d49e2418e6710edb343e91f49b305702b31a26da4d6c8c2dcf1556b","salt2":"db356800d76793fe7192da09c03e34febdbc7347b7795d665b47ea7716e34dfd8de17d7e","hash":"7a3076edb293dec7f159d5c39bdc952d19b78f6f"},"rpdata":{"salt1":"b4d8c9fb44121d938ed5859fe2d1710df0c799eb954f49c9c9a2ea739c906ec4e5706c9e","salt2":"fb20840afb41ebd783cede289fc2feaa4734a6342776265f49401b593f8b2db266e3d79c","hash":"4dc7d6ae2111d98c8adc30485398bcfa4fc557bf","erk":"85b60d36a62253c43c22c663fed9469f8dae7a3376ea4bd180e76128f28a8f89dfbb37bec8bdf8c987175a7bfbf39df01a75c757a67629acaf39fba4"},"accounts":{"rGe8Bh3ne7X6u934Ji3EzatD3nqfgt7YVa":{"ppsalt":"12f93666a2a7b9d5a719b7b19fd110ec1b4478df9f34c848cfdbcd06e1c4087c00a4f92f","ppsecret":"80ede887a9aa87523886370fcfe4a25c51df653e9e8a5d11453400b1eb971703a1bef6be74b7b796670c82b96aa554a1daa4d067f10d721f88d9dc83345ac423b545b5a6eecf21167e","rpsalt":"7a83d48040e325b4f0b6c9bd7ed7ed2bc6dfd172a0058696c3f9be3bd03cba29b2ebf0c7","rpsecret":"c50095c1c95edfd7459ef3a3769d9695e465e00e297310eecaf7d2a5ef33c3d0b23935e7c2c00d70ea7d2c36e6a3cf5369e83b4dfd12585cbb96fa45599c49640f43ec08d13b6d8649","nickname":"test1"},"rhmm4M1HshZeiw4Nx8ATerLGTduX3PZraN":{"ppsalt":"f7cd50393e3f8249cf4fb9f583f558d400ba4e57feac1875d42017051751d0fbdead0463","ppsecret":"4de9c6884d0d03fedd29c139eaeaa8003c9ee9bfba8ed853a8099b9df0d477acde77d051509e4646c2a9ed542aba48afc8604a844770a2440f22887a9ea9d840107cecff08bd342a65","rpsalt":"d2c5b82d138a434006ae3224def58528122531713259829b5ca40bd063c28b2111f6bfcb","rpsecret":"7cc05f587453bb803d0e40f8da70989c6281ac3bbc78e51c9ca6e80713fc29af0ab08bbbe26d38e92cda97f088a9c410abbddc8a75720bad2b365f232858e9afcc674fe8a0ef626ac9","nickname":"test2"}}} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/utc-keystore-v3-pbkdf2-custom.json b/btcrecover/test/test-wallets/utc-keystore-v3-pbkdf2-custom.json new file mode 100644 index 000000000..de34ed096 --- /dev/null +++ b/btcrecover/test/test-wallets/utc-keystore-v3-pbkdf2-custom.json @@ -0,0 +1,20 @@ +{ + "address": "ab28206d93e0b1c4cedf4302351a206777056c4c", + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "22aa39377a157b2e3d76480e3a7c69a4" + }, + "ciphertext": "7660d1d13068281b82678086df7b1be3b7f6832d65ee7bf07047dbf9fc8dce4a", + "kdf": "pbkdf2", + "kdfparams": { + "c": 1000000, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "d13bda68840932e5e4ffd398ca45e198" + }, + "mac": "219c38cbd4351de1b5db09d225c492e94524a8c0a766907a1cb789da52cf6c80" + }, + "id": "a6dab4a7-b684-4af6-ad96-1bc27be268c0", + "version": 3 +} \ No newline at end of file diff --git a/btcrecover/test/test-wallets/utc-keystore-v3-scrypt-myetherwallet.json b/btcrecover/test/test-wallets/utc-keystore-v3-scrypt-myetherwallet.json new file mode 100644 index 000000000..752bb92ca --- /dev/null +++ b/btcrecover/test/test-wallets/utc-keystore-v3-scrypt-myetherwallet.json @@ -0,0 +1 @@ +{"version":3,"id":"21f3023c-56b0-4ebd-af8a-1a1a93196b7b","address":"ab28206d93e0b1c4cedf4302351a206777056c4c","crypto":{"ciphertext":"49a9b15096d0113c5fbef9f3da454286f0760cc307f205d836aef92dbbc906ec","cipherparams":{"iv":"952ef90db87d60f87f25ec5a342e06af"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"9395a0e0204464f01de5d3020651759125cea5bfc30781c67bf963628ff63eda","n":131072,"r":8,"p":1},"mac":"5042ad36d868e16986c198c638a90cc0695392ed1d7284479b51a553f8d6b9f8"}} \ No newline at end of file diff --git a/btcrecover/test/test_passwords.py b/btcrecover/test/test_passwords.py index 6920eeafd..ce83a78c6 100644 --- a/btcrecover/test/test_passwords.py +++ b/btcrecover/test/test_passwords.py @@ -3,6 +3,7 @@ # test_passwords.py -- unit tests for btcrecover.py # Copyright (C) 2014-2017 Christopher Gurnee +# 2019-2021 Stephen Rothery # # This file is part of btcrecover. # @@ -19,78 +20,49 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! -# (all optional futures for 2.7) -from __future__ import print_function, absolute_import, division, unicode_literals - -import warnings, os, unittest, cPickle, tempfile, shutil, multiprocessing, time, gc, filecmp, sys, hashlib -if __name__ == b'__main__': +import warnings, os, unittest, pickle, tempfile, shutil, multiprocessing, time, gc, filecmp, sys, hashlib, argparse +if __name__ == '__main__': sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..")) + from btcrecover import btcrpass +import btcrecover.opencl_helpers class NonClosingBase(object): pass -# Enables either ASCII or Unicode mode for all tests based on either -# the value of tstr or the value of the BTCR_CHAR_MODE env. variable -tstr = None + +tstr = str def setUpModule(): global orig_warnings, tstr, tchr, utf8_opt, BytesIO, StringIO, BytesIONonClosing, StringIONonClosing - orig_warnings = warnings.catch_warnings() - orig_warnings.__enter__() # save the current warnings settings (it's a context manager) - # Convert warnings to errors: - warnings.simplefilter("error") - # except this from Intel's OpenCL compiler: - warnings.filterwarnings("ignore", r"Non-empty compiler output encountered\. Set the environment variable PYOPENCL_COMPILER_OUTPUT=1 to see more\.", UserWarning) - # except these from Armory: - warnings.filterwarnings("ignore", r"the sha module is deprecated; use the hashlib module instead", DeprecationWarning) - warnings.filterwarnings("ignore", r"import \* only allowed at module level", SyntaxWarning) - # except this from Google protobuf, and because of pkg_resources (used by PyOpenCL) many others (see #62): - warnings.filterwarnings("ignore", r"Not importing directory '.*': missing __init__\.py", ImportWarning) - - if tstr is None: - tstr = unicode if os.getenv("BTCR_CHAR_MODE", "").lower() == "unicode" else str - else: - assert tstr in (str, unicode) - - if tstr == str: - import StringIO, cStringIO - BytesIO = StringIO.StringIO - StringIO = cStringIO.StringIO - class BytesIONonClosing(BytesIO, NonClosingBase): - def close(self): pass - class StringIONonClosing(BytesIO, NonClosingBase): - def close(self): pass - btcrpass.enable_ascii_mode() - tchr = chr - utf8_opt = "" - print("** Testing in ASCII character mode **") - - else: - import io - BytesIO = io.BytesIO - StringIO = io.StringIO - class BytesIONonClosing(BytesIO, NonClosingBase): - def close(self): pass - class StringIONonClosing(StringIO, NonClosingBase): - def close(self): pass - btcrpass.enable_unicode_mode() - tchr = unichr - utf8_opt = " --utf8" - print("** Testing in Unicode character mode **") + # orig_warnings = warnings.catch_warnings() + # orig_warnings.__enter__() # save the current warnings settings (it's a context manager) + # # Convert warnings to errors: + # warnings.simplefilter("error") + # # Ignore import warnigns that appear in Python 3.6 + # warnings.filterwarnings("ignore", message=r"Not importing directory .*: missing __init__", category=ImportWarning) + # # Ignore protobuf deprecation warnings that appear in Python 3.6 and 3.7 + # warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working", category=DeprecationWarning) + + import io + BytesIO = io.BytesIO + StringIO = io.StringIO + class BytesIONonClosing(BytesIO, NonClosingBase): + def close(self): pass + class StringIONonClosing(StringIO, NonClosingBase): + def close(self): pass + btcrpass.enable_unicode_mode() + tchr = chr + utf8_opt = " --utf8" + print("** Testing in Unicode character mode **") + def tearDownModule(): global tstr tstr = None - orig_warnings.__exit__(None, None, None) # restore the original warnings settings + # orig_warnings.__exit__(None, None, None) # restore the original warnings settings WALLET_DIR = os.path.join(os.path.dirname(__file__), "test-wallets") @@ -122,7 +94,7 @@ class GeneratorTester(unittest.TestCase): # expected_skipped == the expected # of skipped passwords, if any # extra_kwds == additional StringIO objects to act as file stand-ins def do_generator_test(self, tokenlist, expected_passwords, extra_cmd_line = None, test_passwordlist = False, - chunksize = sys.maxint, expected_skipped = None, **extra_kwds): + chunksize = sys.maxsize, expected_skipped = None, **extra_kwds): assert isinstance(tokenlist, list) assert isinstance(expected_passwords, list) tokenlist_str = tstr("\n".join(tokenlist)) @@ -130,12 +102,12 @@ def do_generator_test(self, tokenlist, expected_passwords, extra_cmd_line = None if extra_cmd_line: args += tstr(extra_cmd_line).split(tstr(" ")) - btcrpass.parse_arguments([tstr("--tokenlist")] + args, tokenlist=StringIO(tokenlist_str), **extra_kwds) + btcrpass.parse_arguments([tstr("--tokenlist")] + args, tokenlist=StringIO(tokenlist_str), disable_security_warning_param = True, **extra_kwds) tok_it, skipped = btcrpass.password_generator_factory(chunksize) if expected_skipped is not None: self.assertEqual(skipped, expected_skipped) try: - generated_passwords = tok_it.next() + generated_passwords = tok_it.__next__() for p in generated_passwords: self.assertIs(type(p), tstr) self.assertEqual(generated_passwords, expected_passwords) @@ -147,12 +119,12 @@ def do_generator_test(self, tokenlist, expected_passwords, extra_cmd_line = None for sio in filter(lambda s: isinstance(s, NonClosingBase), extra_kwds.values()): sio.seek(0) - btcrpass.parse_arguments([tstr("--passwordlist")] + args, passwordlist=StringIO(tokenlist_str), **extra_kwds) + btcrpass.parse_arguments([tstr("--passwordlist")] + args, passwordlist=StringIO(tokenlist_str), disable_security_warning_param = True, **extra_kwds) pwl_it, skipped = btcrpass.password_generator_factory(chunksize) if expected_skipped is not None: self.assertEqual(skipped, expected_skipped) try: - generated_passwords = pwl_it.next() + generated_passwords = pwl_it.__next__() for p in generated_passwords: self.assertIs(type(p), tstr) self.assertEqual(generated_passwords, expected_passwords) @@ -169,16 +141,31 @@ def expect_syntax_failure(self, tokenlist, expected_error, extra_cmd_line = "", with self.assertRaises(SystemExit) as cm: btcrpass.parse_arguments( (tstr("--tokenlist __funccall --listpass "+extra_cmd_line+utf8_opt)).split(), - tokenlist = StringIO(tstr("\n".join(tokenlist))), + tokenlist = StringIO(tstr("\n".join(tokenlist))), disable_security_warning_param = True, **extra_kwds) self.assertIn(expected_error, cm.exception.code) +class TestOuterIterations(unittest.TestCase): + def test_outer_iterations_minimum(self): + class DummyWallet: + def passwords_per_seconds(self, seconds): + return 0 + def return_verified_password_or_false(self, pw_list): + pass + wallet = DummyWallet() + CHUNKSIZE_SECONDS = 1.0 / 100.0 + measure_performance_iterations = wallet.passwords_per_seconds(0.5) + inner_iterations = int(round(2 * measure_performance_iterations * CHUNKSIZE_SECONDS)) or 1 + outer_iterations = max(1, int(round(measure_performance_iterations / inner_iterations))) + self.assertEqual(outer_iterations, 1) + + class Test01Basics(GeneratorTester): def test_alternate(self): self.do_generator_test(["one", "two"], ["one", "two", "twoone", "onetwo"]) - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_alternate_unicode(self): self.do_generator_test(["один", "два"], ["один", "два", "дваодин", "одиндва"]) @@ -192,71 +179,71 @@ def test_require(self): def test_chunksize_divisible(self): tok_it, = self.do_generator_test(["one two three four five six"], ["one", "two", "three"], "", False, 3) - self.assertEqual(tok_it.next(), ["four", "five", "six"]) - self.assertRaises(StopIteration, tok_it.next) + self.assertEqual(tok_it.__next__(), ["four", "five", "six"]) + self.assertRaises(StopIteration, tok_it.__next__) def test_chunksize_indivisible(self): tok_it, = self.do_generator_test(["one two three four five"], ["one", "two", "three"], "", False, 3) - self.assertEqual(tok_it.next(), ["four", "five"]) - self.assertRaises(StopIteration, tok_it.next) + self.assertEqual(tok_it.__next__(), ["four", "five"]) + self.assertRaises(StopIteration, tok_it.__next__) def test_chunksize_modified(self): tok_it, = self.do_generator_test(["one two three four five six"], ["one", "two"], "", False, 2) self.assertIsNone(tok_it.send( (3, False) )) - self.assertEqual(tok_it.next(), ["three", "four", "five"]) - self.assertEqual(tok_it.next(), ["six"]) - self.assertRaises(StopIteration, tok_it.next) + self.assertEqual(tok_it.__next__(), ["three", "four", "five"]) + self.assertEqual(tok_it.__next__(), ["six"]) + self.assertRaises(StopIteration, tok_it.__next__) def test_only_yield_count(self): btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one two three four five six"))) + tokenlist = StringIO(tstr("one two three four five six")), disable_security_warning_param = True) tok_it = btcrpass.password_generator(2, only_yield_count=True) - self.assertEqual(tok_it.next(), 2) + self.assertEqual(tok_it.__next__(), 2) self.assertIsNone(tok_it.send( (3, True) )) - self.assertEqual(tok_it.next(), 3) + self.assertEqual(tok_it.__next__(), 3) self.assertIsNone(tok_it.send( (3, False) )) - self.assertEqual(tok_it.next(), ["six"]) - self.assertRaises(StopIteration, tok_it.next) + self.assertEqual(tok_it.__next__(), ["six"]) + self.assertRaises(StopIteration, tok_it.__next__) btcrpass.parse_arguments(("--passwordlist __funccall --listpass"+utf8_opt).split(), - passwordlist = StringIO(tstr("one two three four five six".replace(" ", "\n")))) + passwordlist = StringIO(tstr("one two three four five six".replace(" ", "\n"))), disable_security_warning_param = True) pwl_it = btcrpass.password_generator(2, only_yield_count=True) - self.assertEqual(pwl_it.next(), 2) + self.assertEqual(pwl_it.__next__(), 2) self.assertIsNone(pwl_it.send( (3, True) )) - self.assertEqual(pwl_it.next(), 3) + self.assertEqual(pwl_it.__next__(), 3) self.assertIsNone(pwl_it.send( (3, False) )) - self.assertEqual(pwl_it.next(), ["six"]) - self.assertRaises(StopIteration, pwl_it.next) + self.assertEqual(pwl_it.__next__(), ["six"]) + self.assertRaises(StopIteration, pwl_it.__next__) def test_only_yield_count_all(self): btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one two three"))) + tokenlist = StringIO(tstr("one two three")), disable_security_warning_param = True) tok_it = btcrpass.password_generator(4, only_yield_count=True) - self.assertEqual(tok_it.next(), 3) - self.assertRaises(StopIteration, tok_it.next) + self.assertEqual(tok_it.__next__(), 3) + self.assertRaises(StopIteration, tok_it.__next__) btcrpass.parse_arguments(("--passwordlist __funccall --listpass"+utf8_opt).split(), - passwordlist = StringIO(tstr("one two three".replace(" ", "\n")))) + passwordlist = StringIO(tstr("one two three".replace(" ", "\n"))), disable_security_warning_param = True) pwl_it = btcrpass.password_generator(4, only_yield_count=True) - self.assertEqual(pwl_it.next(), 3) - self.assertRaises(StopIteration, pwl_it.next) + self.assertEqual(pwl_it.__next__(), 3) + self.assertRaises(StopIteration, pwl_it.__next__) def test_count(self): btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one two three"))) + tokenlist = StringIO(tstr("one two three")), disable_security_warning_param = True) self.assertEqual(btcrpass.count_and_check_eta(1.0), 3) def test_count_zero(self): btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr(""))) + tokenlist = StringIO(tstr("")), disable_security_warning_param = True) self.assertEqual(btcrpass.count_and_check_eta(1.0), 0) # the size of a "chunk" is == btcrpass.PASSWORDS_BETWEEN_UPDATES == 100000 def test_count_one_chunk(self): assert btcrpass.PASSWORDS_BETWEEN_UPDATES == 100000 btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("%5d"))) + tokenlist = StringIO(tstr("%5d")), disable_security_warning_param = True) self.assertEqual(btcrpass.count_and_check_eta(1.0), 100000) def test_count_two_chunks(self): assert btcrpass.PASSWORDS_BETWEEN_UPDATES == 100000 btcrpass.parse_arguments(("--tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("%5d 100000"))) + tokenlist = StringIO(tstr("%5d 100000")), disable_security_warning_param = True) self.assertEqual(btcrpass.count_and_check_eta(1.0), 100001) def test_token_counts_min_0(self): @@ -275,6 +262,31 @@ def test_token_counts_min_max_2(self): ["twoone", "onetwo", "threeone", "onethree", "threetwo", "twothree"], "--min-tokens 2 --max-tokens 2") + def test_truncate(self): + self.do_generator_test(["one", "two", "three"], + ["o", "t"], + "--truncate-length 1") + + def test_password_repeats_postypos(self): + self.do_generator_test(["aa"], + ["aa", "aaaa", "Xa", "XaXa", "aX", "aXaX"], + "--password-repeats-posttypos --typos-replace X") + + def test_password_repeats_pretypos(self): + self.do_generator_test(["aa"], + ["aa", "Xa", "aX", "aaaa", "Xaaa", "aXaa", "aaXa", "aaaX"], + "--password-repeats-pretypos --typos-replace X") + + def test_password_repeats_x3(self): + self.do_generator_test(["one"], + ["one", "oneone", "oneoneone"], + "--password-repeats-posttypos --max-password-repeats 3") + + def test_keep_tokens_order(self): + self.do_generator_test(["one", "two", "three"], + ['one', 'two', 'onetwo', 'three', 'onethree', 'twothree', 'onetwothree'], + "--keep-tokens-order") + def test_empty_file(self): self.do_generator_test([], [], test_passwordlist=True) def test_one_char_file(self): @@ -283,9 +295,9 @@ def test_comments(self): self.do_generator_test(["#one", " #two", "#three"], ["#two"]) def test_z_all(self): - self.do_generator_test(["1", "2 3", "+ 4 5"], map(tstr, [ + self.do_generator_test(["1", "2 3", "+ 4 5"], list(map(str, [ 4,41,14,42,24,421,412,241,214,142,124,43,34,431,413,341,314,143,134, - 5,51,15,52,25,521,512,251,215,152,125,53,35,531,513,351,315,153,135])) + 5,51,15,52,25,521,512,251,215,152,125,53,35,531,513,351,315,153,135]))) class Test02Anchors(GeneratorTester): @@ -351,13 +363,13 @@ def test_relative_same(self): class Test03WildCards(GeneratorTester): def test_basics_1(self): - self.do_generator_test(["%d"], map(tstr, xrange(10)), "--has-wildcards", True) + self.do_generator_test(["%d"], list(map(tstr, range(10))), "--has-wildcards", True) def test_basics_2(self): - self.do_generator_test(["%dtest"], [unicode(i)+"test" for i in xrange(10)], "--has-wildcards", True) + self.do_generator_test(["%dtest"], [str(i)+"test" for i in range(10)], "--has-wildcards", True) def test_basics_3(self): - self.do_generator_test(["te%dst"], ["te"+unicode(i)+"st" for i in xrange(10)], "--has-wildcards", True) + self.do_generator_test(["te%dst"], ["te"+str(i)+"st" for i in range(10)], "--has-wildcards", True) def test_basics_4(self): - self.do_generator_test(["test%d"], ["test"+unicode(i) for i in xrange(10)], "--has-wildcards", True) + self.do_generator_test(["test%d"], ["test"+str(i) for i in range(10)], "--has-wildcards", True) def test_invalid_nocust(self): self.expect_syntax_failure(["%c"], "invalid wildcard") @@ -367,15 +379,15 @@ def test_invalid_notype(self): self.expect_syntax_failure(["test%"], "invalid wildcard") def test_multiple(self): - self.do_generator_test(["%d%d"], ["{:02}".format(i) for i in xrange(100)], "--has-wildcards", True) + self.do_generator_test(["%d%d"], ["{:02}".format(i) for i in range(100)], "--has-wildcards", True) def test_length_2(self): - self.do_generator_test(["%2d"], ["{:02}".format(i) for i in xrange(100)], "--has-wildcards", True) + self.do_generator_test(["%2d"], ["{:02}".format(i) for i in range(100)], "--has-wildcards", True) def test_length_range(self): self.do_generator_test(["%0,2d"], [""] + - map(tstr, xrange(10)) + - ["{:02}".format(i) for i in xrange(100)], + list(map(tstr, range(10))) + + ["{:02}".format(i) for i in range(100)], "--has-wildcards", True) def test_length_invalid_range(self): @@ -386,15 +398,15 @@ def test_invalid_length_2(self): self.expect_syntax_failure(["%,2d"], "invalid wildcard") def test_case_lower(self): - self.do_generator_test(["%a"], map(tchr, xrange(ord("a"), ord("z")+1)), "--has-wildcards", True) + self.do_generator_test(["%a"], list(map(tchr, range(ord("a"), ord("z")+1))), "--has-wildcards", True) def test_case_upper(self): - self.do_generator_test(["%A"], map(tchr, xrange(ord("A"), ord("Z")+1)), "--has-wildcards", True) + self.do_generator_test(["%A"], list(map(tchr, range(ord("A"), ord("Z")+1))), "--has-wildcards", True) def test_case_insensitive_1(self): self.do_generator_test(["%ia"], - map(tchr, xrange(ord("a"), ord("z")+1)) + map(tchr, xrange(ord("A"), ord("Z")+1)), "--has-wildcards", True) + list(map(tchr, range(ord("a"), ord("z")+1))) + list(map(tchr, range(ord("A"), ord("Z")+1))), "--has-wildcards", True) def test_case_insensitive_2(self): self.do_generator_test(["%iA"], - map(tchr, xrange(ord("A"), ord("Z")+1)) + map(tchr, xrange(ord("a"), ord("z")+1)), "--has-wildcards", True) + list(map(tchr, range(ord("A"), ord("Z")+1))) + list(map(tchr, range(ord("a"), ord("z")+1))), "--has-wildcards", True) def test_custom(self): self.do_generator_test(["%c"], ["a", "b", "c", "D", "2"], "--has-wildcards --custom-wild a-cD2", True) @@ -411,6 +423,8 @@ def test_set(self): self.do_generator_test(["%[abcc-]"], ["a", "b", "c", "-"], "--has-wildcards -d", True) def test_set_insensitive(self): self.do_generator_test(["%i[abcc-]"], ["a", "b", "c", "-", "A", "B", "C"], "--has-wildcards -d", True) + def test_set_withspace(self): + self.do_generator_test(["%[a b]"], ["a", " ", "b"], "--has-wildcards -d", True) def test_noset(self): self.do_generator_test(["%%[not-a-range]"], ["%[not-a-range]"], "--has-wildcards", True) @@ -482,7 +496,7 @@ def test_capslock(self): def test_capslock_nocaps(self): self.do_generator_test(["123"], ["123"], "--typos-capslock --typos 2 -d", True) - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_capslock_unicode(self): self.do_generator_test(["Один2Три"], ["Один2Три", "оДИН2тРИ"], "--typos-capslock --typos 2 -d", True) @@ -603,38 +617,38 @@ def test_map(self): self.do_generator_test(["axb"], ["axb", "Axb", "Bxb", "axA", "axB", "AxA", "AxB", "BxA", "BxB"], "--typos-map __funccall --typos 2 -d", True, - typos_map=StringIONonClosing(tstr(" ab \t AB \n x x \n a aB "))) + typos_map=StringIONonClosing(" ab \t AB \n x x \n a aB ")) def test_map_max(self): self.do_generator_test(["axb"], ["axb", "Axb", "Bxb", "axA", "axB"], "--typos-map __funccall --max-typos-map 1 --typos 2 -d", True, - typos_map=StringIONonClosing(tstr(" ab \t AB \n x x \n a aB "))) + typos_map=StringIONonClosing(" ab \t AB \n x x \n a aB ")) def test_z_all(self): self.do_generator_test(["12"], - map(tstr, [12,812,182,128,8812,8182,8128,1882,1828,1288,112,8112,1812,1182, + list(map(tstr, [12,812,182,128,8812,8182,8128,1882,1828,1288,112,8112,1812,1182, 1128,2,82,28,92,892,982,928,122,8122,1822,1282,1228,1,81,18,19,819,189, - 198,1122,11,119,22,"",9,922,9,99,21,821,281,218,221,1,91,211,2,29]), + 198,1122,11,119,22,"",9,922,9,99,21,821,281,218,221,1,91,211,2,29])), "--typos-swap --typos-repeat --typos-delete --typos-case --typos-insert 8 --typos-replace 9 --typos 2 --max-adjacent-inserts 2 -d", True) def test_z_all_max(self): self.do_generator_test(["12"], - map(tstr, [12,812,182,128,112,8112,1812,1182,1128,2,82,28,92,892,982,928,122,8122,1822, - 1282,1228,1,81,18,19,819,189,198,11,119,22,9,922,9,21,821,281,218,221,1,91,211,2,29]), + list(map(tstr, [12,812,182,128,112,8112,1812,1182,1128,2,82,28,92,892,982,928,122,8122,1822, + 1282,1228,1,81,18,19,819,189,198,11,119,22,9,922,9,21,821,281,218,221,1,91,211,2,29])), "--typos-swap --max-typos-swap 1 --typos-repeat --max-typos-repeat 1 --typos-delete --max-typos-delete 1 " + \ "--typos-case --typos-insert 8 --max-typos-insert 1 --typos-replace 9 --max-typos-replace 1 --typos 2 -d", True) def test_z_min_typos_1(self): self.do_generator_test(["12"], - map(tstr, [88182,88128,81882,81828,81288,18828,18288,88112,81812,81182,81128, + list(map(tstr, [88182,88128,81882,81828,81288,18828,18288,88112,81812,81182,81128, 18812,18182,18128,11882,11828,11288,882,828,288,8892,8982,8928,9882,9828, 9288,88122,81822,81282,81228,18822,18282,18228,12882,12828,12288,881,818, 188,8819,8189,8198,1889,1898,1988,81122,18122,11822,11282,11228,811,181, 118,8119,1819,1189,1198,822,282,228,8,89,98,8922,9822,9282,9228,89,98,899, 989,998,8821,8281,8218,2881,2818,2188,8221,2821,2281,2218,81,18,891,981, - 918,8211,2811,2181,2118,82,28,829,289,298,2211,22,229,11,"",9,911,9,99]), + 918,8211,2811,2181,2118,82,28,829,289,298,2211,22,229,11,"",9,911,9,99])), "--typos-swap --typos-repeat --typos-delete --typos-case --typos-insert 8 --typos-replace 9 --typos 3 --max-adjacent-inserts 2 --min-typos 3 -d", True) def test_z_min_typos_2(self): @@ -648,28 +662,52 @@ class Test05CommandLine(GeneratorTester): @classmethod def setUpClass(cls): cls.LARGE_TOKENLIST_LEN = 2 * btcrpass.PASSWORDS_BETWEEN_UPDATES - cls.LARGE_TOKENLIST = tstr(" ").join(tstr(i) for i in xrange(cls.LARGE_TOKENLIST_LEN)) + cls.LARGE_TOKENLIST = tstr(" ").join(tstr(i) for i in range(cls.LARGE_TOKENLIST_LEN)) cls.LARGE_LAST_TOKEN = tstr(cls.LARGE_TOKENLIST_LEN - 1) def test_embedded_tokenlist_option(self): self.do_generator_test(["#--typos-capslock", "one"], ["one", "ONE"]) def test_embedded_tokenlist_overwridden_option(self): self.do_generator_test(["#--skip 1", "one two"], [], "--skip 2") - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_embedded_tokenlist_option_unicode(self): self.do_generator_test(["#--typos-insert в", "да"], ["да", "вда", "два", "дав"]) def test_embedded_tokenlist_option_invalid(self): self.expect_syntax_failure(["#--tokenlist file"], "--tokenlist option is not permitted inside a tokenlist file") - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + def test_embedded_passwordlist_option(self): + btcrpass.parse_arguments(("--passwordlist __funccall --passwordlist-arguments --listpass"+utf8_opt).split(), + passwordlist = StringIO(tstr("#--skip 1\none\ntwo")), disable_security_warning_param = True) + tok_it, skipped = btcrpass.password_generator_factory(2) + self.assertEqual(tok_it.__next__(), ["two"]) + + def test_embedded_passwordlist_overwridden_option(self): + btcrpass.parse_arguments(("--passwordlist __funccall --passwordlist-arguments --skip 1 --listpass"+utf8_opt).split(), + passwordlist = StringIO(tstr("#--skip 2\none\ntwo\nthree")), disable_security_warning_param = True) + tok_it, skipped = btcrpass.password_generator_factory(3) + self.assertEqual(tok_it.__next__(), ["two", "three"]) + + def test_embedded_passwordlist_option_invalid(self): + with self.assertRaises(SystemExit) as cm: + btcrpass.parse_arguments(("--passwordlist __funccall --passwordlist-arguments --listpass"+utf8_opt).split(), + passwordlist = StringIO(tstr("#--passwordlist file\none")), disable_security_warning_param = True) + self.assertIn("--passwordlist option is not permitted inside a passwordlist file", cm.exception.code) + + def test_embedded_passwordlist_option_missing(self): + with self.assertRaises(SystemExit) as cm: + btcrpass.parse_arguments(("--passwordlist __funccall --passwordlist-arguments --listpass"+utf8_opt).split(), + passwordlist = StringIO(tstr("not-an-option\none")), disable_security_warning_param = True) + self.assertIn("--passwordlist-arguments requires the first line to begin with '#--'", cm.exception.code) + + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_unicode(self): self.do_generator_test(["да"], ["да", "вда", "два", "дав"], "--typos-insert в") def test_passwordlist_no_wildcards(self): btcrpass.parse_arguments(("--passwordlist __funccall --listpass"+utf8_opt).split(), - passwordlist = StringIO(tstr("%%"))) + passwordlist = StringIO(tstr("%%")), disable_security_warning_param = True) tok_it, skipped = btcrpass.password_generator_factory(2) - self.assertEqual(tok_it.next(), ["%%"]) + self.assertEqual(tok_it.__next__(), ["%%"]) def test_regex_only(self): self.do_generator_test(["one", "two"], ["one", "twoone", "onetwo"], "--regex-only o.e") @@ -687,81 +725,81 @@ def test_delimiter_typosmap(self): # Try to test the myriad of --skip related boundary conditions in password_generator_factory() def test_skip(self): - self.do_generator_test(["one", "two"], ["twoone", "onetwo"], "--skip 2", False, sys.maxint, 2) + self.do_generator_test(["one", "two"], ["twoone", "onetwo"], "--skip 2", False, sys.maxsize, 2) def test_skip_all_exact(self): - self.do_generator_test(["one"], [], "--skip 1", True, sys.maxint, 1) + self.do_generator_test(["one"], [], "--skip 1", True, sys.maxsize, 1) def test_skip_all_pastend_1(self): - self.do_generator_test(["one"], [], "--skip 2", True, sys.maxint, 1) + self.do_generator_test(["one"], [], "--skip 2", True, sys.maxsize, 1) def test_skip_all_pastend_2(self): - self.do_generator_test(["one"], [], "--skip " + unicode(self.LARGE_TOKENLIST_LEN), True, sys.maxint, 1) + self.do_generator_test(["one"], [], "--skip " + str(self.LARGE_TOKENLIST_LEN), True, sys.maxsize, 1) def test_skip_empty_1(self): - self.do_generator_test([], [], "--skip 1", True, sys.maxint, 0) + self.do_generator_test([], [], "--skip 1", True, sys.maxsize, 0) def test_skip_empty_2(self): - self.do_generator_test([], [], "--skip " + unicode(self.LARGE_TOKENLIST_LEN), True, sys.maxint, 0) + self.do_generator_test([], [], "--skip " + str(self.LARGE_TOKENLIST_LEN), True, sys.maxsize, 0) def test_skip_large_1(self): self.do_generator_test([self.LARGE_TOKENLIST], [self.LARGE_LAST_TOKEN], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN - 1), - False, sys.maxint, self.LARGE_TOKENLIST_LEN - 1) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN - 1), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN - 1) def test_skip_large_1_all_exact(self): self.do_generator_test([self.LARGE_TOKENLIST], [], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN), - False, sys.maxint, self.LARGE_TOKENLIST_LEN) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN) def test_skip_large_1_all_pastend(self): self.do_generator_test([self.LARGE_TOKENLIST], [], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN + 1), - False, sys.maxint, self.LARGE_TOKENLIST_LEN) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN + 1), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN) def test_skip_large_2(self): self.do_generator_test([self.LARGE_TOKENLIST + " last"], ["last"], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN), - False, sys.maxint, self.LARGE_TOKENLIST_LEN) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN) def test_skip_large_2_all_exact(self): self.do_generator_test([self.LARGE_TOKENLIST + " last"], [], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN + 1), - False, sys.maxint, self.LARGE_TOKENLIST_LEN + 1) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN + 1), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN + 1) def test_skip_large_2_all_pastend(self): self.do_generator_test([self.LARGE_TOKENLIST + " last"], [], - "-d --skip " + unicode(self.LARGE_TOKENLIST_LEN + 2), - False, sys.maxint, self.LARGE_TOKENLIST_LEN + 1) + "-d --skip " + str(self.LARGE_TOKENLIST_LEN + 2), + False, sys.maxsize, self.LARGE_TOKENLIST_LEN + 1) def test_skip_end2end(self): btcrpass.parse_arguments(("--skip 2 --tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one \n two"))) + tokenlist = StringIO(tstr("one \n two")), disable_security_warning_param = True) self.assertIn("2 password combinations (plus 2 skipped)", btcrpass.main()[1]) def test_skip_end2end_all_exact(self): btcrpass.parse_arguments(("--skip 4 --tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one \n two"))) + tokenlist = StringIO(tstr("one \n two")), disable_security_warning_param = True) self.assertIn("0 password combinations (plus 4 skipped)", btcrpass.main()[1]) def test_skip_end2end_all_pastend(self): btcrpass.parse_arguments(("--skip 5 --tokenlist __funccall --listpass"+utf8_opt).split(), - tokenlist = StringIO(tstr("one \n two"))) + tokenlist = StringIO(tstr("one \n two")), disable_security_warning_param = True) self.assertIn("0 password combinations (plus 4 skipped)", btcrpass.main()[1]) def test_skip_end2end_all_noeta(self): btcrpass.parse_arguments(("--skip 5 --tokenlist __funccall --no-eta --wallet __null"+utf8_opt).split(), - tokenlist = StringIO(tstr("one \n two"))) + tokenlist = StringIO(tstr("one \n two")), disable_security_warning_param = True) self.assertIn("Skipped all 4 passwords", btcrpass.main()[1]) def test_max_eta(self): btcrpass.parse_arguments(("--max-eta 1 --tokenlist __funccall --wallet __null"+utf8_opt).split(), - tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11"))) + tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11")), disable_security_warning_param = True) with self.assertRaises(SystemExit) as cm: btcrpass.count_and_check_eta(360.0) # 360s * 11 passwords > 1 hour self.assertIn("at least 11 passwords to try, ETA > --max-eta option (1 hours)", cm.exception.code) def test_max_eta_ok(self): btcrpass.parse_arguments(("--max-eta 1 --tokenlist __funccall --wallet __null"+utf8_opt).split(), - tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10"))) + tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10")), disable_security_warning_param = True) self.assertEqual(btcrpass.count_and_check_eta(360.0), 10) # 360s * 10 passwords <= 1 hour def test_max_eta_skip(self): btcrpass.parse_arguments(("--max-eta 1 --skip 4 --tokenlist __funccall --wallet __null"+utf8_opt).split(), - tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"))) + tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15")), disable_security_warning_param = True) with self.assertRaises(SystemExit) as cm: btcrpass.count_and_check_eta(360.0) # 360s * 11 passwords > 1 hour self.assertIn("at least 11 passwords to try, ETA > --max-eta option (1 hours)", cm.exception.code) def test_max_eta_skip_ok(self): btcrpass.parse_arguments(("--max-eta 1 --skip 5 --tokenlist __funccall --wallet __null"+utf8_opt).split(), - tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"))) + tokenlist = StringIO(tstr("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15")), disable_security_warning_param = True) # 360s * 10 passwords <= 1 hour, but count_and_check_eta still returns the total count of 15 self.assertEqual(btcrpass.count_and_check_eta(360.0), 15) - def test_worker(self): + def test_worker_evensplit(self): self.do_generator_test(["one two three four five six seven eight"], ["one", "four", "seven"], "--worker 1/3") self.do_generator_test(["one two three four five six seven eight"], ["two", "five", "eight"], @@ -769,6 +807,14 @@ def test_worker(self): self.do_generator_test(["one two three four five six seven eight"], ["three", "six"], "--worker 3/3") + def test_worker_unevensplit(self): + self.do_generator_test(["one two three four five six seven eight"], ["one", "two", "four", "five", "seven", "eight"], + "--worker 1,2/3") + self.do_generator_test(["one two three four five six seven eight"], ["two", "three", "five", "six", "eight"], + "--worker 2,3/3") + self.do_generator_test(["one two three four five six seven eight"], ["one", "two", "three", "four", "five", "six", "seven", "eight"], + "--worker 1,2,3/3") + def test_no_dupchecks_1(self): self.do_generator_test(["one", "one"], ["one", "one", "oneone", "oneone"], "-ddd") self.do_generator_test(["one", "one"], ["one", "one", "oneone"], "-dd") @@ -776,13 +822,13 @@ def test_no_dupchecks_1(self): def test_no_dupchecks_2(self): self.do_generator_test(["one", "one"], ["one", "oneone"], "-d") # Duplicate code works differently the second time around; test it also - self.assertEqual(btcrpass.password_generator(3).next(), ["one", "oneone"]) + self.assertEqual(btcrpass.password_generator(3).__next__(), ["one", "oneone"]) def test_no_dupchecks_3(self): self.do_generator_test(["%[ab] %[a-b]"], ["a", "b", "a", "b"], "-d") self.do_generator_test(["%[ab] %[a-b]"], ["a", "b"]) # Duplicate code works differently the second time around; test it also - self.assertEqual(btcrpass.password_generator(3).next(), ["a", "b"]) + self.assertEqual(btcrpass.password_generator(3).__next__(), ["a", "b"]) # Need to check four different code paths for --exclude-passwordlist def test_exclude(self): @@ -814,13 +860,15 @@ def run_autosave_parse_arguments(self, autosave_file): btcrpass.parse_arguments(self.AUTOSAVE_ARGS, autosave = autosave_file, tokenlist = StringIO(self.AUTOSAVE_TOKENLIST), - data_extract = self.AUTOSAVE_DATA_EXTRACT) + data_extract = self.AUTOSAVE_DATA_EXTRACT, + disable_security_warning_param = True) def run_restore_parse_arguments(self, restore_file): btcrpass.parse_arguments("--restore __funccall".split(), restore = restore_file, tokenlist = StringIO(self.AUTOSAVE_TOKENLIST), - data_extract = self.AUTOSAVE_DATA_EXTRACT) + data_extract = self.AUTOSAVE_DATA_EXTRACT, + disable_security_warning_param = True) # These test_ functions are in alphabetical order (the same order they're executed in) @@ -828,18 +876,18 @@ def run_restore_parse_arguments(self, restore_file): def test_autosave(self): autosave_file = self.autosave_file self.run_autosave_parse_arguments(autosave_file) - self.assertIn("Password search exhausted", btcrpass.main()[1]) + self.assertIn(btcrpass.searchfailedtext, btcrpass.main()[1]) # # Load slot 0, and verify it was created before any passwords were tested autosave_file.seek(0) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 0) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 0) self.assertLessEqual(autosave_file.tell(), SAVESLOT_SIZE) # # Load slot 1, and verify it was created after all passwords were tested autosave_file.seek(SAVESLOT_SIZE) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 9) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 9) self.assertLessEqual(autosave_file.tell(), 2*SAVESLOT_SIZE) # Using --autosave, restore (a copy of) the autosave data created by test_autosave(), @@ -861,8 +909,9 @@ def test_restore_changed_args(self): btcrpass.parse_arguments(self.AUTOSAVE_ARGS + ["--typos-capslock"], autosave = BytesIO(self.autosave_file.getvalue()), tokenlist = StringIO(self.AUTOSAVE_TOKENLIST), - data_extract = self.AUTOSAVE_DATA_EXTRACT) - self.assertIn("can't restore previous session: the command line options have changed", cm.exception.code) + data_extract = self.AUTOSAVE_DATA_EXTRACT, + disable_security_warning_param = True) + self.assertIn("Disallowed Arguments Difference:", cm.exception.code) # Using --autosave, restore (a copy of) the autosave data created by test_autosave(), # but change the tokenlist file to generate an error @@ -871,7 +920,8 @@ def test_restore_changed_tokenlist(self): btcrpass.parse_arguments(self.AUTOSAVE_ARGS, autosave = BytesIO(self.autosave_file.getvalue()), tokenlist = StringIO(self.AUTOSAVE_TOKENLIST + "four"), - data_extract = self.AUTOSAVE_DATA_EXTRACT) + data_extract = self.AUTOSAVE_DATA_EXTRACT, + disable_security_warning_param = True) self.assertIn("can't restore previous session: the tokenlist file has changed", cm.exception.code) # Using --restore, restore (a copy of) the autosave data created by test_autosave(), @@ -881,7 +931,8 @@ def test_restore_changed_data_extract(self): btcrpass.parse_arguments("--restore __funccall".split(), restore = BytesIO(self.autosave_file.getvalue()), tokenlist = StringIO(self.AUTOSAVE_TOKENLIST), - data_extract = "bWI6ACkebfNQTLk75CfI5X3svX6AC7NFeGsgUxKNFg==") # has a valid CRC + data_extract = "bWI6ACkebfNQTLk75CfI5X3svX6AC7NFeGsgUxKNFg==", + disable_security_warning_param = True) # has a valid CRC self.assertIn("can't restore previous session: the encrypted key entered is not the same", cm.exception.code) # Using --restore, restore the autosave data created by test_autosave(), @@ -894,19 +945,19 @@ def test_restore_truncated(self): # # Slot 1 had the final save, but since it is invalid, the loader should fall # back to slot 0 with the initial save, so the passwords should be tried again. - self.assertIn("Password search exhausted", btcrpass.main()[1]) + self.assertIn(btcrpass.searchfailedtext, btcrpass.main()[1]) # # Because slot 1 was invalid, it is the first slot overwritten. Load it, and # verify it was written to before any passwords were tested autosave_file.seek(SAVESLOT_SIZE) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 0) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 0) # # Load slot 0 (the second slot overwritten), and verify it was written to # after all passwords were tested autosave_file.seek(0) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 9) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 9) is_pycrypto_loadable = None @@ -914,37 +965,15 @@ def can_load_pycrypto(): global is_pycrypto_loadable if is_pycrypto_loadable is None: print(warnings.filters) - is_pycrypto_loadable = btcrpass.load_aes256_library().__name__ == b"Crypto" + is_pycrypto_loadable = btcrpass.load_aes256_library().__name__ == "Crypto" return is_pycrypto_loadable -is_hashlib_pbkdf2_available = None -def has_hashlib_pbkdf2(): - global is_hashlib_pbkdf2_available - if is_hashlib_pbkdf2_available is None: - is_hashlib_pbkdf2_available = btcrpass.load_pbkdf2_library().__name__ == b"hashlib" - return is_hashlib_pbkdf2_available - -is_armory_loadable = None -def can_load_armory(): - if tstr == unicode: - return False - global is_armory_loadable - # Don't call the load function more than once - # (calling more than once on success is OK though) - if is_armory_loadable is None: - try: - btcrpass.load_armory_library() - is_armory_loadable = True - except ImportError: - is_armory_loadable = False - return is_armory_loadable - is_protobuf_loadable = None def can_load_protobuf(): global is_protobuf_loadable if is_protobuf_loadable is None: try: - from .. import wallet_pb2 + from .. import bitcoinj_pb2 is_protobuf_loadable = True except ImportError: is_protobuf_loadable = False @@ -955,28 +984,18 @@ def can_load_scrypt(): global pylibscrypt if pylibscrypt is None: try: - import pylibscrypt + from lib import pylibscrypt except ImportError: pylibscrypt = False return pylibscrypt and pylibscrypt._done # True iff a binary implementation was found -is_ripemd_available = None -def has_ripemd160(): - global is_ripemd_available - if is_ripemd_available is None: - try: - hashlib.new(b"ripemd160") - is_ripemd_available = True - except ValueError: - is_ripemd_available = False - return is_ripemd_available - is_sha3_loadable = None -def can_load_sha3(): +def can_load_keccak(): global is_sha3_loadable if is_sha3_loadable is None: try: - import sha3 + from lib.eth_hash.auto import keccak + keccak(b'') is_sha3_loadable = True except ImportError: is_sha3_loadable = False @@ -993,6 +1012,131 @@ def can_load_coincurve(): is_coincurve_loadable = False return is_coincurve_loadable +is_groestlcoin_hash_loadable = None +def can_load_groestlcoin_hash(): + global is_groestlcoin_hash_loadable + if is_groestlcoin_hash_loadable is None: + try: + import groestlcoin_hash + is_groestlcoin_hash_loadable = True + except ImportError: + is_groestlcoin_hash_loadable = False + return is_groestlcoin_hash_loadable + +bundled_bitcoinlib_mod_available = None +def can_load_bundled_bitcoinlib_mod(): + global bundled_bitcoinlib_mod_available + if bundled_bitcoinlib_mod_available is None: + try: + from lib.bitcoinlib_mod import encoding as encoding_mod + + bundled_bitcoinlib_mod_available = True + except: + bundled_bitcoinlib_mod_available = False + return bundled_bitcoinlib_mod_available + +is_ecdsa_loadable = None +def can_load_ecdsa(): + global is_ecdsa_loadable + if is_ecdsa_loadable is None: + try: + import ecdsa + is_ecdsa_loadable = True + except ImportError: + is_ecdsa_loadable = False + return is_ecdsa_loadable + +is_bitcoinutils_loadable = None +def can_load_bitcoinutils(): + global is_bitcoinutils_loadable + if is_bitcoinutils_loadable is None: + try: + import bitcoinutils + is_bitcoinutils_loadable = True + except ImportError: + is_bitcoinutils_loadable = False + return is_bitcoinutils_loadable + +is_eth_keyfile_loadable = None +def can_load_eth_keyfile(): + global is_eth_keyfile_loadable + if is_eth_keyfile_loadable is None: + try: + import eth_keyfile + is_eth_keyfile_loadable = True + except ImportError: + is_eth_keyfile_loadable = False + return is_eth_keyfile_loadable + +is_leveldb_loadable = None +def can_load_leveldb(): + global is_leveldb_loadable + if is_leveldb_loadable is None: + try: + from lib.ccl_chrome_indexeddb import ccl_leveldb + is_leveldb_loadable = True + except: + is_leveldb_loadable = False + return is_leveldb_loadable + +is_PyCryptoHDWallet_loadable = None +def can_load_PyCryptoHDWallet(): + global is_PyCryptoHDWallet_loadable + if is_PyCryptoHDWallet_loadable is None: + try: + import py_crypto_hd_wallet + is_PyCryptoHDWallet_loadable = True + except: + is_PyCryptoHDWallet_loadable = False + return is_PyCryptoHDWallet_loadable + +is_ShamirMnemonic_loadable = None +def can_load_ShamirMnemonic(): + global is_ShamirMnemonic_loadable + if is_ShamirMnemonic_loadable is None: + try: + import shamir_mnemonic + is_ShamirMnemonic_loadable = True + except: + is_ShamirMnemonic_loadable = False + return is_ShamirMnemonic_loadable + +eth2_staking_deposit_available = None +def can_load_staking_deposit(): + global eth2_staking_deposit_available + if eth2_staking_deposit_available is None: + try: + from staking_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key + from py_ecc.bls import G2ProofOfPossession as bls + + eth2_staking_deposit_available = True + except: + eth2_staking_deposit_available = False + return eth2_staking_deposit_available + +# Modules dependant on SJCL +sjcl_available = None +def can_load_sjcl(): + global sjcl_available + if sjcl_available is None: + try: + from sjcl import SJCL + sjcl_available = True + except: + sjcl_available = False + return sjcl_available + +is_nacl_loadable = None +def can_load_nacl(): + global is_nacl_loadable + if is_nacl_loadable is None: + try: + import nacl.pwhash + import nacl.secret + is_nacl_loadable = True + except: + is_nacl_loadable = False + return is_nacl_loadable # Wrapper for btcrpass.init_worker() which clears btcrpass.loaded_wallet to simulate the way # multiprocessing works on Windows (even on other OSs) and permits pure python library testing @@ -1003,19 +1147,50 @@ def init_worker(wallet, char_mode, force_purepython, force_kdf_purepython): if force_kdf_purepython: btcrpass.load_pbkdf2_library(force_purepython=True) +opencl_device_count = None +def has_any_opencl_devices(): + global opencl_device_count + global opencl_devices_list + if opencl_device_count is None: + try: + opencl_devices_list = list(btcrpass.get_opencl_devices()) + except ImportError: + opencl_devices_list = () + opencl_device_count = len(opencl_devices_list) + return opencl_device_count > 0 + +def is_pocl_platform(): + if not has_any_opencl_devices(): + return False + try: + import pyopencl as cl + return cl.get_platforms()[0].name.startswith("Portable Computing Language") + except Exception: + return False + class Test07WalletDecryption(unittest.TestCase): # Checks a test wallet against the known password, and ensures # that the library doesn't make any changes to the wallet file - def wallet_tester(self, wallet_filename, + def wallet_tester(self, arg_wallet_filename, force_purepython = False, force_kdf_purepython = False, force_bsddb_purepython = False, correct_pass = None, blockchain_mainpass = None, android_backuppass = None): - wallet_filename = os.path.join(WALLET_DIR, wallet_filename) + wallet_filename = os.path.join(WALLET_DIR, arg_wallet_filename) temp_dir = tempfile.mkdtemp("-test-btcr") parent_process = True # bug workaround, see finally block below for details try: temp_wallet_filename = os.path.join(temp_dir, os.path.basename(wallet_filename)) - shutil.copyfile(wallet_filename, temp_wallet_filename) + try: + shutil.copyfile(wallet_filename, temp_wallet_filename) + except PermissionError: + temp_wallet_filename = "./btcrecover/test/test-wallets/" + arg_wallet_filename + + except IsADirectoryError: + temp_wallet_filename = "./btcrecover/test/test-wallets/" + arg_wallet_filename + + except FileNotFoundError: + temp_wallet_filename = "./btcrecover/test/test-wallets/" + arg_wallet_filename + if android_backuppass: wallet = btcrpass.WalletAndroidSpendingPIN.load_from_filename( @@ -1034,7 +1209,6 @@ def wallet_tester(self, wallet_filename, if not correct_pass: correct_pass = "btcr-test-password" - correct_pass = tstr(correct_pass) # Perform the tests in the current process self.assertEqual(wallet.return_verified_password_or_false( @@ -1049,37 +1223,57 @@ def wallet_tester(self, wallet_filename, password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, ( ( tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2") ), ( tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4") ) )) - self.assertEqual(password_found_iterator.next(), (False, 2)) - self.assertEqual(password_found_iterator.next(), (correct_pass, 2)) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) self.assertRaises(StopIteration, password_found_iterator.next) pool.close() pool.join() del wallet gc.collect() - self.assertTrue(filecmp.cmp(wallet_filename, temp_wallet_filename, False)) # False == always compare file contents finally: # There's a bug which only occurs when combining unittest, multiprocessing, and "real" # forking (Linux/BSD/WSL); only remove the temp dir if we're sure this is the parent process if parent_process: shutil.rmtree(temp_dir) - @skipUnless(can_load_armory, "requires Armory and ASCII mode") - def test_armory(self): - self.wallet_tester("armory-wallet.wallet") - - @skipUnless(can_load_pycrypto, "requires PyCrypto") - def test_bitcoincore(self): + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_bitcoincore_bdb(self): self.wallet_tester("bitcoincore-wallet.dat") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_bitcoincore_bdbhd(self): + self.wallet_tester("bitcoincore-0.20.1-wallet.dat") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_bitcoincore_sqlite(self): + self.wallet_tester("bitcoincore-0.21.1-wallet.dat") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_litecoincore(self): + self.wallet_tester("litecoincore-0.18.1-wallet.dat") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_dogecoincore(self): + self.wallet_tester("dogecoincore-1.14.2-wallet.dat") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_tenupcore(self): + self.wallet_tester("tenup-1.1.0.4-wallet.dat") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum(self): self.wallet_tester("electrum-wallet") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum27(self): self.wallet_tester("electrum27-wallet") + # A special case that tests an old wallet file that has been opened, but not unlocked in Electrum 4.x + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_electrum27_updated_to_4(self): + self.wallet_tester("electrum27-wallet-updated") + def test_electrum27_multisig(self): self.wallet_tester("electrum27-multisig-wallet") @@ -1099,35 +1293,86 @@ def test_electrum27_upgradedfrom_electrum1(self): self.wallet_tester("electrum1-upgradedto-electrum27-wallet") @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum28(self): self.wallet_tester("electrum28-wallet") + @skipUnless(can_load_coincurve, "requires coincurve") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_electrum41(self): + self.wallet_tester("electrum41-wallet") + + @skipUnless(can_load_coincurve, "requires coincurve") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_electrum4_4_3_unencrypted(self): + self.wallet_tester("electrum4_4_3_unencrypted") + + @skipUnless(can_load_coincurve, "requires coincurve") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_electrum_100kbwallet(self): + self.wallet_tester("electrum28-100kbwallet") + @skipUnless(can_load_coincurve, "requires coincurve") def test_electrum28_pp(self): self.wallet_tester("electrum28-wallet", force_purepython=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_multibit(self): self.wallet_tester("multibit-wallet.key") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_protobuf, "requires protobuf") + @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_bitcoinj_multibit(self): + self.wallet_tester("multibit.wallet.bitcoinj.encrypted") + + @skipUnless(can_load_protobuf, "requires protobuf") + @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_coinomi_android(self): + self.wallet_tester("coinomi.wallet.android") + + @skipUnless(can_load_protobuf, "requires protobuf") + @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_coinomi_desktop(self): + self.wallet_tester("coinomi.wallet.desktop") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_multidoge(self): + self.wallet_tester("multidoge-wallet.key") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_dogecoin_wallet_android_backup(self): + self.wallet_tester("dogecoin-wallet-android-backup") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd(self): self.wallet_tester("mbhd.wallet.aes") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd_v0_5_0(self): self.wallet_tester(os.path.join("multibithd-v0.5.0", "mbhd.wallet.aes")) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_protobuf, "requires protobuf") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_bitcoinj(self): self.wallet_tester("bitcoinj-wallet.wallet") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + @skipUnless(can_load_protobuf, "requires protobuf") + def test_android_bitcoin_wallet(self): + self.wallet_tester("android-bitcoin-wallet-backup") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + @skipUnless(can_load_protobuf, "requires protobuf") + def test_android_bitcoin_wallet_2022(self): + self.wallet_tester("android-bitcoin-wallet-backup-2022") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_protobuf, "requires protobuf") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_androidpin(self): @@ -1139,49 +1384,175 @@ def test_androidpin(self): def test_androidpin_unencrypted(self): self.wallet_tester("bitcoinj-wallet.wallet", android_backuppass="IGNORED") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_bither(self): self.wallet_tester("bither-wallet.db") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(has_ripemd160, "requires that hashlib implements RIPEMD-160") def test_bither_hdonly(self): self.wallet_tester("bither-hdonly-wallet.db") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_msigna(self): self.wallet_tester("msigna-wallet.vault") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v1_1(self): + self.wallet_tester("blockchain-github-v1-1", correct_pass="mypassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v1_2(self): + self.wallet_tester("blockchain-github-v1-2", correct_pass="mypassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v1_3(self): + self.wallet_tester("blockchain-github-v1-3", correct_pass="mypassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v2_1(self): + self.wallet_tester("blockchain-github-v2-1", correct_pass="SomeTestPassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v2_2(self): + self.wallet_tester("blockchain-github-v2-2", correct_pass="SomeTestPassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v3_1(self): + self.wallet_tester("blockchain-github-v3-1", correct_pass="SomeTestPassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v3_2(self): + self.wallet_tester("blockchain-github-v3-2", correct_pass="SomeTestPassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_github_v1_3_secondpass(self): + self.wallet_tester("blockchain-github-v1-3", blockchain_mainpass="mypassword", correct_pass="mysecondpassword") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v0_Jan2014(self): + self.wallet_tester("blockchain-v0.0-Jan2014-wallet.aes.json", correct_pass="testblockchain") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v0_Mar2012_uncompressed(self): + self.wallet_tester("blockchain-v0.0-march2012-uncompressed-wallet.aes.json", correct_pass="inmydreams") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v3_Jan2021(self): + self.wallet_tester("blockchain-v3.0-Jan2021-Android.json", correct_pass="Testing123!") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_v0(self): self.wallet_tester("blockchain-v0.0-wallet.aes.json") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v3(self): + self.wallet_tester("blockchain-v3.0-MAY2020-wallet.aes.json") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_v2(self): self.wallet_tester("blockchain-v2.0-wallet.aes.json") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v4(self): + self.wallet_tester("blockchain-v4.0-wallet.aes.json") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_secondpass_v0(self): self.wallet_tester("blockchain-v0.0-wallet.aes.json", blockchain_mainpass="btcr-test-password") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_secondpass_v2(self): self.wallet_tester("blockchain-v2.0-wallet.aes.json", blockchain_mainpass="btcr-test-password") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") def test_blockchain_secondpass_unencrypted(self): # this wallet has no second-password iter_count, so this case is also tested here self.wallet_tester("blockchain-unencrypted-wallet.aes.json", blockchain_mainpass="IGNORED") + def test_dogechain_info_cpu(self): + self.wallet_tester("dogechain.wallet.aes.json") + + def test_dogechain_info_cpu_2024_CBC(self): + self.wallet_tester("dogechain.wallet.aes.json.2024-cbc") + + def test_dogechain_info_cpu_2024_GCM(self): + self.wallet_tester("dogechain.wallet.aes.json.2024-gcm") + + @skipUnless(can_load_ecdsa, "requires ECDSA") + @skipUnless(can_load_bitcoinutils, "requires Bitcoin-Utils") + def test_block_io_privkeyrequest_data_legacy_cpu(self): + self.wallet_tester("block.io.request.legacy.json", correct_pass="Anhday12") + + @skipUnless(can_load_ecdsa, "requires ECDSA") + @skipUnless(can_load_bitcoinutils, "requires Bitcoin-Utils") + def test_block_io_privkeyrequest_data_cpu(self): + self.wallet_tester("block.io.request.json", correct_pass="btcrtestpassword2022") + + @skipUnless(can_load_ecdsa, "requires ECDSA") + @skipUnless(can_load_bitcoinutils, "requires Bitcoin-Utils") + def test_block_io_pinchange_data_cpu(self): + self.wallet_tester("block.io.change.json", correct_pass="btcrtestpassword2022") + + @skipUnless(can_load_sjcl, "requires SJCL") + def test_bitgo_keycard_userkey(self): + self.wallet_tester("bitgo_keycard_userkey.json", correct_pass="btcr-test-password") + + def test_btc_com_backup(self): + self.wallet_tester("btc_com_parsed_wallet_data_v3_random.json", correct_pass="santacruzbolivia") + + @skipUnless(can_load_nacl, "requires NaCl") + def test_toastwallet(self): + self.wallet_tester("toastwallet.txt", correct_pass="Btcr-test-passw0rd") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_leveldb_chrome_cpu(self): + self.wallet_tester("metamask/nkbihfbeogaeaoehlefnkodbefgpgknn") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_v10_11_3_leveldb_chrome_cpu(self): + self.wallet_tester("metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v10_11_3") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_v11_12_1_leveldb_chrome_cpu(self): + self.wallet_tester("metamask/nkbihfbeogaeaoehlefnkodbefgpgknn-v11_12_1") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_v11_12_1_json_chrome_cpu(self): + self.wallet_tester("metamask_vault_v11_12_1.txt") + + def test_metamask_JSON_firefox_cpu(self): + self.wallet_tester("metamask.9.8.4_firefox_vault") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_binancechainwallet_leveldb_cpu(self): + self.wallet_tester("metamask/fhbohimaelbohpjbbldcngcnapndodjp", correct_pass="BTCR-test-passw0rd") + + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + def test_metamask_ronin_leveldb_cpu(self): + self.wallet_tester("metamask/fnjhmkhhmkbjkkabndcnnogagogbneec") + + def test_metamask_JSON_iOS_cpu(self): + self.wallet_tester("metamask.ios.persist-root") + + def test_metamask_JSON_Android_cpu(self): + self.wallet_tester("metamask.android.persist-root") + def test_bitcoincore_pywallet(self): self.wallet_tester("bitcoincore-pywallet-dumpwallet.txt") + @skipUnless(can_load_eth_keyfile, "requires Eth-Keyfile module") + def test_eth_keystore_scrypt(self): + self.wallet_tester("utc-keystore-v3-scrypt-myetherwallet.json") + + @skipUnless(can_load_eth_keyfile, "requires Eth-Keyfile module") + def test_eth_keystore_pbkdf2(self): + self.wallet_tester("utc-keystore-v3-pbkdf2-custom.json") + + @skipUnless(can_load_eth_keyfile, "requires Eth-Keyfile module") + def test_imtoken_keystore(self): + self.wallet_tester("imtoken-identity.json") + # Make sure the Blockchain wallet loader can heuristically determine that files containing # base64 data that doesn't look entirely encrypted (random) are not Blockchain wallets def test_blockchain_invalid(self): @@ -1196,6 +1567,12 @@ def test_bitcoincore_pp(self): def test_bitcoincore_no_bsddb(self): self.wallet_tester("bitcoincore-wallet.dat", force_bsddb_purepython=True) + def test_litecoincore_pp(self): + self.wallet_tester("litecoincore-0.18.1-wallet.dat", force_purepython=True) + + def test_litecoincore_no_bsddb(self): + self.wallet_tester("litecoincore-0.18.1-wallet.dat", force_bsddb_purepython=True) + def test_electrum_pp(self): self.wallet_tester("electrum-wallet", force_purepython=True) @@ -1226,7 +1603,6 @@ def test_bither_pp(self): @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(has_ripemd160, "requires that hashlib implements RIPEMD-160") def test_bither_hdonly_pp(self): self.wallet_tester("bither-hdonly-wallet.db", force_purepython=True) @@ -1239,6 +1615,9 @@ def test_blockchain_v0_pp(self): def test_blockchain_v2_pp(self): self.wallet_tester("blockchain-v2.0-wallet.aes.json", force_purepython=True, force_kdf_purepython=True) + def test_blockchain_v3_pp(self): + self.wallet_tester("blockchain-v3.0-MAY2020-wallet.aes.json", force_purepython=True, force_kdf_purepython=True) + def test_blockchain_secondpass_v0_pp(self): self.wallet_tester("blockchain-v0.0-wallet.aes.json", force_purepython=True, force_kdf_purepython=True, blockchain_mainpass="btcr-test-password") @@ -1255,289 +1634,879 @@ def test_invalid_wallet(self): btcrpass.load_wallet(__file__) self.assertIn("unrecognized wallet format", cm.exception.code) + @skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+") + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_metamask_chrome_leveldb_OpenCL_Brute(self): + wallet_filename = "./btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn" + + btcrpass.loaded_wallet = btcrpass.WalletMetamask.load_from_filename(wallet_filename) + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletMetamask._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletMetamask._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") + + del btcrpass.loaded_wallet + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_metamask_JSON_ios_OpenCL_Brute(self): + wallet_filename = "./btcrecover/test/test-wallets/metamask.ios.persist-root" + + btcrpass.loaded_wallet = btcrpass.WalletMetamask.load_from_filename(wallet_filename) + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletMetamask._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletMetamask._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") + + del btcrpass.loaded_wallet + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_blockchain_second_OpenCL_Brute(self): + wallet_filename = os.path.join(WALLET_DIR, "blockchain-v2.0-wallet.aes.json") + temp_dir = tempfile.mkdtemp("-test-btcr") + temp_wallet_filename = os.path.join(temp_dir, os.path.basename(wallet_filename)) + shutil.copyfile(wallet_filename, temp_wallet_filename) + + btcrpass.loaded_wallet = btcrpass.WalletBlockchainSecondpass.load_from_filename(temp_wallet_filename, password="btcr-test-password") + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletBlockchainSecondpass._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletBlockchainSecondpass._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") + + del btcrpass.loaded_wallet + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + @skipUnless(lambda: not is_pocl_platform(), "OpenCL MultiBit kernel not supported on PoCL") + def test_multibit_OpenCL_Brute(self): + wallet_filename = os.path.join(WALLET_DIR, "multibit-wallet.key") + temp_dir = tempfile.mkdtemp("-test-btcr") + temp_wallet_filename = os.path.join(temp_dir, os.path.basename(wallet_filename)) + shutil.copyfile(wallet_filename, temp_wallet_filename) + + btcrpass.loaded_wallet = btcrpass.WalletMultiBit.load_from_filename(temp_wallet_filename) + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletMultiBit._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletMultiBit._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") + + del btcrpass.loaded_wallet + + def Electrum28_tester_OpenCL_Brute(self, walletfile): + wallet_filename = os.path.join(WALLET_DIR, walletfile) + temp_dir = tempfile.mkdtemp("-test-btcr") + temp_wallet_filename = os.path.join(temp_dir, os.path.basename(wallet_filename)) + shutil.copyfile(wallet_filename, temp_wallet_filename) + + btcrpass.loaded_wallet = btcrpass.load_wallet(temp_wallet_filename) + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletElectrum28._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletElectrum28._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") + + del btcrpass.loaded_wallet + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_electrum28_OpenCL_Brute(self): + self.Electrum28_tester_OpenCL_Brute("electrum28-wallet") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_electrum41_OpenCL_Brute(self): + self.Electrum28_tester_OpenCL_Brute("electrum41-wallet") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_electrum28_100kbwallet_OpenCL_Brute(self): + self.Electrum28_tester_OpenCL_Brute("electrum28-100kbwallet") class Test08BIP39Passwords(unittest.TestCase): + def bip39_tester_opencl(self, force_purepython = False, unicode_pw = False, *args, **kwargs): + + wallet = btcrpass.WalletBIP39(*args, **kwargs) + if force_purepython: btcrpass.load_pbkdf2_library(force_purepython=True) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + # Perform the tests in the current process + correct_pass = "btcr-test-password" if not unicode_pw else "btcr-тест-пароль" + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + @skipUnless(can_load_coincurve, "requires coincurve") + def test_bip39_mpk_opencl_brute(self): + self.bip39_tester_opencl( + mpk= "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" + ) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + @skipUnless(can_load_coincurve, "requires coincurve") + def test_Electrum2_mpk_opencl_brute(self): + self.bip39_tester_opencl( + wallet_type= "Electrum2", + mpk= "zpub6oCYZXxa8YvFyR51r12U7q5B2cbeY25MqRnWTdXYex1EPuTvbfmeJmCFoo88xbqkgHyitfK1UW2q5CTPUW8fWqpZtsDF3jVwk6PTdGTbX2w", + mnemonic= "quote voice evidence aspect warfare hire system black rate wing ask rug" + ) + def bip39_tester(self, force_purepython = False, unicode_pw = False, *args, **kwargs): wallet = btcrpass.WalletBIP39(*args, **kwargs) if force_purepython: btcrpass.load_pbkdf2_library(force_purepython=True) # Perform the tests in the current process - correct_pass = tstr("btcr-test-password") if not unicode_pw else "btcr-тест-пароль" - self.assertEqual(wallet.return_verified_password_or_false( + correct_pass = "btcr-test-password" if not unicode_pw else "btcr-тест-пароль" + self.assertEqual(wallet._return_verified_password_or_false_cpu( (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) - self.assertEqual(wallet.return_verified_password_or_false( + self.assertEqual(wallet._return_verified_password_or_false_cpu( (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) # Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded + wallet.opencl = False pool = multiprocessing.Pool(1, init_worker, (wallet, tstr, force_purepython, False)) password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, ( ( tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2") ), ( tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4") ) )) - self.assertEqual(password_found_iterator.next(), (False, 2)) - self.assertEqual(password_found_iterator.next(), (correct_pass, 2)) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) self.assertRaises(StopIteration, password_found_iterator.next) pool.close() pool.join() - @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") - def test_bip39_mpk(self): - self.bip39_tester( - mpk= "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", - mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" - ) + def cardano_tester(self, *args, **kwargs): - @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - def test_bip39_unicode_password(self): - self.bip39_tester( - mpk= "xpub6CZe1G1A1CaaSepbekLMSk1sBRNA9kHZzEQCedudHAQHHB21FW9fYpQWXBevrLVQfL8JFQVFWEw3aACdr6szksaGsLiHDKyRd1rPJ6ev5ig", - mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop", - unicode_pw= True - ) + wallet = btcrpass.WalletCardano(*args, **kwargs) - @skipUnless(can_load_coincurve, "requires coincurve") - def test_bip39_unicode_mnemonic(self): - self.bip39_tester( - mpk= "xpub6C7cXo5w4HPs6X93zKdkRNDFyHedGHwQHvmMst7HYjeudySyF3eTsWktz6JVz4CkrzuLiEbieYP8dQaxsffJXjquD3FLmnqioHe8qZwcBF3", - mnemonic= u"あんまり おんがく いとこ ひくい こくはく あらゆる てあし げどく はしる げどく そぼろ はみがき" - ) + # Perform the tests in the current process + correct_pass = "btcr-test-password" + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) - @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(has_ripemd160, "requires that hashlib implements RIPEMD-160") - def test_bip39_address(self): - self.bip39_tester( - addresses= ["1AmugMgC6pBbJGYuYmuRrEpQVB9BBMvCCn"], - address_limit= 5, - mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" - ) + # Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded + wallet.opencl = False + pool = multiprocessing.Pool(1, init_worker, (wallet, tstr, False, False)) + password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, + ( ( tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2") ), + ( tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4") ) )) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) + self.assertRaises(StopIteration, password_found_iterator.next) + pool.close() + pool.join() - @skipUnless(can_load_coincurve, "requires coincurve") - def test_bip39_pp(self): - self.bip39_tester( - mpk= "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", - mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop", - force_purepython= True - ) + @unittest.skipUnless(can_load_staking_deposit(), "requires staking-deposit and py_ecc") + def ethvalidator_tester(self, *args, **kwargs): - @skipUnless(can_load_coincurve, "requires coincurve") - @skipUnless(can_load_sha3, "requires pysha3") - def test_ethereum_address(self): - self.bip39_tester( - wallet_type= "ethereum", - addresses= ["0x4daE22510CE2fE1BC81B97b31350Faf07c0A80D2"], - address_limit= 3, - mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" - ) + wallet = btcrpass.WalletEthereumValidator(*args, **kwargs) + # Perform the tests in the current process + correct_pass = "btcr-test-password" + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) -opencl_device_count = None -def has_any_opencl_devices(): - global opencl_device_count - if opencl_device_count is None: - try: - devs = btcrpass.get_opencl_devices() - except ImportError: - devs = () - opencl_device_count = len(devs) - return opencl_device_count > 0 + # Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded + wallet.opencl = False + pool = multiprocessing.Pool(1, init_worker, (wallet, tstr, False, False)) + password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, + ((tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")), + (tstr("btcr-wrong-password-3"), correct_pass, + tstr("btcr-wrong-password-4")))) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) + self.assertRaises(StopIteration, password_found_iterator.next) + pool.close() + pool.join() + + def WalletPyCryptoHDWallet_tester(self, correct_pass = "btcr-test-password", *args, **kwargs): + + wallet = btcrpass.WalletPyCryptoHDWallet(*args, **kwargs) + + # Perform the tests in the current process + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) + + # Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded + wallet.opencl = False + pool = multiprocessing.Pool(1, init_worker, (wallet, tstr, False, False)) + password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, + ( ( tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2") ), + ( tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4") ) )) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) + self.assertRaises(StopIteration, password_found_iterator.next) + pool.close() + pool.join() + + def WalletSLIP39_tester(self, correct_pass = "btcr-test-password", *args, **kwargs): + + wallet = btcrpass.WalletSLIP39(*args, **kwargs) + + # Perform the tests in the current process + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) + + # Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded + wallet.opencl = False + pool = multiprocessing.Pool(1, init_worker, (wallet, tstr, False, False)) + password_found_iterator = pool.imap(btcrpass.return_verified_password_or_false, + ( ( tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2") ), + ( tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4") ) )) + self.assertEqual(password_found_iterator.__next__(), (False, 2)) + self.assertEqual(password_found_iterator.__next__(), (correct_pass, 2)) + self.assertRaises(StopIteration, password_found_iterator.next) + pool.close() + pool.join() + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_skip_mnemonic_checksum(self): + invalid_mnemonic = " ".join(["abandon"] * 12) + btcrpass.args = argparse.Namespace(skip_mnemonic_checksum=False) + with self.assertRaises(SystemExit): + btcrpass.WalletBIP39( + mpk="xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + mnemonic=invalid_mnemonic, + lang="en", + ) + btcrpass.args = argparse.Namespace(skip_mnemonic_checksum=True) + wallet = btcrpass.WalletBIP39( + mpk="xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + mnemonic=invalid_mnemonic, + lang="en", + ) + self.assertEqual( + wallet._return_verified_password_or_false_cpu((tstr("wrong"),)), + (False, 1), + ) + btcrpass.args = argparse.Namespace() + + def cardano_tester_opencl(self, *args, **kwargs): + + wallet = btcrpass.WalletCardano(*args, **kwargs) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + # Perform the tests in the current process + correct_pass = "btcr-test-password" + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-3"), correct_pass, tstr("btcr-wrong-password-4"))), (correct_pass, 2)) + + def test_address_cardano_ledger(self): + self.cardano_tester( + addresses= ["addr1qy0efqgj8vv85jlltlpjqe2sz4yuw27zqfczpcw8lavzq9flnqg3r29wezptgt8jzya9j5cya9jg4xgy3p7x9tcrqdmqfu7thk"], + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + def test_address_cardano_trezor_12word(self): + self.cardano_tester( + addresses= ["addr1q90kk6lsmk3fdy54mqfr50hy025ymnmn5hhj8ztthcv3qlzh5aynphrad3d26hzxg7xzzf8hnmdpxwtwums4nmryj3jqk8kvak"], + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + + # def test_address_cardano_trezor_24word(self): + # self.cardano_tester( + # addresses= ["addr1qxy8p3rpnpnszuz3xjn7r220g0mls2s7y40u60856haqzvqugjuyvl52aplawmqtthwf98pusznhzy7m7v3vpy5tlydqpd86x9"], + # mnemonic= "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique" + # ) + + def test_address_ethereumvalidator(self): + self.ethvalidator_tester( + addresses= ["90c86e593885da004359fc6d1fe52e1b8210b0f46117c032d40a705cfeeda17bc0a5e8c85a4f46bd75c49100802976fd"], + mnemonic= "spatial evolve range inform burst screen session kind clap goat force sort", + address_limit=2 + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_multiversx(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="multiversx", + address_limit=1, + addresses= ["erd1t20rq7jqlspn5an5kw0vk75536x3m64ll0pcsx7g5v95daea6fhqqza54a"], + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_solana(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="solana", + address_limit=1, + addresses= ["8rYAgtkMvM1GmhLUTksAyEYyxC3ckwyGG7747FHFsn3y"], + mnemonic= "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_tron(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="tron", + address_limit=1, + addresses= ["TGvJrj5D8qdzhcppg9RoLdfbEjDYCne8xc"], + mnemonic= "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_cosmos(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="cosmos", + address_limit=1, + addresses= ["cosmos1djx4wh8zc9wdk5cwe3lawpmh0j4nsekej6mk9k"], + mnemonic= "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_tezos(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="tezos", + address_limit=1, + addresses= ["tz1WovCBe7yYsctWcqpw8pG5zfj6XpupYCh1"], + mnemonic= "cake return enhance slender swap butter code cram fashion warm uphold adapt swarm slight misery enhance almost ability artefact lava sugar regret example lake" + ) + + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_stellar(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="stellar", + address_limit=2, + addresses= ["GBPYX2ELQ6YTAF7DXER7RCQJR2HXXFX6HUZKWEZD3B6RKOLDSJF7UGXK"], + mnemonic= "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_avalanche(self): + self.WalletPyCryptoHDWallet_tester( + wallet_type="avalanche", + address_limit=1, + addresses= ["X-avax170r6a4nwudym6tx494nxgdatpep2gvpm40h4tg"], + mnemonic= "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch" + ) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_address_PyCryptoHDWallet_polkadotsubstrate(self): + self.WalletPyCryptoHDWallet_tester( + path = ["//hard/soft"], + correct_pass = "btcr-test-password", + wallet_type="polkadotsubstrate", + address_limit=1, + addresses= ["12uMBgecqfkHTYZE4GFRx847CwR7sfs2bTdPbPLpzeMDGFwC"], + mnemonic= "toilet assume drama keen dust warrior stick quote palace imitate music disease" + ) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_address_cardano_ledger_opencl(self): + self.cardano_tester_opencl( + addresses= ["addr1qy0efqgj8vv85jlltlpjqe2sz4yuw27zqfczpcw8lavzq9flnqg3r29wezptgt8jzya9j5cya9jg4xgy3p7x9tcrqdmqfu7thk"], + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_address_cardano_trezor_12word_opencl(self): + self.cardano_tester_opencl( + addresses= ["addr1q90kk6lsmk3fdy54mqfr50hy025ymnmn5hhj8ztthcv3qlzh5aynphrad3d26hzxg7xzzf8hnmdpxwtwums4nmryj3jqk8kvak"], + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + # @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + # def test_address_cardano_trezor_24word(self): + # self.cardano_tester_opencl( + # addresses= ["addr1qxy8p3rpnpnszuz3xjn7r220g0mls2s7y40u60856haqzvqugjuyvl52aplawmqtthwf98pusznhzy7m7v3vpy5tlydqpd86x9"], + # mnemonic= "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique" + # ) + + + @skipUnless(can_load_ShamirMnemonic, "requires Shamir-Mnemonic module") + def test_address_SLIP39_BTC(self): + self.WalletSLIP39_tester( + correct_pass = "btcr-test-password", + wallet_type="bip39", + address_limit=2, + addresses= ["bc1q76szkxz4cta5p5s66muskvads0nhwe5m5w07pq"], + slip39_shares = ["hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing", + "hearing echo academic agency deliver join grant laden index depart deadline starting duration loud crystal bulge gasoline injury tofu together"] + ) + + @skipUnless(can_load_ShamirMnemonic, "requires Shamir-Mnemonic module") + def test_address_SLIP39_ETH(self): + self.WalletSLIP39_tester( + correct_pass = "btcr-test-password", + wallet_type="ethereum", + address_limit=2, + addresses= ["0x0Ef61684B1E671dcBee4D51646cA6247487Ef91a"], + slip39_shares = ["hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing", + "hearing echo academic agency deliver join grant laden index depart deadline starting duration loud crystal bulge gasoline injury tofu together"] + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_bip39_mpk(self): + self.bip39_tester( + mpk= "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + @skipUnless(lambda: tstr == str, "Unicode mode only") + def test_bip39_unicode_password(self): + self.bip39_tester( + mpk= "xpub6CZe1G1A1CaaSepbekLMSk1sBRNA9kHZzEQCedudHAQHHB21FW9fYpQWXBevrLVQfL8JFQVFWEw3aACdr6szksaGsLiHDKyRd1rPJ6ev5ig", + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop", + unicode_pw= True + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_bip39_unicode_mnemonic(self): + self.bip39_tester( + mpk= "xpub6C7cXo5w4HPs6X93zKdkRNDFyHedGHwQHvmMst7HYjeudySyF3eTsWktz6JVz4CkrzuLiEbieYP8dQaxsffJXjquD3FLmnqioHe8qZwcBF3", + mnemonic= u"あんまり おんがく いとこ ひくい こくはく あらゆる てあし げどく はしる げどく そぼろ はみがき" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_bip39_pp(self): + self.bip39_tester( + mpk= "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop", + force_purepython= True + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_bip44(self): + self.bip39_tester( + addresses= ["1AmugMgC6pBbJGYuYmuRrEpQVB9BBMvCCn"], + address_limit= 5, + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_bip49(self): + self.bip39_tester( + addresses= ["34yrZYvhWEfVgZ8XFMuuwFeRpQ4m3u4EbY"], + address_limit= 5, + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_bip84(self): + self.bip39_tester( + addresses= ["bc1qj7najywjcjwd7ccn7kmeh3ckccwrslnrqrlnm7"], + address_limit= 5, + mnemonic= "certain come keen collect slab gauge photo inside mechanic deny leader drop" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_bip86(self): + self.bip39_tester( + addresses= ["bc1p98xclt0tfcrxf8ktq2s0hdhjsfcajfcy5ehwt0n7wrr5y7as3czq5gqetn"], + address_limit= 5, + mnemonic= "swing wedding strike accuse walk reduce immense blur rotate south myself memory" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_bip86_force_p2tr(self): + self.bip39_tester( + addresses= ["bc1psx9yanxmvuuj2mw00ye2f4uqj8arrq3jf0ftjztkdf737u332vgsaxpexm"], + address_limit= 5, + mnemonic= "swing wedding strike accuse walk reduce immense blur rotate south myself memory", + force_p2tr = True + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_bitcoin_mybitcoinwallet_single(self): + self.bip39_tester( + addresses= ["1NLcraWZhG3wFBYX2zwkKwYztL6yyhJG32"], + address_limit= 1, + mnemonic = "spatial stereo thrive reform shallow blouse minimum foster eagle game answer worth size stumble theme crater bounce stay extra duty man weather awesome search", + checksinglexpubaddress = True + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + @skipUnless(can_load_keccak, "requires PyCryptoDomedome") + def test_address_ethereum(self): + self.bip39_tester( + wallet_type= "ethereum", + addresses= ["0x4daE22510CE2fE1BC81B97b31350Faf07c0A80D2"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Zilliqa(self): + self.bip39_tester( + wallet_type= "zilliqa", + addresses= ["zil1dcsu2uz0yczmunyk90e8g9sr5400c892yeh8fp"], + address_limit= 1, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_BCH(self): + self.bip39_tester( + wallet_type= "bch", + addresses= ["bitcoincash:qqv8669jcauslc88ty5v0p7xj6p6gpmlgv04ejjq97"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Dash(self): + self.bip39_tester( + wallet_type= "dash", + addresses= ["XuTTeMZjUJuZGotrtTPRCmHCaxnX44a2aP"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Dogecoin(self): + self.bip39_tester( + wallet_type= "dogecoin", + addresses= ["DSTy3eptg18QWm6pCJGG4BvodSkj3KWvHx"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Vertcoin(self): + self.bip39_tester( + wallet_type= "vertcoin", + addresses= ["Vwodj33bXcT7K1uWbTqtk9UKymYSMeaXc3"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Litecoin(self): + self.bip39_tester( + wallet_type= "litecoin", + addresses= ["LdxLVMdt49CXcrnQRVJFRs8Yftu9dE8xxP"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Monacoin(self): + self.bip39_tester( + wallet_type= "monacoin", + addresses= ["MHLW7WdRKE1XBkLFS6oaTJE1nPCkD6acUd"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_DigiByte(self): + self.bip39_tester( + wallet_type= "digibyte", + addresses= ["DNGbPa9QMbLgeVspu9jb6EEnXjJASMvA5r"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Groestlecoin(self): + self.bip39_tester( + wallet_type= "groestlecoin", + addresses= ["FWzSMhK2TkotZodkApNxi4c6tvLUo7MBWk"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Ripple(self): + self.bip39_tester( + wallet_type= "Ripple", + addresses= ["rwv2s1wPjaCxmEFRm4j724yQ5Lh161mzwK"], + address_limit= 3, + mnemonic= "cable top mango offer mule air lounge refuse stove text cattle opera" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_stacks(self): + self.bip39_tester( + wallet_type= "Stacks", + addresses= ["SP2KJB4F9C91R3N5XSNQE0Z3G34DNJWQYTP3PBJTH"], + address_limit= 2, + mnemonic= "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Electrum2_segwit(self): + self.bip39_tester( + wallet_type= "Electrum2", + addresses= ["bc1q6n3u9aar3vgydfr6q23fzcfadh4zlp2ns2ljp6"], + address_limit= 3, + mnemonic= "quote voice evidence aspect warfare hire system black rate wing ask rug" + ) + + @skipUnless(can_load_coincurve, "requires coincurve") + def test_address_Electrum2_legacy(self): + self.bip39_tester( + wallet_type= "Electrum2", + addresses= ["157yth95rdqsp6F3qbb12ejTnRimkQ7d5h"], + address_limit= 3, + mnemonic= "water wait table horse smooth birth identify food favorite depend brother hand" + ) class Test08KeyDecryption(unittest.TestCase): - def key_tester(self, key_crc_base64, force_purepython = False, force_kdf_purepython = False, unicode_pw = False): + def key_tester(self, key_crc_base64, force_purepython = False, force_kdf_purepython = False, unicode_pw = False, correct_password = "btcr-test-password"): btcrpass.load_from_base64_key(key_crc_base64) if force_purepython: btcrpass.load_aes256_library(force_purepython=True) if force_kdf_purepython: btcrpass.load_pbkdf2_library(force_purepython=True) - correct_pw = tstr("btcr-test-password") if not unicode_pw else "btcr-тест-пароль" + correct_pw = tstr(correct_password) if not unicode_pw else "btcr-тест-пароль" self.assertEqual(btcrpass.return_verified_password_or_false( (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) self.assertEqual(btcrpass.return_verified_password_or_false( (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) - @skipUnless(can_load_armory, "requires Armory and ASCII mode") - def test_armory(self): - self.key_tester("YXI6r7mks1qvph4G+rRT7WlIptdr9qDqyFTfXNJ3ciuWJ12BgWX5Il+y28hLNr/u4Wl49hUi4JBeq6Jz9dVBX3vAJ6476FEAACAABAAAAGGwnwXRpPbBzC5lCOBVVWDu7mUJetBOBvzVAv0IbrboDXqA8A==") - - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bitcoincore(self): self.key_tester("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bitcoincore_unicode(self): self.key_tester("YmM6XAL2X19VfzlKJfc+7LIeNrB2KC8E9DWe1YhhOchPoClvwftbuqjXKkfdAAARmggo", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_multibit(self): self.key_tester("bWI6oikebfNQTLk75CfI5X3svX6AC7NFeGsgTNXZfA==") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_multibit_unicode(self): self.key_tester("bWI6YK6OX8bVP2Ar/j2dZBBQ+F0pEn8kZK6rlXiAWA==", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_multidoge(self): self.key_tester("bWI6IdK25nMhHI9n4zlb1cUtWBl7mL7gh7ZtxkYaDw==") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_multidoge_unicode(self): self.key_tester("bWI6ry78W+RkeTi2dVt2omZMfXRi46xDsIhr0jKN3g==", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_androidwallet(self): self.key_tester("bWI6Ii/ZEeDjUJKq704wzUxKudpvAralnrOQtXM4og==") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_androidwallet_unicode(self): self.key_tester("bWI6f1QdX7xXtC0zG7XK9pTGTifie5FUeAGhJ05esw==", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_androidknc(self): self.key_tester("bWI6n6ccPSkbrmxQpdfKNAOBFppQLGloPDHE2sOucQ====") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_androidknc_unicode(self): self.key_tester("bWI6TaEiZOBE+52jqe09jKcVa39KqvOpJxbpEtCVPQ==", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd(self): self.key_tester("bTU6LbH/+ROEa0cQ0inH7V3thcYVi5WL/4uGfU9/JQgsPZ6Y3zps") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd_unicode(self): self.key_tester("bTU6M7wXqwXQWo4o22eN50PNnsYVi5WL/4uGfU9/JQgsPZ42BGtS", unicode_pw=True) # - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd_v0_5_0(self): self.key_tester("bTU6Uh0pDwAKoBrKkMbf2ARxmyftdKB5dsqDUWTsD1fVrnsM2EYW") @skipUnless(can_load_protobuf, "requires protobuf") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bitcoinj(self): self.key_tester("Ymo6MacXiCd1+6/qtPc5rCaj6qIGJbu5tX2PXQXqF4Df/kFrjNGMDMHqrwBAAAAIAAEAZwdBow==") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(can_load_protobuf, "requires protobuf") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bitcoinj_unicode(self): self.key_tester("Ymo6hgWTejxVYfL/LLF4af8j2RfEsi5y16kTQhECWnn9iCt8AmGWPoPomQBAAAAIAAEAfNRA3A==", unicode_pw=True) @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bither(self): self.key_tester("YnQ6PocfHvWGVbCzlVb9cUtPDjosnuB7RoyspTEzZZAqURlCsLudQaQ4IkIW8YE=") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_bither_unicode(self): self.key_tester("YnQ6ENNU1KSJlzC8FMfAq/MHgWgaZkxpiByt/vLQ/UdP2NlCsLudQaQ4IjTbPcw=", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_msigna(self): self.key_tester("bXM6SWd6U+qTKOzQDfz8auBL1/tzu0kap7NMOqctt7U0nA8XOI6j6BCjxCsc7mU=") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_msigna_unicode(self): self.key_tester("bXM6i9OkMzrIJqWvpM+Dxq795jeFFxiB6DtBwuGmeEtfHLLOjMvoJRAWeSsf+Pg=", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum(self): self.key_tester("ZWw6kLJxTDF7LxneT7c5DblJ9k9WYwV6YUIUQO+IDiIXzMUZvsCT") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum_unicode(self): self.key_tester("ZWw6rLwP/stP422FgteriIgvq4LD90adedrAqz61gKuYDRrx3+Q+", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum2(self): self.key_tester("ZTI69B961mYKYFV7Bg1zRYZ8ZGw4cE+2D8NF3lp6d2XPe8qTdJUz") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_electrum2_unicode(self): self.key_tester("ZTI6k2tz83Lzs83hyQPRj2g90f7nVYHYM20qLv4NIVIzUNNqVWv8", unicode_pw=True) def test_electrum2_loosekey(self): self.key_tester("ZWs6FPx4P6wESVURM253BSUQvL8OMYotir0NptnEElninGsj4CuI") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_v0(self): self.key_tester("Yms69Z9y1J66ceYKkrXy11mHR+YDD8WrPJeTNaAnO7LO7YgAAAAAbnp7YQ==") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(lambda: tstr == str, "Unicode mode only") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_v0_unicode(self): self.key_tester("Yms68OsennSoypcGGUvhrhEBFCiIkAK2Qphnfdc3Ungk/SoAAAAAcr6jYQ==", unicode_pw=True) - @skipUnless(can_load_pycrypto, "requires PyCrypto") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") def test_blockchain_v2(self): self.key_tester("Yms6abF6aZYdu5sKpStKA4ihra6GEAeZTumFiIM0YQUkTjcQJwAAj8ekAQ==") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_blockchain_v3(self): + self.key_tester("Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q==") + def test_blockchain_secondpass(self): # extracted from blockchain-v0.0-wallet.aes.json which has a second password iter_count self.key_tester("YnM6ujsYxz3SE7fEEekfMuIC1oII7KY//j5FMObBn7HydqVyjnaeTCZDAaC4LbJcVkxaCgAAACsWXkw=") # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_blockchain_secondpass_unicode(self): self.key_tester("YnM6/e8Inpbesj+CYE0YvdXLewgN5UH9KFvliZrI43OmYnyHbCa71RBD57XO0CbuADDTCgAAACCVL/w=", unicode_pw=True) - @skipUnless(has_hashlib_pbkdf2, "requires Python 2.7.8+") def test_blockchain_secondpass_no_iter_count(self): # extracted from blockchain-unencrypted-wallet.aes.json which is missing a second password iter_count self.key_tester("YnM6ujsYxz3SE7fEEekfMuIC1oII7KY//j5FMObBn7HydqVyjnaeTCZDAaC4LbJcVkxaAAAAAE/24yM=") + @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_coinomi(self): + self.key_tester("Y246wmkdyRJJWG85XUTWYe9r9UUBkSrGN43WWUg5xXDelnEAGXs/lDcBMQBAAAAIAAEARsFrJw==") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_metamask_chrome(self): + self.key_tester("bXQ6Ny6zeXCgltvFkIWycZU3gR/Ng61aKDp3Ue35NUiihsFzGnSlG2yDJQGOWXoyS1TYfJK5uu2cxo9cDBoGwA0DVgCUdDI1") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_metamask_firefox(self): + self.key_tester("bXQ6bB5JP1EW0xwBmWZ9vI/iw9IRkorRs9rI6wJCNrd8KUw61ubkQxf9JF9jDv9kZIlxVVkKb7lIwnt7+519MLodzgA1vVjR") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_metamask_ronin(self): + self.key_tester("bXQ6NFrMHOjRpTWv8X6Ofs9xFr9aCuHXqiBcn2cs+JnIV/Rz3e8cXASN3jCOCATGEfJqwATWFKP3CoAzETPxhc1e3gDSNsw1") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_metamask_binancechainwallet(self): + self.key_tester("bXQ6wTs1FXlOeMHlFE5++vK+HM3ZxgIeRn6YChp9zI7NtEnfrSKY8uVFIJpSsGOexEHbxr5uqUAH5ETrZSWjSFadggBvIHJ/", correct_password="BTCR-test-passw0rd") + + @skipUnless(can_load_pycrypto, "requires PyCryptoDome") + def test_metamask_ios(self): + self.key_tester("bXQ6oyvzsghbruyPSrkcEv0B+p7CkKODpMTPX8rpE3RV3HJ5d3Y0ZDNVeWpCdGpWUTBnaXAwU3dnPT0AAAAAAAAAAAFwj2d+") + def test_bitcoincore_pp(self): self.key_tester("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_bitcoincore_unicode_pp(self): self.key_tester("YmM6XAL2X19VfzlKJfc+7LIeNrB2KC8E9DWe1YhhOchPoClvwftbuqjXKkfdAAARmggo", force_purepython=True, unicode_pw=True) def test_multibit_pp(self): self.key_tester("bWI6oikebfNQTLk75CfI5X3svX6AC7NFeGsgTNXZfA==", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_multibit_unicode_pp(self): self.key_tester("bWI6YK6OX8bVP2Ar/j2dZBBQ+F0pEn8kZK6rlXiAWA==", force_purepython=True, unicode_pw=True) def test_multidoge_pp(self): self.key_tester("bWI6IdK25nMhHI9n4zlb1cUtWBl7mL7gh7ZtxkYaDw==", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_multidoge_unicode_pp(self): self.key_tester("bWI6ry78W+RkeTi2dVt2omZMfXRi46xDsIhr0jKN3g==", force_purepython=True, unicode_pw=True) def test_androidwallet_pp(self): self.key_tester("bWI6Ii/ZEeDjUJKq704wzUxKudpvAralnrOQtXM4og==", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_androidwallet_unicode_pp(self): self.key_tester("bWI6f1QdX7xXtC0zG7XK9pTGTifie5FUeAGhJ05esw==", force_purepython=True, unicode_pw=True) def test_androidknc_pp(self): self.key_tester("bWI6n6ccPSkbrmxQpdfKNAOBFppQLGloPDHE2sOucQ==", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_androidknc_unicode_pp(self): self.key_tester("bWI6TaEiZOBE+52jqe09jKcVa39KqvOpJxbpEtCVPQ==", force_purepython=True, unicode_pw=True) @@ -1545,7 +2514,7 @@ def test_androidknc_unicode_pp(self): def test_multibithd_pp(self): self.key_tester("bTU6LbH/+ROEa0cQ0inH7V3thcYVi5WL/4uGfU9/JQgsPZ6Y3zps", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_multibithd_unicode_pp(self): self.key_tester("bTU6M7wXqwXQWo4o22eN50PNnsYVi5WL/4uGfU9/JQgsPZ42BGtS", force_purepython=True, unicode_pw=True) @@ -1559,7 +2528,7 @@ def test_multibithd_v0_5_0_pp(self): def test_bitcoinj_pp(self): self.key_tester("Ymo6MacXiCd1+6/qtPc5rCaj6qIGJbu5tX2PXQXqF4Df/kFrjNGMDMHqrwBAAAAIAAEAZwdBow==", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(can_load_protobuf, "requires protobuf") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_bitcoinj_unicode_pp(self): @@ -1569,7 +2538,7 @@ def test_bitcoinj_unicode_pp(self): def test_bither_pp(self): self.key_tester("YnQ6PocfHvWGVbCzlVb9cUtPDjosnuB7RoyspTEzZZAqURlCsLudQaQ4IkIW8YE=", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(can_load_scrypt, "requires a binary implementation of pylibscrypt") def test_bither_unicode_pp(self): self.key_tester("YnQ6ENNU1KSJlzC8FMfAq/MHgWgaZkxpiByt/vLQ/UdP2NlCsLudQaQ4IjTbPcw=", force_purepython=True, unicode_pw=True) @@ -1577,38 +2546,41 @@ def test_bither_unicode_pp(self): def test_msigna_pp(self): self.key_tester("bXM6SWd6U+qTKOzQDfz8auBL1/tzu0kap7NMOqctt7U0nA8XOI6j6BCjxCsc7mU=", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_msigna_unicode_pp(self): self.key_tester("bXM6i9OkMzrIJqWvpM+Dxq795jeFFxiB6DtBwuGmeEtfHLLOjMvoJRAWeSsf+Pg=", force_purepython=True, unicode_pw=True) def test_electrum_pp(self): self.key_tester("ZWw6kLJxTDF7LxneT7c5DblJ9k9WYwV6YUIUQO+IDiIXzMUZvsCT", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_electrum_unicode_pp(self): self.key_tester("ZWw6rLwP/stP422FgteriIgvq4LD90adedrAqz61gKuYDRrx3+Q+", force_purepython=True, unicode_pw=True) def test_electrum2_pp(self): self.key_tester("ZTI69B961mYKYFV7Bg1zRYZ8ZGw4cE+2D8NF3lp6d2XPe8qTdJUz", force_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_electrum2_unicode_pp(self): self.key_tester("ZTI6k2tz83Lzs83hyQPRj2g90f7nVYHYM20qLv4NIVIzUNNqVWv8", force_purepython=True, unicode_pw=True) def test_blockchain_v0_pp(self): self.key_tester("Yms69Z9y1J66ceYKkrXy11mHR+YDD8WrPJeTNaAnO7LO7YgAAAAAbnp7YQ==", force_purepython=True, force_kdf_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_blockchain_v0_unicode_pp(self): self.key_tester("Yms68OsennSoypcGGUvhrhEBFCiIkAK2Qphnfdc3Ungk/SoAAAAAcr6jYQ==", force_purepython=True, force_kdf_purepython=True, unicode_pw=True) def test_blockchain_v2_pp(self): self.key_tester("Yms6abF6aZYdu5sKpStKA4ihra6GEAeZTumFiIM0YQUkTjcQJwAAj8ekAQ==", force_purepython=True, force_kdf_purepython=True) + def test_blockchain_v3(self): + self.key_tester("Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q==", force_purepython=True, force_kdf_purepython=True) + def test_blockchain_secondpass_pp(self): # extracted from blockchain-v0.0-wallet.aes.json which has a second password iter_count self.key_tester("YnM6ujsYxz3SE7fEEekfMuIC1oII7KY//j5FMObBn7HydqVyjnaeTCZDAaC4LbJcVkxaCgAAACsWXkw=", force_kdf_purepython=True) # - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + @skipUnless(lambda: tstr == str, "Unicode mode only") def test_blockchain_secondpass_unicode_pp(self): self.key_tester("YnM6/e8Inpbesj+CYE0YvdXLewgN5UH9KFvliZrI43OmYnyHbCa71RBD57XO0CbuADDTCgAAACCVL/w=", force_kdf_purepython=True, unicode_pw=True) @@ -1620,176 +2592,225 @@ def init_opencl_kernel(self, devices, global_ws, int_rate = 200, **kwds): btcrpass.loaded_wallet.init_opencl_kernel(devices, global_ws, global_ws, int_rate, **kwds) except SystemExit as e: # this can happen with OpenCL CPUs whose max local-ws is 1, see #104 - if isinstance(e.code, basestring) and "local-ws" in e.code and "exceeds max" in e.code: + if isinstance(e.code, str) and "local-ws" in e.code and "exceeds max" in e.code: btcrpass.loaded_wallet.init_opencl_kernel(devices, global_ws, [None] * len(global_ws), int_rate, **kwds) else: raise @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_bitcoincore_cl(self): + def test_bitcoincore_OpenCL_JTR(self): + global opencl_devices_list btcrpass.load_from_base64_key("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR") dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): + test_succeeded = False + for dev in opencl_devices_list: if dev.name in dev_names_tested: continue + if "oclgrind" in dev.name.lower(): continue # Skip testing on oclgrind device if present... dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [4]) + try: + self.init_opencl_kernel([dev], [4]) + + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + dev.name.strip() + " found a false positive") + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + dev.name.strip() + " failed to find password") - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), - dev.name.strip() + " found a false positive") - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), - dev.name.strip() + " failed to find password") + test_succeeded = True - @skipUnless(lambda: tstr == unicode, "Unicode mode only") + except Exception as e: + print(dev.name, " PyOpenCL Exception: ", e) + + self.assertTrue(test_succeeded) #Check that at least one device successfully ran the test + + @skipUnless(lambda: tstr == str, "Unicode mode only") @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_bitcoincore_cl_unicode(self): + def test_bitcoincore_OpenCL_JTR_unicode(self): + global opencl_devices_list btcrpass.load_from_base64_key("YmM6XAL2X19VfzlKJfc+7LIeNrB2KC8E9DWe1YhhOchPoClvwftbuqjXKkfdAAARmggo") dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): + test_succeeded = False + for dev in opencl_devices_list: if dev.name in dev_names_tested: continue + if "oclgrind" in dev.name.lower(): continue # Skip testing on oclgrind device if present... dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [4]) + try: + self.init_opencl_kernel([dev], [4]) + + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + ["btcr-wrong-password-3", "btcr-тест-пароль", "btcr-wrong-password-4"]), ("btcr-тест-пароль", 2), + dev.name.strip() + " failed to find password") - self.assertEqual(btcrpass.return_verified_password_or_false( - ["btcr-wrong-password-3", "btcr-тест-пароль", "btcr-wrong-password-4"]), ("btcr-тест-пароль", 2), - dev.name.strip() + " failed to find password") + test_succeeded = True + + except Exception as e: + print(dev.name, " PyOpenCL Exception: ", e) + + self.assertTrue(test_succeeded) # Check that at least one device successfully ran the test @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - @skipUnless(lambda: sys.platform != "win32", "windows kills and restarts drivers which take too long") - def test_bitcoincore_cl_no_interrupts(self): + def test_bitcoincore_OpenCL_JTR_low_interrupt_rate(self): + global opencl_devices_list btcrpass.load_from_base64_key("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR") dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): + test_succeeded = False + for dev in opencl_devices_list: if dev.name in dev_names_tested: continue + if "oclgrind" in dev.name.lower(): continue # Skip testing on oclgrind device if present... dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [4], int_rate=1) + try: + self.init_opencl_kernel([dev], [4], int_rate=10) #Interrupt rate defaults to 200 + + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2)) + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2)) + + test_succeeded = True + + except Exception as e: + print(dev.name, " PyOpenCL Exception: ", e) + + self.assertTrue(test_succeeded) # Check that at least one device successfully ran the test - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2)) @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_bitcoincore_cl_sli(self): + def test_bitcoincore_OpenCL_JTR_sli(self): + global opencl_devices_list devices_by_name = dict() - for dev in btcrpass.get_opencl_devices(): + for dev in opencl_devices_list: + if "oclgrind" in dev.name.lower(): continue # Skip testing on oclgrind device if present... if dev.name in devices_by_name: break else: devices_by_name[dev.name] = dev else: self.skipTest("requires two identical OpenCL devices") + test_succeeded = False btcrpass.load_from_base64_key("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR") - self.init_opencl_kernel([devices_by_name[dev.name], dev], [2, 2]) + try: + self.init_opencl_kernel([devices_by_name[dev.name], dev], [2, 2]) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"), tstr("btcr-wrong-password-3"), tstr("btcr-wrong-password-4")]), (False, 4)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-5"), tstr("btcr-test-password"), tstr("btcr-wrong-password-6")]), (tstr("btcr-test-password"), 2)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-5"), tstr("btcr-wrong-password-6"), tstr("btcr-test-password")]), (tstr("btcr-test-password"), 3)) + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"), tstr("btcr-wrong-password-3"), tstr("btcr-wrong-password-4")]), (False, 4)) + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-5"), tstr("btcr-test-password"), tstr("btcr-wrong-password-6")]), (tstr("btcr-test-password"), 2)) + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_gpu(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-5"), tstr("btcr-wrong-password-6"), tstr("btcr-test-password")]), (tstr("btcr-test-password"), 3)) - @skipUnless(can_load_armory, "requires Armory and ASCII mode") - @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_armory_cl(self): - btcrpass.load_from_base64_key("YXI6r7mks1qvph4G+rRT7WlIptdr9qDqyFTfXNJ3ciuWJ12BgWX5Il+y28hLNr/u4Wl49hUi4JBeq6Jz9dVBX3vAJ6476FEAACAABAAAAGGwnwXRpPbBzC5lCOBVVWDu7mUJetBOBvzVAv0IbrboDXqA8A==") + test_succeeded = True - dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): - if dev.name in dev_names_tested: continue - dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [4]) + except Exception as e: + print(dev.name, " PyOpenCL Exception: ", e) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), - dev.name.strip() + " found a false positive") - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), - dev.name.strip() + " failed to find password") + self.assertTrue(test_succeeded) # Check that at least one device successfully ran the test - @skipUnless(can_load_armory, "requires Armory and ASCII mode") @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_armory_cl_mem_factor(self): - btcrpass.load_from_base64_key("YXI6r7mks1qvph4G+rRT7WlIptdr9qDqyFTfXNJ3ciuWJ12BgWX5Il+y28hLNr/u4Wl49hUi4JBeq6Jz9dVBX3vAJ6476FEAACAABAAAAGGwnwXRpPbBzC5lCOBVVWDu7mUJetBOBvzVAv0IbrboDXqA8A==") + def test_bitcoincore_OpenCL_Brute(self): + btcrpass.load_from_base64_key("YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR") - dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): - if dev.name in dev_names_tested: continue - dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [8], save_every=3) + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), - dev.name.strip() + " found a false positive") - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), - dev.name.strip() + " failed to find password") + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) - @skipUnless(can_load_armory, "requires Armory and ASCII mode") - @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - @skipUnless(lambda: sys.platform != "win32", "windows kills and restarts drivers which take too long") - def test_armory_cl_no_interrupts(self): - btcrpass.load_from_base64_key("YXI6r7mks1qvph4G+rRT7WlIptdr9qDqyFTfXNJ3ciuWJ12BgWX5Il+y28hLNr/u4Wl49hUi4JBeq6Jz9dVBX3vAJ6476FEAACAABAAAAGGwnwXRpPbBzC5lCOBVVWDu7mUJetBOBvzVAv0IbrboDXqA8A==") + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletBitcoinCore._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") - dev_names_tested = set() - for dev in btcrpass.get_opencl_devices(): - if dev.name in dev_names_tested: continue - dev_names_tested.add(dev.name) - self.init_opencl_kernel([dev], [4], int_rate=1) + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_blockchain_main_OpenCL_Brute(self): + btcrpass.load_from_base64_key("Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q==") + + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2)) + self.assertEqual(btcrpass.WalletBlockchain._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletBlockchain._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") - @skipUnless(can_load_armory, "requires Armory and ASCII mode") @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") - def test_armory_cl_sli(self): - devices_by_name = dict() - for dev in btcrpass.get_opencl_devices(): - if dev.name in devices_by_name: break - else: devices_by_name[dev.name] = dev - else: - self.skipTest("requires two identical OpenCL devices") + def test_dogechain_info_OpenCL_Brute(self): + btcrpass.load_from_base64_key("ZGM6jJzIUd6i9DMEgCFG9JQ1/z4xSamItXAiQnV4AeJ0BwcZznn+169Eb84PFQ3QQ2JGiBMAAGL+4VE=") - btcrpass.load_from_base64_key("YXI6r7mks1qvph4G+rRT7WlIptdr9qDqyFTfXNJ3ciuWJ12BgWX5Il+y28hLNr/u4Wl49hUi4JBeq6Jz9dVBX3vAJ6476FEAACAABAAAAGGwnwXRpPbBzC5lCOBVVWDu7mUJetBOBvzVAv0IbrboDXqA8A==") - self.init_opencl_kernel([devices_by_name[dev.name], dev], [4, 4]) + btcrecover.opencl_helpers.auto_select_opencl_platform(btcrpass.loaded_wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(btcrpass.loaded_wallet) + + self.assertEqual(btcrpass.WalletDogechain._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2")]), (False, 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " found a false positive") + self.assertEqual(btcrpass.WalletDogechain._return_verified_password_or_false_opencl(btcrpass.loaded_wallet, + [tstr("btcr-wrong-password-3"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4")]), (tstr("btcr-test-password"), 2), + "Platform:" + str(btcrpass.loaded_wallet.opencl_platform) + " failed to find password") - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"), tstr("btcr-wrong-password-3"), tstr("btcr-wrong-password-4"), - tstr("btcr-wrong-password-5"), tstr("btcr-wrong-password-6"), tstr("btcr-wrong-password-7"), tstr("btcr-wrong-password-8")]), (False, 8)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"), tstr("btcr-test-password"), tstr("btcr-wrong-password-4"), - tstr("btcr-wrong-password-5"), tstr("btcr-wrong-password-6"), tstr("btcr-wrong-password-7"), tstr("btcr-wrong-password-8")]), (tstr("btcr-test-password"), 3)) - self.assertEqual(btcrpass.return_verified_password_or_false( - [tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"), tstr("btcr-wrong-password-3"), tstr("btcr-wrong-password-4"), - tstr("btcr-wrong-password-5"), tstr("btcr-wrong-password-6"), tstr("btcr-wrong-password-7"), tstr("btcr-test-password")]), (tstr("btcr-test-password"), 8)) def test_invalid_crc(self): - with self.assertRaises(SystemExit) as cm: - self.key_tester("aWI6oikebfNQTLk75CfI5X3svX6AC7NFeGsgTNXZfA==") - self.assertIn("encrypted key data is corrupted (failed CRC check)", cm.exception.code) + with self.assertRaises(SystemExit) as cm: + self.key_tester("aWI6oikebfNQTLk75CfI5X3svX6AC7NFeGsgTNXZfA==") + self.assertIn("encrypted key data is corrupted (failed CRC check)", cm.exception.code) class GPUTests(unittest.TestSuite) : def __init__(self): + import os + os.environ['PYOPENCL_COMPILER_OUTPUT'] = '1' super(GPUTests, self).__init__() self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test08KeyDecryption." + method_name for method_name in ( - "test_bitcoincore_cl", - "test_bitcoincore_cl_unicode", - "test_bitcoincore_cl_no_interrupts", - "test_bitcoincore_cl_sli", - "test_armory_cl", - "test_armory_cl_mem_factor", - "test_armory_cl_no_interrupts", - "test_armory_cl_sli")), + "test_bitcoincore_OpenCL_JTR", + "test_bitcoincore_OpenCL_JTR_unicode", + "test_bitcoincore_OpenCL_JTR_low_interrupt_rate", + "test_bitcoincore_OpenCL_JTR_sli")), + module=sys.modules[__name__] + )) + +class OpenCL_Tests(unittest.TestSuite) : + def __init__(self): + import os + os.environ['PYOPENCL_COMPILER_OUTPUT'] = '1' + super(OpenCL_Tests, self).__init__() + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test08KeyDecryption." + method_name + for method_name in ( + "test_bitcoincore_OpenCL_Brute", + "test_blockchain_main_OpenCL_Brute")), + module=sys.modules[__name__] + )) + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test07WalletDecryption." + method_name + for method_name in ( + "test_blockchain_second_OpenCL_Brute", + "test_electrum28_OpenCL_Brute", + "test_multibit_OpenCL_Brute")), + module=sys.modules[__name__] + )) + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test08BIP39Passwords." + method_name + for method_name in ( + "test_bip39_mpk_opencl_brute", + "test_Electrum2_mpk_opencl_brute")), + module=sys.modules[__name__] + )) + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test10YoroiWalletDecryption." + method_name + for method_name in ( + ["test_yoroi_opencl_brute"])), + module=sys.modules[__name__] + )) + + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("Test11BIP38WalletDecryption." + method_name + for method_name in ( + ["test_bip38_bitcoin_opencl_brute"])), module=sys.modules[__name__] )) + class Test09EndToEnd(unittest.TestCase): @classmethod @@ -1810,15 +2831,16 @@ def test_end_to_end(self): tokenlist = StringIO(self.E2E_TOKENLIST), exclude_passwordlist = StringIO(self.E2E_EXCLUDELIST), data_extract = self.E2E_DATA_EXTRACT, - autosave = autosave_file) + autosave = autosave_file, + disable_security_warning_param = True) self.assertEqual("btcr-test-password", btcrpass.main()[0]) for process in multiprocessing.active_children(): process.join() # wait for any remaining child processes to exit cleanly # Verify the exact password number where it was found to ensure password ordering hasn't changed autosave_file.seek(SAVESLOT_SIZE) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 103762) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 103762) # Repeat the test above using the same autosave file, starting off just before the password was found def test_restore(self): @@ -1827,8 +2849,8 @@ def test_restore(self): # Verify the password number where the search started autosave_file = self.autosave_file autosave_file.seek(0) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 103762) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 103762) # Repeat the first test with a new autosave file, using --skip to start just after the password is located def test_skip(self): @@ -1837,20 +2859,380 @@ def test_skip(self): tokenlist = StringIO(self.E2E_TOKENLIST), exclude_passwordlist = StringIO(self.E2E_EXCLUDELIST), data_extract = self.E2E_DATA_EXTRACT, - autosave = autosave_file) - self.assertIn("Password search exhausted", btcrpass.main()[1]) + autosave = autosave_file, + disable_security_warning_param = True) + self.assertIn(btcrpass.searchfailedtext, btcrpass.main()[1]) for process in multiprocessing.active_children(): process.join() # wait for any remaining child processes to exit cleanly # Verify the password number where the search started autosave_file.seek(0) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 103763) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 103763) # Verify the total count of passwords autosave_file.seek(SAVESLOT_SIZE) - savestate = cPickle.load(autosave_file) - self.assertEqual(savestate.get(b"skip"), 139652) + savestate = pickle.load(autosave_file) + self.assertEqual(savestate.get("skip"), 139652) + +class Test10YoroiWalletDecryption(unittest.TestCase): + + test_master_password = b'A997F83D70BF83B32F8AC936AC32067653EE899979CCFDA67DFCBD535948C42A77DC' \ + b'9E719BF4ECE7DEB18BA3CD86F53C5EC75DE2126346A791250EC09E570E8241EE4F84' \ + b'0902CDFCBABC605ABFF30250BFF4903D0090AD1C645CEE4CDA53EA30BF419F4ECEA7' \ + b'909306EAE4B671FA7EEE3C2F65BE1235DEA4433F20B97F7BB8933521C657C61BBE6C' \ + b'031A7F1FEEF48C6978090ED009DD578A5382770A' + + def test_yoroi_cpu(self): + + wallet = btcrpass.WalletYoroi(self.test_master_password) + + correct_pw = tstr("btcr-test-password") + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_yoroi_opencl_brute(self): + wallet = btcrpass.WalletYoroi(self.test_master_password) + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + correct_pw = tstr("btcr-test-password") + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + +class Test11BIP38WalletDecryption(unittest.TestCase): + + def bip38_tester_cpu(self, bip38_encrypted_key, bip38_network = 'bitcoin'): + wallet = btcrpass.WalletBIP38(bip38_encrypted_key, bip38_network = bip38_network) + + correct_pw = tstr("btcr-test-password") + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + + def bip38_tester_opencl(self, bip38_encrypted_key, bip38_network = 'bitcoin'): + wallet = btcrpass.WalletBIP38(bip38_encrypted_key, bip38_network = bip38_network) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + correct_pw = tstr("btcr-test-password") + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + + @skipUnless(can_load_ecdsa, "requires ecdsa") + def test_bip38_bitcoin_cpu(self): + self.bip38_tester_cpu('6PnM7h9sBC9EMZxLVsKzpafvBN8zjKp8MZj6h9mfvYEQRMkKBTPTyWZHHx') + + @skipUnless(can_load_ecdsa, "requires ecdsa") + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_bip38_bitcoin_opencl_brute(self): + self.bip38_tester_opencl('6PnM7h9sBC9EMZxLVsKzpafvBN8zjKp8MZj6h9mfvYEQRMkKBTPTyWZHHx') + + @skipUnless(can_load_ecdsa, "requires ecdsa") + def test_bip38_litecoin_cpu(self): + self.bip38_tester_cpu('6PfVHSTbgRNDaSwddBNgx2vMhMuNdiwRWjFgMGcJPb6J2pCG32SuL3vo6q', + bip38_network='litecoin') + + @skipUnless(can_load_ecdsa, "requires ecdsa") + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_bip38_litecoin_opencl_brute(self): + self.bip38_tester_opencl('6PfVHSTbgRNDaSwddBNgx2vMhMuNdiwRWjFgMGcJPb6J2pCG32SuL3vo6q', + bip38_network='litecoin') + + @skipUnless(can_load_ecdsa, "requires ecdsa") + def test_bip38_dash_cpu(self): + self.bip38_tester_cpu('6PnZC9Snn1DHyvfEq9UKUmZwonqpfaWav6vRiSVNXXLUEDAuikZTxBUTEA', + bip38_network='dash') + + @skipUnless(can_load_ecdsa, "requires ecdsa") + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_bip38_dash_opencl_brute(self): + self.bip38_tester_opencl('6PnZC9Snn1DHyvfEq9UKUmZwonqpfaWav6vRiSVNXXLUEDAuikZTxBUTEA', + bip38_network='dash') + +class Test12BrainwalletDecryption(unittest.TestCase): + + def brainwallet_tester_cpu(self, addresses = None, check_compressed = True, + check_uncompressed = True, force_check_p2sh = False, + password = None, warpwallet = False, salt = None, + crypto='bitcoin'): + + wallet = btcrpass.WalletBrainwallet(addresses = [addresses], + check_compressed = check_compressed, + check_uncompressed = check_uncompressed, + force_check_p2sh = force_check_p2sh, + isWarpwallet=warpwallet, + salt = salt, + crypto=crypto) + if password: + correct_pw = password + else: + correct_pw = tstr("btcr-test-password") + + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + + def brainwallet_tester_opencl(self, addresses = None, check_compressed = True, + check_uncompressed = True, force_check_p2sh = False, + password = None, warpwallet = False, salt = None, + crypto='bitcoin'): + + wallet = btcrpass.WalletBrainwallet(addresses = [addresses], + check_compressed = check_compressed, + check_uncompressed = check_uncompressed, + force_check_p2sh = force_check_p2sh, + isWarpwallet=warpwallet, + salt=salt, + crypto=crypto) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + if password: + correct_pw = password + else: + correct_pw = tstr("btcr-test-password") + + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-1"), tstr("btcr-wrong-password-2"))), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (tstr("btcr-wrong-password-3"), correct_pw, tstr("btcr-wrong-password-4"))), (correct_pw, 2)) + + def test_brainwallet_bitcoin_p2pkh_compressed_cpu(self): + self.brainwallet_tester_cpu(addresses = "1BBRWFHjFhEQc1iS6WTQCtPu2GtZvrRcwy", + password="btcr-test-password:p2pkh", + check_uncompressed=False) + + def test_brainwallet_bitcoin_p2pkh_uncompressed_cpu(self): + self.brainwallet_tester_cpu(addresses="1MHoPPuGJyunUB5LZQF5dXTrLboEdxTmUm", + password="btcr-test-password:p2pkh", + check_compressed=False) + + def test_brainwallet_bitcoin_p2sh_compressed_cpu(self): + self.brainwallet_tester_cpu(addresses = "3C4dEdngg4wnmwDYSwiDLCweYawMGg8dVN", + password="btcr-test-password:p2wpkh-p2sh", + check_uncompressed=False) + + def test_brainwallet_bitcoin_p2wpkh_compressed_cpu(self): + self.brainwallet_tester_cpu(addresses="bc1qth4w90jmh0a6ug6pwsuyuk045fmtwzreg03gvj", + password="btcr-test-password:p2wpkh", + check_uncompressed=False) + + def test_brainwallet_litecoin_p2pkh_uncompressed_cpu(self): + self.brainwallet_tester_cpu(addresses = "LfWkecD6Pe9qiymVjYENuYXcYpAWjU3mXw", + password="btcr-test-password:p2pkh", + check_compressed=False) + + def test_brainwallet_dash_p2pkh_uncompressed_cpu(self): + self.brainwallet_tester_cpu(addresses = "XvyeDeZAGh8Nd7fvRHZJV49eAwNvfCubvB", + password="btcr-test-password:p2pkh", + check_compressed=False) + + def test_brainwallet_dash_p2pkh_compressed_cpu(self): + self.brainwallet_tester_cpu(addresses = "XksGLVwdDQSzkxK1xPmd4R5grcUFyB3ouY", + password="btcr-test-password:p2pkh", + check_uncompressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_bitcoin_p2pkh_compressed_opencl(self): + self.brainwallet_tester_opencl(addresses = "1BBRWFHjFhEQc1iS6WTQCtPu2GtZvrRcwy", + password="btcr-test-password:p2pkh", + check_uncompressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_bitcoin_p2pkh_uncompressed_opencl(self): + self.brainwallet_tester_opencl(addresses="1MHoPPuGJyunUB5LZQF5dXTrLboEdxTmUm", + password="btcr-test-password:p2pkh", + check_compressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_bitcoin_p2sh_compressed_opencl(self): + self.brainwallet_tester_opencl(addresses = "3C4dEdngg4wnmwDYSwiDLCweYawMGg8dVN", + password="btcr-test-password:p2wpkh-p2sh", + check_uncompressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_bitcoin_p2wpkh_compressed_opencl(self): + self.brainwallet_tester_opencl(addresses="bc1qth4w90jmh0a6ug6pwsuyuk045fmtwzreg03gvj", + password="btcr-test-password:p2wpkh", + check_uncompressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_litecoin_p2pkh_uncompressed_opencl(self): + self.brainwallet_tester_opencl(addresses = "LfWkecD6Pe9qiymVjYENuYXcYpAWjU3mXw", + password="btcr-test-password:p2pkh", + check_compressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_dash_p2pkh_uncompressed_opencl(self): + self.brainwallet_tester_opencl(addresses = "XvyeDeZAGh8Nd7fvRHZJV49eAwNvfCubvB", + password="btcr-test-password:p2pkh", + check_compressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_dash_p2pkh_compressed_opencl(self): + self.brainwallet_tester_opencl(addresses = "XksGLVwdDQSzkxK1xPmd4R5grcUFyB3ouY", + password="btcr-test-password:p2pkh", + check_uncompressed=False) + + def test_brainwallet_warpwallet_bitcoin_cpu(self): + self.brainwallet_tester_cpu(addresses = "1FThrDFjhSf8s1Aw2ed5U2sTrMz7HicZun", + password="btcr-test-password", + warpwallet = True, + salt="btcr-test-password", + check_compressed=False) + + def test_brainwallet_warpwallet_litecoin_cpu(self): + self.brainwallet_tester_cpu(addresses = "LeBzGzZFxRUzzRAtm8EB2Dw74jRfQqUZeq", + password="btcr-test-password", + warpwallet = True, + salt="btcr-test-password", + crypto = "litecoin", + check_compressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_warpwallet_bitcoin_opencl(self): + self.brainwallet_tester_opencl(addresses = "1FThrDFjhSf8s1Aw2ed5U2sTrMz7HicZun", + password="btcr-test-password", + warpwallet = True, + salt="btcr-test-password", + check_compressed=False) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_brainwallet_warpwallet_litecoin_opencl(self): + self.brainwallet_tester_opencl(addresses = "LeBzGzZFxRUzzRAtm8EB2Dw74jRfQqUZeq", + password="btcr-test-password", + warpwallet = True, + salt="btcr-test-password", + crypto = "litecoin", + check_compressed=False) + +# Raw Private Key Wallets +class Test13RawPrivateKeyRecovery(unittest.TestCase): + def test_rawprivatekey_Eth(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['0xB9644424F9E639D1D0F27C4897e696CC324948BB'], + check_compressed=True, + check_uncompressed=True, + force_check_p2sh=False, + crypto='ethereum') + + correct_pw = tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d33") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_legacy_Hex_Compressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['1KoHUH6vf9MGRvooN7bHqrWghDqKc566tB'], + check_compressed=True, + check_uncompressed=True, + force_check_p2sh=False, + crypto='bitcoin') + + correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_legacy_Hex_Uncompressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['1N8pQZkmrKjzSwuYFzThiMr8Ceg2mX4tAo'], + check_compressed=True, + check_uncompressed=True, + force_check_p2sh=False, + crypto='bitcoin') + + correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_p2sh_Hex_Compressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['3AZyE1Dobb71DWjvYtSNqwNELPGQhdjqp4'], + check_compressed=True, + check_uncompressed=False, + force_check_p2sh=True, + crypto='bitcoin') + + correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_nativesegwit_Hex_Compressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['bc1qecejtf3csl8gmjxvjmh0mwtqah8z7eetrcay67'], + check_compressed=True, + check_uncompressed=True, + force_check_p2sh=False, + crypto='bitcoin') + + correct_pw = tstr("1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_legacy_WIF_Uncompressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['1EDrqbJMVwjQ2K5avN3627NcAXyWbkpGBL'], + check_compressed=False, + check_uncompressed=True, + force_check_p2sh=False, + crypto='bitcoin') + + correct_pw = tstr("5JYsdUthE1KzGAUXwfomeocw6vwzoTNXzcbJq9e7LcAyt1Svoo8") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + def test_rawprivatekey_Btc_legacy_WIF_Compressed(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['1NMnWwgsaLaV97m3Z3PAkCvAyJqdVKHnEE'], + check_compressed=True, + check_uncompressed=False, + force_check_p2sh=False, + crypto='bitcoin') + + correct_pw = tstr("KzTgU27AYQSojvpXaJHS5rmboB7k83Q3oCDGAeoD3Rz2BbLFoP19") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d34"), tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d35"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d36"), correct_pw, tstr("5db77aa7aea5ea7d6b4c64dab219972cf4763d4937d3e6e17f580436dcb10d37"))), (correct_pw, 2)) + + @skipUnless(can_load_ecdsa, "requires ECDSA") + def test_rawprivatekey_Btc_BIP38(self): + wallet = btcrpass.WalletRawPrivateKey(addresses=['141HH8P17qa2tAiYsynueMKeKJtEMNDYaa'], + check_compressed=True, + check_uncompressed=False, + force_check_p2sh=False, + crypto='bitcoin', + correct_wallet_password='btcr-test-password') + + correct_pw = tstr("6PnRsXuVMAnEUHxixjLuLxsUosU7phXLZWTpSYA9Y7Ev482cAGwYP6CTop") + + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("6PnRsXuVMAnEUHxixjLuLxsUosU7phXLZWTpSYA9Y7Ev482cAGwYP6CToo"), tstr("6PnRsXuVMAnEUHxixjLuLxsUosU7phXLZWTpSYA9Y7Ev482cAGwYP6CToq"))), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (tstr("6PnRsXuVMAnEUHxixjLuLxsUosU7phXLZWTpSYA9Y7Ev482cAGwYP6CToo"), correct_pw, tstr("6PnRsXuVMAnEUHxixjLuLxsUosU7phXLZWTpSYA9Y7Ev482cAGwYP6CToq"))), ("L1H4Ht7spfgX87kSCa3qGU2HYLHsHqymLpkcv5oAA1xhLmBLNTm9", 1)) # QuickTests: all of Test01Basics, Test02Anchors, Test03WildCards, and Test04Typos, @@ -1929,7 +3311,7 @@ def __init__(self): self.addTests(tl.loadTestsFromTestCase(Test08BIP39Passwords)) -if __name__ == b'__main__': +if __name__ == '__main__': import argparse diff --git a/btcrecover/test/test_seeds.py b/btcrecover/test/test_seeds.py index 546a73fe8..5910babd4 100644 --- a/btcrecover/test/test_seeds.py +++ b/btcrecover/test/test_seeds.py @@ -1,518 +1,2207 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- - -# test_seeds.py -- unit tests for seedrecover.py -# Copyright (C) 2014-2017 Christopher Gurnee -# -# This file is part of btcrecover. -# -# btcrecover is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version -# 2 of the License, or (at your option) any later version. -# -# btcrecover is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ - -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! - -# (all optional futures for 2.7 except unicode_literals) -from __future__ import print_function, absolute_import, division - -import warnings, unittest, os, tempfile, shutil, filecmp, sys, hashlib, random, mmap, pickle -if __name__ == b'__main__': - sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..")) -from btcrecover import btcrseed -from btcrecover.addressset import AddressSet - -wallet_dir = os.path.join(os.path.dirname(__file__), "test-wallets") - - -def setUpModule(): - global orig_warnings - orig_warnings = warnings.catch_warnings() - orig_warnings.__enter__() # save the current warnings settings (it's a context manager) - # Convert warnings to errors: - warnings.simplefilter("error") - -def tearDownModule(): - orig_warnings.__exit__(None, None, None) # restore the original warnings settings - - -class TestRecoveryFromWallet(unittest.TestCase): - - @classmethod - def setUpClass(cls): - btcrseed.register_autodetecting_wallets() - - # Checks a test wallet against the known mnemonic, and ensures - # that the library doesn't make any changes to the wallet file - def wallet_tester(self, wallet_basename, correct_mnemonic, **kwds): - assert os.path.basename(wallet_basename) == wallet_basename - wallet_filename = os.path.join(wallet_dir, wallet_basename) - - temp_dir = tempfile.mkdtemp("-test-btcr") - try: - temp_wallet_filename = os.path.join(temp_dir, wallet_basename) - shutil.copyfile(wallet_filename, temp_wallet_filename) - - wallet = btcrseed.btcrpass.load_wallet(temp_wallet_filename) - - # Convert the mnemonic string into a mnemonic_ids_guess - wallet.config_mnemonic(correct_mnemonic, **kwds) - correct_mnemonic = btcrseed.mnemonic_ids_guess - - # Creates wrong mnemonic id guesses - wrong_mnemonic_iter = wallet.performance_iterator() - - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), wrong_mnemonic_iter.next())), (False, 2)) - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), correct_mnemonic, wrong_mnemonic_iter.next())), (correct_mnemonic, 2)) - - del wallet - self.assertTrue(filecmp.cmp(wallet_filename, temp_wallet_filename, False)) # False == always compare file contents - finally: - shutil.rmtree(temp_dir) - - def test_electrum1(self): - self.wallet_tester("electrum-wallet", "straight subject wild ask clean possible age hurt squeeze cost stuck softly") - - def test_electrum2(self): - self.wallet_tester("electrum2-wallet", "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13) - - def test_electrum27(self): - self.wallet_tester("electrum27-wallet", "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", - expected_len=12) - - def test_electrum2_upgradedfrom_electrum1(self): - self.wallet_tester("electrum1-upgradedto-electrum2-wallet", "straight subject wild ask clean possible age hurt squeeze cost stuck softly") - - def test_electrum27_upgradedfrom_electrum1(self): - self.wallet_tester("electrum1-upgradedto-electrum27-wallet", "straight subject wild ask clean possible age hurt squeeze cost stuck softly") - - -class TestRecoveryFromMPK(unittest.TestCase): - - def mpk_tester(self, wallet_type, the_mpk, correct_mnemonic, **kwds): - - wallet = wallet_type.create_from_params(mpk=the_mpk) - - # Convert the mnemonic string into a mnemonic_ids_guess - wallet.config_mnemonic(correct_mnemonic, **kwds) - correct_mnemonic = btcrseed.mnemonic_ids_guess - - # Creates wrong mnemonic id guesses - wrong_mnemonic_iter = wallet.performance_iterator() - - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), wrong_mnemonic_iter.next())), (False, 2)) - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), correct_mnemonic, wrong_mnemonic_iter.next())), (correct_mnemonic, 2)) - - def test_electrum1(self): - self.mpk_tester(btcrseed.WalletElectrum1, - "c79b02697b32d9af63f7d2bd882f4c8198d04f0e4dfc5c232ca0c18a87ccc64ae8829404fdc48eec7111b99bda72a7196f9eb8eb42e92514a758f5122b6b5fea", - "straight subject wild ask clean possible age hurt squeeze cost stuck softly") - - def test_electrum2(self): - self.mpk_tester(btcrseed.WalletElectrum2, - "xpub661MyMwAqRbcGsUXkGBkytQkYZ6M16bFWwTocQDdPSm6eJ1wUsxG5qty1kTCUq7EztwMscUstHVo1XCJMxWyLn4PP1asLjt4gPt3HkA81qe", - "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13) - - def test_electrum27(self): - self.mpk_tester(btcrseed.WalletElectrum2, - "xpub661MyMwAqRbcGt6qtQ19Ttwvo5Dbf2cQdA2GMf9Xkjth8NqYXXordg3gLK1npATRm9Fr7d7fA5ziCwqEVMmzeRezofp8CEaru8pJ57zV8hN", - "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", - expected_len=12) - - def test_electrum2_ja(self): - self.mpk_tester(btcrseed.WalletElectrum2, - "xpub661MyMwAqRbcFAyy6MaWCK5uGHhgvMZNaFbKy1TbSrcEm8oCgD3N2AfzPC8ndmdvcQbY8EbU414X4xNrs9dcNgcntShiBFJYJ6HJy7zKnQV", - u"すんぽう うけつけ ぬいくぎ きどう ごはん たかね いてざ よしゅう なにもの われる たんき さとる あじわう", - expected_len=13) - - TEST_ELECTRUM2_PASS_XPUB = "xpub661MyMwAqRbcG4s8buUEpDeeBMZeXxnroY3i9jZJNQuDrWQaCyR5Mvk9pmRK5q5WrEKTwSuYwBiSjcp3ZkM2ujhngFQXxvrTyv2uFCryyii" - def test_electrum2_pass(self): - self.mpk_tester(btcrseed.WalletElectrum2, - self.TEST_ELECTRUM2_PASS_XPUB, - "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13, passphrase=u"btcr test password 测试密码") - - def test_electrum2_pass_normalize(self): - p = u" btcr TEST ℙáⓢⓢᵂöṝⅆ 测试 密码 " - assert p == u" btcr TEST \u2119\xe1\u24e2\u24e2\u1d42\xf6\u1e5d\u2146 \u6d4b\u8bd5 \u5bc6\u7801 " - self.mpk_tester(btcrseed.WalletElectrum2, - self.TEST_ELECTRUM2_PASS_XPUB, - "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13, passphrase=p) - - def test_electrum2_pass_wide(self): - p = u"𝔅tcr 𝔗est 𝔓assword 测试密码" - assert p == u"\U0001d505tcr \U0001d517est \U0001d513assword \u6d4b\u8bd5\u5bc6\u7801" - self.mpk_tester(btcrseed.WalletElectrum2, - # for narrow Unicode builds, check that we reproduce the same Electrum 2.x bugs: - "xpub661MyMwAqRbcGYwDPmhGppsmr2NxcoFNAzGy3qRcE9wrtQhF6tCjtitFnizWKHv684AfshexRAiByRFX3VHpugBcAMYpwQezeYroi53KEKM" - if sys.maxunicode < 65536 else - # for wide Unicode builds, there are no bugs: - self.TEST_ELECTRUM2_PASS_XPUB, - "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13, passphrase=p) - - def test_bitcoinj(self): - # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export - self.mpk_tester(btcrseed.WalletBitcoinj, - "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4QLCpPu", - "laundry foil reform disagree cotton hope loud mix wheel snow real board") - - def test_bip44(self): - # an xpub at path m/44'/0'/0', as Mycelium for Android would export - self.mpk_tester(btcrseed.WalletBIP39, - "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx", - "certain come keen collect slab gauge photo inside mechanic deny leader drop") - - def test_bip44_firstfour(self): - # an xpub at path m/44'/0'/0', as Mycelium for Android would export - self.mpk_tester(btcrseed.WalletBIP39, - "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx", - "cert come keen coll slab gaug phot insi mech deny lead drop") - - def test_bip44_ja(self): - # an xpub at path m/44'/0'/0' - self.mpk_tester(btcrseed.WalletBIP39, - "xpub6BfYc7HCQuKNxRMfmUhtkJ8HQ5A4t4zTy8cAQWjD7x5SZAdUD2QM2WoymmGfAD84mgbXbxyWiR922dyRtZUK2JPtBr8YLTzcQod3orvGB3k", - u"あんまり おんがく いとこ ひくい こくはく あらゆる てあし げどく はしる げどく そぼろ はみがき") - - def test_bip44_pass(self): - # an xpub at path m/44'/0'/0', as Mycelium for Android would export - self.mpk_tester(btcrseed.WalletBIP39, - "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", - "certain come keen collect slab gauge photo inside mechanic deny leader drop", - passphrase=u"btcr-test-password") - - def test_bip44_pass_unicode(self): - # an xpub at path m/44'/0'/0', as Mycelium for Android would export - self.mpk_tester(btcrseed.WalletBIP39, - "xpub6CZe1G1A1CaaSepbekLMSk1sBRNA9kHZzEQCedudHAQHHB21FW9fYpQWXBevrLVQfL8JFQVFWEw3aACdr6szksaGsLiHDKyRd1rPJ6ev5ig", - "certain come keen collect slab gauge photo inside mechanic deny leader drop", - passphrase=u"btcr-тест-пароль") - - -is_sha3_loadable = None -def can_load_sha3(): - global is_sha3_loadable - if is_sha3_loadable is None: - try: - import sha3 - is_sha3_loadable = True - except ImportError: - is_sha3_loadable = False - return is_sha3_loadable - - -class TestRecoveryFromAddress(unittest.TestCase): - - @classmethod - def setUpClass(cls): - try: - hashlib.new(b"ripemd160") - except ValueError: - raise unittest.SkipTest("requires that hashlib implements RIPEMD-160") - - def address_tester(self, wallet_type, the_address, the_address_limit, correct_mnemonic, **kwds): - assert the_address_limit > 1 - - wallet = wallet_type.create_from_params(addresses=[the_address], address_limit=the_address_limit) - - # Convert the mnemonic string into a mnemonic_ids_guess - wallet.config_mnemonic(correct_mnemonic, **kwds) - correct_mnemonic_ids = btcrseed.mnemonic_ids_guess - - # Creates wrong mnemonic id guesses - wrong_mnemonic_iter = wallet.performance_iterator() - - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), wrong_mnemonic_iter.next())), (False, 2)) - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), correct_mnemonic_ids, wrong_mnemonic_iter.next())), (correct_mnemonic_ids, 2)) - - # Make sure the address_limit is respected (note the "the_address_limit-1" below) - wallet = wallet_type.create_from_params(addresses=[the_address], address_limit=the_address_limit-1) - wallet.config_mnemonic(correct_mnemonic, **kwds) - self.assertEqual(wallet.return_verified_password_or_false( - (correct_mnemonic_ids,)), (False, 1)) - - def test_electrum1(self): - self.address_tester(btcrseed.WalletElectrum1, "12zAz6pAB6LhzGSZFCc6g9uBSWzwESEsPT", 3, - "straight subject wild ask clean possible age hurt squeeze cost stuck softly") - - def test_electrum2(self): - self.address_tester(btcrseed.WalletElectrum2, "14dpd9nayyoyCTNki5UUsm1KnAZ1x7o83E", 5, - "eagle pair eager human cage forget pony fall robot vague later bright acid", - expected_len=13) - - def test_electrum27(self): - self.address_tester(btcrseed.WalletElectrum2, "1HQrNUBEsEqwEaZZzMqqLqCHSVCGF7dTVS", 5, - "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", - expected_len=12) - - def test_bitcoinj(self): - self.address_tester(btcrseed.WalletBitcoinj, "17Czu38CcLwWr8jFZrDJBHWiEDd2QWhPSU", 4, - "skin join dog sponsor camera puppy ritual diagram arrow poverty boy elbow") - - def test_bip44(self): - self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, - "certain come keen collect slab gauge photo inside mechanic deny leader drop") - - @unittest.skipUnless(can_load_sha3(), "requires pysha3") - def test_ethereum(self): - self.address_tester(btcrseed.WalletEthereum, "0x9544a5BD7D9AACDc0A12c360C1ec6182C84bab11", 3, - "cable top mango offer mule air lounge refuse stove text cattle opera") - - # tests for a bug affecting certain seeds/wallets in v0.7.1 - @unittest.skipUnless(can_load_sha3(), "requires pysha3") - def test_padding_bug(self): - self.address_tester(btcrseed.WalletEthereum, "0xaeaa91ba7235dc2d90e28875d3e466aaa27e076d", 2, - "appear section card oak mercy output person grab rotate sort where rural") - - -class TestAddressSet(unittest.TestCase): - HASH_BYTES = 1 - TABLE_LEN = 2 ** (8*HASH_BYTES) - BYTES_PER_ADDR = AddressSet(1)._bytes_per_addr - - def test_add(self): - aset = AddressSet(self.TABLE_LEN) - addr = "".join(chr(b) for b in xrange(20)) - self.assertNotIn(addr, aset) - aset.add(addr) - self.assertIn (addr, aset) - self.assertEqual(len(aset), 1) - - def collision_tester(self, aset, addr1, addr2): - aset.add(addr1) - self.assertIn (addr1, aset) - self.assertNotIn(addr2, aset) - self.assertEqual(len(aset), 1) - aset.add(addr2) - self.assertIn (addr1, aset) - self.assertIn (addr2, aset) - self.assertEqual(len(aset), 2) - return aset - # - def test_collision(self): - aset = AddressSet(self.TABLE_LEN) - # the last HASH_BYTES (1) bytes are the "hash", and only the next BYTES_PER_ADDR (8) rightmost bytes are stored - addr1 = "".join(chr(b) for b in xrange(20)) - addr2 = addr1.replace(chr(20 - self.HASH_BYTES - self.BYTES_PER_ADDR), "\0") # the leftmost byte that's stored - self.collision_tester(aset, addr1, addr2) - # - def test_collision_fail(self): - aset = AddressSet(self.TABLE_LEN) - # the last 1 (HASH_BYTES) bytes are the "hash", and only the next 8 (BYTES_PER_ADDR) rightmost bytes are stored - addr1 = "".join(chr(b) for b in xrange(20)) - addr2 = addr1.replace(chr(20 - self.HASH_BYTES - self.BYTES_PER_ADDR - 1), "\0") # the rightmost byte not stored - self.assertRaises(unittest.TestCase.failureException, self.collision_tester, aset, addr1, addr2) - self.assertEqual(len(aset), 1) - - def test_null(self): - aset = AddressSet(self.TABLE_LEN) - addr = 20 * "\0" - aset.add(addr) - self.assertNotIn(addr, aset) - self.assertEqual(len(aset), 0) - - # very unlikely to fail; if it does, there's probably a significant problem - def test_false_positives(self): - aset = AddressSet(131072, bytes_per_addr=5) # reduce bytes_per_addr to increase failure probability - rand_byte_count = aset._hash_bytes + aset._bytes_per_addr - nonrand_prefix = (20 - rand_byte_count) * "\0" - for i in xrange(aset._max_len): - aset.add(nonrand_prefix + "".join(chr(random.randrange(256)) for i in xrange(rand_byte_count))) - for i in xrange(524288): - self.assertNotIn( - nonrand_prefix + "".join(chr(random.randrange(256)) for i in xrange(rand_byte_count)), - aset) - - def test_file(self): - aset = AddressSet(self.TABLE_LEN) - addr = "".join(chr(b) for b in xrange(20)) - aset.add(addr) - dbfile = tempfile.TemporaryFile() - aset.tofile(dbfile) - dbfile.seek(0) - aset = AddressSet.fromfile(dbfile) - self.assertTrue(dbfile.closed) # should be closed by AddressSet in read-only mode - self.assertIn(addr, aset) - self.assertEqual(len(aset), 1) - - def test_file_update(self): - aset = AddressSet(self.TABLE_LEN) - dbfile = tempfile.NamedTemporaryFile(delete=False) - try: - aset.tofile(dbfile) - dbfile.seek(0) - aset = AddressSet.fromfile(dbfile, mmap_access=mmap.ACCESS_WRITE) - addr = "".join(chr(b) for b in xrange(20)) - aset.add(addr) - aset.close() - self.assertTrue(dbfile.closed) - dbfile = open(dbfile.name, "rb") - aset = AddressSet.fromfile(dbfile) - self.assertIn(addr, aset) - self.assertEqual(len(aset), 1) - finally: - aset.close() - dbfile.close() - os.remove(dbfile.name) - - def test_pickle_mmap(self): - aset = AddressSet(self.TABLE_LEN) - addr = "".join(chr(b) for b in xrange(20)) - aset.add(addr) - dbfile = tempfile.NamedTemporaryFile(delete=False) - try: - aset.tofile(dbfile) - dbfile.seek(0) - aset = AddressSet.fromfile(dbfile) # now it's an mmap - pickled = pickle.dumps(aset, protocol=pickle.HIGHEST_PROTOCOL) - aset.close() # also closes the file - aset = pickle.loads(pickled) - self.assertIn(addr, aset) - self.assertEqual(len(aset), 1) - finally: - aset.close() - dbfile.close() - os.remove(dbfile.name) - - -class TestRecoveryFromAddressDB(unittest.TestCase): - - @classmethod - def setUpClass(cls): - if not os.path.isfile(btcrseed.ADDRESSDB_DEF_FILENAME): - raise unittest.SkipTest("requires '"+btcrseed.ADDRESSDB_DEF_FILENAME+"' file in the current directory") - - def addressdb_tester(self, wallet_type, the_address_limit, correct_mnemonic, **kwds): - assert the_address_limit > 1 - - addressdb = AddressSet.fromfile(open(btcrseed.ADDRESSDB_DEF_FILENAME, "rb"), preload=False) - wallet = wallet_type.create_from_params(hash160s=addressdb, address_limit=the_address_limit) - - # Convert the mnemonic string into a mnemonic_ids_guess - wallet.config_mnemonic(correct_mnemonic, **kwds) - correct_mnemonic_ids = btcrseed.mnemonic_ids_guess - - # Creates wrong mnemonic id guesses - wrong_mnemonic_iter = wallet.performance_iterator() - - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), wrong_mnemonic_iter.next())), (False, 2)) - self.assertEqual(wallet.return_verified_password_or_false( - (wrong_mnemonic_iter.next(), correct_mnemonic_ids, wrong_mnemonic_iter.next())), (correct_mnemonic_ids, 2)) - - # Make sure the address_limit is respected (note the "the_address_limit-1" below) - wallet = wallet_type.create_from_params(hash160s=addressdb, address_limit=the_address_limit-1) - wallet.config_mnemonic(correct_mnemonic, **kwds) - self.assertEqual(wallet.return_verified_password_or_false( - (correct_mnemonic_ids,)), (False, 1)) - - def test_bip44(self): - # 1D5noXUg7za4W3zjhgCmn1cFewqRrXSM9B is in block 476446 - self.addressdb_tester(btcrseed.WalletBIP39, 5, - "certain come keen collect slab gauge photo inside mechanic deny leader drop") - - -class TestSeedTypos(unittest.TestCase): - XPUB = "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx" - - def seed_tester(self, the_mpk, correct_mnemonic, mnemonic_guess, typos = None, big_typos = 0): - correct_mnemonic = correct_mnemonic.split() - assert mnemonic_guess.split() != correct_mnemonic - assert typos or big_typos - btcrseed.loaded_wallet = btcrseed.WalletBIP39.create_from_params(mpk=the_mpk) - btcrseed.loaded_wallet.config_mnemonic(mnemonic_guess) - self.assertEqual( - btcrseed.run_btcrecover(typos or big_typos, big_typos, extra_args="--threads 1".split()), - tuple(correct_mnemonic)) - - def test_delete(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "certain come come keen collect slab gauge photo inside mechanic deny leader drop", # guess - typos=1) - - def test_replacewrong(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "certain X keen collect slab gauge photo inside mechanic deny leader drop", # guess - big_typos=1) - - def test_insert(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - " come keen collect slab gauge photo inside mechanic deny leader drop", # guess - big_typos=1) - - def test_swap(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "certain keen come collect slab gauge photo inside mechanic deny leader drop", # guess - typos=1) - - def test_replace(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "disagree come keen collect slab gauge photo inside mechanic deny leader drop", # guess - big_typos=1) - - def test_replaceclose(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "certain become keen collect slab gauge photo inside mechanic deny leader drop", # guess - typos=1) - - def test_replaceclose_firstfour(self): - self.seed_tester(self.XPUB, - "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct - "cere come keen coll slab gaug phot insi mech deny lead drop", # guess - # "cere" is close to "cert" in the en-firstfour language, even though "cereal" is not close to "certain" - typos=1) - - -# All seed tests except TestAddressSet.test_false_positives are quick -class QuickTests(unittest.TestSuite): - def __init__(self): - super(QuickTests, self).__init__() - for suite in unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]): - if isinstance(suite._tests[0], TestAddressSet): - for test_num in xrange(len(suite._tests)): - if suite._tests[test_num]._testMethodName == "test_false_positives": - del suite._tests[test_num] - break - self.addTests(suite) - - -if __name__ == b'__main__': - - import argparse - - # Add one new argument to those already provided by unittest.main() - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--no-buffer", action="store_true") - args, unittest_args = parser.parse_known_args() - sys.argv[1:] = unittest_args - - unittest.main(buffer = not args.no_buffer) +# -*- coding: utf-8 -*- + +# test_seeds.py -- unit tests for seedrecover.py +# Copyright (C) 2014-2017 Christopher Gurnee +# 2019-2021 Stephen Rothery +# +# This file is part of btcrecover. +# +# btcrecover is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# btcrecover is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + + +import warnings, unittest, os, tempfile, shutil, filecmp, sys, hashlib, random, mmap, pickle + +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..")) +from btcrecover import aezeed, btcrpass, btcrseed +from btcrecover.addressset import AddressSet +import btcrecover.opencl_helpers + +wallet_dir = os.path.join(os.path.dirname(__file__), "test-wallets") + + +# def setUpModule(): +# global orig_warnings +# orig_warnings = warnings.catch_warnings() +# orig_warnings.__enter__() # save the current warnings settings (it's a context manager) +# # Convert warnings to errors: +# warnings.simplefilter("error") +# +# +# def tearDownModule(): +# orig_warnings.__exit__(None, None, None) # restore the original warnings settings + +opencl_device_count = None +def has_any_opencl_devices(): + global opencl_device_count + if opencl_device_count is None: + try: + devs = list(btcrpass.get_opencl_devices()) + except ImportError: + devs = () + opencl_device_count = len(devs) + return opencl_device_count > 0 + + +is_groestlcoin_hash_loadable = None +def can_load_groestlcoin_hash(): + global is_groestlcoin_hash_loadable + if is_groestlcoin_hash_loadable is None: + is_groestlcoin_hash_loadable = False + try: + import groestlcoin_hash + is_groestlcoin_hash_loadable = True + except ModuleNotFoundError: + pass + + return is_groestlcoin_hash_loadable + +is_PyCryptoHDWallet_loadable = None +def can_load_PyCryptoHDWallet(): + global is_PyCryptoHDWallet_loadable + if is_PyCryptoHDWallet_loadable is None: + try: + import py_crypto_hd_wallet + is_PyCryptoHDWallet_loadable = True + except: + is_PyCryptoHDWallet_loadable = False + return is_PyCryptoHDWallet_loadable + +is_nacl_loadable = None +def can_load_nacl(): + global is_nacl_loadable + if is_nacl_loadable is None: + try: + import nacl.bindings + is_nacl_loadable = True + except: + is_nacl_loadable = False + return is_nacl_loadable + +is_bitstring_loadable = None +def can_load_bitstring(): + global is_bitstring_loadable + if is_bitstring_loadable is None: + try: + import bitstring + is_bitstring_loadable = True + except: + is_bitstring_loadable = False + return is_bitstring_loadable + +eth2_staking_deposit_available = None +def can_load_staking_deposit(): + global eth2_staking_deposit_available + if eth2_staking_deposit_available is None: + try: + from staking_deposit.key_handling.key_derivation.path import mnemonic_and_path_to_key + from py_ecc.bls import G2ProofOfPossession as bls + + eth2_staking_deposit_available = True + except: + eth2_staking_deposit_available = False + return eth2_staking_deposit_available + +# import bundled modules that won't work in some environments +bundled_bitcoinlib_mod_available = None +def can_load_bundled_bitcoinlib_mod(): + global bundled_bitcoinlib_mod_available + if bundled_bitcoinlib_mod_available is None: + try: + from lib.bitcoinlib_mod import encoding as encoding_mod + + bundled_bitcoinlib_mod_available = True + except: + bundled_bitcoinlib_mod_available = False + return bundled_bitcoinlib_mod_available + +is_stellarsdk_loadable = None +def can_load_stellarsdk(): + global is_stellarsdk_loadable + if is_stellarsdk_loadable is None: + try: + import stellar_sdk + is_stellarsdk_loadable = True + except: + is_stellarsdk_loadable = False + return is_stellarsdk_loadable + +is_slip10_loadable = None +def can_load_slip10(): + global can_load_slip10 + if can_load_slip10 is None: + try: + import slip10 + can_load_slip10 = True + except: + can_load_slip10 = False + return can_load_slip10 + +is_bip_utils_loadable = None +def can_load_bip_utils(): + global is_bip_utils_loadable + if is_bip_utils_loadable is None: + try: + from bip_utils import Bip32Slip10Ed25519 # noqa: F401 + is_bip_utils_loadable = True + except Exception: + is_bip_utils_loadable = False + return is_bip_utils_loadable + +is_ShamirMnemonic_loadable = None +def can_load_ShamirMnemonic(): + global is_ShamirMnemonic_loadable + if is_ShamirMnemonic_loadable is None: + try: + import shamir_mnemonic + is_ShamirMnemonic_loadable = True + except Exception: + is_ShamirMnemonic_loadable = False + return is_ShamirMnemonic_loadable + + +# Similar to unittest.skipUnless, except the first arg is a function returning a bool instead +# of just a bool. This function isn't called until just before the test is to be run. This +# permits checking the character mode (which isn't set until later) and prevents multiprocessing +# under Windows from calling skipUnless which would otherwise produce spurious warning messages. +def skipUnless(condition_func, reason): + assert callable(condition_func) + + def decorator(test_func): + def skip_or_test(self): + if not condition_func(): + self.skipTest(reason) + test_func(self) + + return skip_or_test + + return decorator + + +_AEZEED_DEFAULT_MNEMONIC = ( + "absorb original enlist once climb erode kid thrive kitchen giant define tube " + "orange leader harbor comfort olive fatal success suggest drink penalty chimney ritual" +) +_AEZEED_CUSTOM_MNEMONIC = ( + "above gap bronze point damp name group actress idea festival cream during " + "bid blanket dumb wage foster merit success suggest drink protect autumn box" +) +_AEZEED_ENTROPY = bytes.fromhex("81b637d86359e6960de795e41e0b4cfd") +_AEZEED_SALT = b"salt1" +_AEZEED_BIRTHDAY = 3365 + + +class TestAezeedModule(unittest.TestCase): + + @classmethod + def setUpClass(cls): + wordlist = btcrseed.load_wordlist("bip39", "en") + cls.word_to_index = {word: idx for idx, word in enumerate(wordlist)} + + def test_validate_mnemonic(self): + words = _AEZEED_DEFAULT_MNEMONIC.split() + self.assertTrue(aezeed.validate_mnemonic(words, self.word_to_index)) + tampered = list(words) + tampered[-1] = "foobar" + self.assertFalse(aezeed.validate_mnemonic(tampered, self.word_to_index)) + + def test_decode_default_passphrase(self): + seed = aezeed.decode_mnemonic( + _AEZEED_DEFAULT_MNEMONIC.split(), "", self.word_to_index + ) + self.assertEqual(seed.entropy, _AEZEED_ENTROPY) + self.assertEqual(seed.salt, _AEZEED_SALT) + self.assertEqual(seed.internal_version, 0) + self.assertEqual(seed.birthday, _AEZEED_BIRTHDAY) + + def test_decode_custom_passphrase(self): + seed = aezeed.decode_mnemonic( + _AEZEED_CUSTOM_MNEMONIC.split(), "!very_safe_55345_password*", self.word_to_index + ) + self.assertEqual(seed.entropy, _AEZEED_ENTROPY) + self.assertEqual(seed.salt, _AEZEED_SALT) + self.assertEqual(seed.birthday, _AEZEED_BIRTHDAY) + + def test_decode_with_incorrect_passphrase(self): + with self.assertRaises(aezeed.InvalidPassphraseError): + aezeed.decode_mnemonic( + _AEZEED_CUSTOM_MNEMONIC.split(), "wrong", self.word_to_index + ) + + def test_wallet_derivation(self): + wallet = btcrseed.WalletAezeed.create_from_params( + addresses=["1Hp6UXuJjzt9eSBa9LhtW97KPb44bq4CAQ"], + address_limit=1, + ) + wallet.config_mnemonic(_AEZEED_DEFAULT_MNEMONIC, passphrases=[u""]) + mnemonic_ids = btcrseed.mnemonic_ids_guess + derived = wallet._derive_seed(mnemonic_ids) + self.assertEqual(len(derived), 1) + self.assertEqual(derived[0][0], _AEZEED_ENTROPY) + + def test_wallet_checksum_only_mode(self): + wallet = btcrseed.WalletAezeed.create_from_params() + self.assertTrue(wallet._checksum_only_mode) + wallet.config_mnemonic(_AEZEED_DEFAULT_MNEMONIC, passphrases=[u""]) + mnemonic_ids = btcrseed.mnemonic_ids_guess + result, count = wallet.return_verified_password_or_false((mnemonic_ids,)) + self.assertEqual(result, mnemonic_ids) + self.assertEqual(count, 1) + + +class TestRecoveryFromWallet(unittest.TestCase): + + @classmethod + def setUpClass(cls): + btcrseed.register_autodetecting_wallets() + + # Checks a test wallet against the known mnemonic, and ensures + # that the library doesn't make any changes to the wallet file + def wallet_tester(self, wallet_basename, correct_mnemonic, **kwds): + assert os.path.basename(wallet_basename) == wallet_basename + wallet_filename = os.path.join(wallet_dir, wallet_basename) + + temp_dir = tempfile.mkdtemp("-test-btcr") + try: + temp_wallet_filename = os.path.join(temp_dir, wallet_basename) + shutil.copyfile(wallet_filename, temp_wallet_filename) + + wallet = btcrseed.btcrpass.load_wallet(temp_wallet_filename) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic, **kwds) + correct_mnemonic = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), correct_mnemonic, wrong_mnemonic_iter.__next__())), + (correct_mnemonic, 2)) + + del wallet + self.assertTrue( + filecmp.cmp(wallet_filename, temp_wallet_filename, False)) # False == always compare file contents + finally: + shutil.rmtree(temp_dir) + + def test_electrum1_legacy(self): + self.wallet_tester("electrum-wallet", + "straight subject wild ask clean possible age hurt squeeze cost stuck softly") + + def test_electrum2_legacy(self): + self.wallet_tester("electrum2-wallet", + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13) + + def test_electrum27_legacy(self): + self.wallet_tester("electrum27-wallet", + "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", + expected_len=12) + + def test_electrum2_upgradedfrom_electrum1_legacy(self): + self.wallet_tester("electrum1-upgradedto-electrum2-wallet", + "straight subject wild ask clean possible age hurt squeeze cost stuck softly") + + def test_electrum27_upgradedfrom_electrum1_legacy(self): + self.wallet_tester("electrum1-upgradedto-electrum27-wallet", + "straight subject wild ask clean possible age hurt squeeze cost stuck softly") + + +class TestRecoveryFromMPK(unittest.TestCase): + + def mpk_tester(self, wallet_type, the_mpk, correct_mnemonic, test_path=None, **kwds): + + # Don't call the wallet create with a path parameter if we don't have to. (for the same of compatibility across wallet types) + if test_path == None: + wallet = wallet_type.create_from_params(mpk=the_mpk) + else: + wallet = wallet_type.create_from_params(mpk=the_mpk, path=[test_path]) + + # Convert the mnemonic string into a mnemonic_ids_guessde + wallet.config_mnemonic(correct_mnemonic, **kwds) + correct_mnemonic = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), correct_mnemonic, wrong_mnemonic_iter.__next__())), (correct_mnemonic, 2)) + + def test_electrum1_xpub_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum1, + "c79b02697b32d9af63f7d2bd882f4c8198d04f0e4dfc5c232ca0c18a87ccc64ae8829404fdc48eec7111b99bda72a7196f9eb8eb42e92514a758f5122b6b5fea", + "straight subject wild ask clean possible age hurt squeeze cost stuck softly") + + def test_electrum2_xpub_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "xpub661MyMwAqRbcGsUXkGBkytQkYZ6M16bFWwTocQDdPSm6eJ1wUsxG5qty1kTCUq7EztwMscUstHVo1XCJMxWyLn4PP1asLjt4gPt3HkA81qe", + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13) + + def test_electrum27_xpub_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "xpub661MyMwAqRbcGt6qtQ19Ttwvo5Dbf2cQdA2GMf9Xkjth8NqYXXordg3gLK1npATRm9Fr7d7fA5ziCwqEVMmzeRezofp8CEaru8pJ57zV8hN", + "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", + expected_len=12) + + def test_electrum27_xpub_keystore1_2fa_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "xpub69RkaG6zUND7ofAxz1GMsd5hAzYkwCV3zrGwfjmYyxDpzivHcRBSqAkYLsfe7MKWpgWBUGxtbj3Zd2bW6orp1tbWR2hVY37G7HYRzgbhdp9", + "carry parade soul sell peace sphere upgrade tackle length tomorrow stick cactus", + expected_len=12) + + def test_electrum27_xpub_keystore1_2fa_segwit(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "Zpub6yEjvzHB3gpxx9S4fb1LZHn8FpFSYkoWifyv81zEYwJEA8c24J7NDqYsa4sFnHgkR2CrAe7pdXuoSkqAR6BMe6nukMnbPnsNRuBtqfmYZsd", + "sunny innocent mail hen act wire wash wish divorce adjust toward canoe", + expected_len=12) + + def test_electrum2_xpub_legacy_ja(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "xpub661MyMwAqRbcFAyy6MaWCK5uGHhgvMZNaFbKy1TbSrcEm8oCgD3N2AfzPC8ndmdvcQbY8EbU414X4xNrs9dcNgcntShiBFJYJ6HJy7zKnQV", + u"すんぽう うけつけ ぬいくぎ きどう ごはん たかね いてざ よしゅう なにもの われる たんき さとる あじわう", + expected_len=13) + + TEST_ELECTRUM2_PASS_XPUB = "xpub661MyMwAqRbcG4s8buUEpDeeBMZeXxnroY3i9jZJNQuDrWQaCyR5Mvk9pmRK5q5WrEKTwSuYwBiSjcp3ZkM2ujhngFQXxvrTyv2uFCryyii" + + def test_electrum2_xpub_pass_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum2, + self.TEST_ELECTRUM2_PASS_XPUB, + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13, passphrases=[u"btcr test password 测试密码",]) + + def test_electrum28_xpub_pass_legacy(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "xpub661MyMwAqRbcEa7eRrwnfAmhDAKBzFiuNxjcUKhwk18J3z1muMxnm1AKYjUo3VEUfYBDshhyxcUqpvqJEgacEMYyGRa7TUNXbieqrKibhCg", + "water wait table horse smooth birth identify food favorite depend brother hand", + expected_len=12, passphrases=["btcr-test-password",]) + + def test_electrum28_xpub_pass_segwit(self): + self.mpk_tester(btcrseed.WalletElectrum2, + "zpub6oCYZXxa8YvFyR51r12U7q5B2cbeY25MqRnWTdXYex1EPuTvbfmeJmCFoo88xbqkgHyitfK1UW2q5CTPUW8fWqpZtsDF3jVwk6PTdGTbX2w", + "quote voice evidence aspect warfare hire system black rate wing ask rug", + expected_len=12, passphrases=["btcr-test-password",]) + + def test_electrum2_xpub_pass_normalize_legacy(self): + p = u" btcr TEST ℙáⓢⓢᵂöṝⅆ 测试 密码 " + assert p == u" btcr TEST \u2119\xe1\u24e2\u24e2\u1d42\xf6\u1e5d\u2146 \u6d4b\u8bd5 \u5bc6\u7801 " + self.mpk_tester(btcrseed.WalletElectrum2, + self.TEST_ELECTRUM2_PASS_XPUB, + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13, passphrases=[p,]) + + def test_electrum2_xpub_pass_wide_legacy(self): + p = u"𝔅tcr 𝔗est 𝔓assword 测试密码" + assert p == u"\U0001d505tcr \U0001d517est \U0001d513assword \u6d4b\u8bd5\u5bc6\u7801" + self.mpk_tester(btcrseed.WalletElectrum2, + # for narrow Unicode builds, check that we reproduce the same Electrum 2.x bugs: + "xpub661MyMwAqRbcGYwDPmhGppsmr2NxcoFNAzGy3qRcE9wrtQhF6tCjtitFnizWKHv684AfshexRAiByRFX3VHpugBcAMYpwQezeYroi53KEKM" + if sys.maxunicode < 65536 else + # for wide Unicode builds, there are no bugs: + self.TEST_ELECTRUM2_PASS_XPUB, + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13, passphrases=[p,]) + + def test_bitcoinj_xpub_legacy(self): + # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export + self.mpk_tester(btcrseed.WalletBitcoinj, + "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4QLCpPu", + "laundry foil reform disagree cotton hope loud mix wheel snow real board") + + def test_bip39_xpub(self): + # an xpub at path m/44'/0'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx", + "certain come keen collect slab gauge photo inside mechanic deny leader drop") + + def test_bip39_ypub(self): + # an ypub at path m/49'/0'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "ypub6X4G7a9RYWheXmmhfrMR8Nt5XeThiupghvdiYyZFsRWUKKSfzamAUM66Ay9P8XsD7asG6PqSBBDbGihKQndHfgkg2HnHfx2fN69AYzpcxVT", + "ice stool great wine enough odor vocal crane owner magnet absent scare", + "m/49'/0'/0'/0") + + def test_bip39_zpub(self): + # an zpub at path m/84'/0'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "zpub6rpXnwsvpxao28enE4M3xMbHuEkMfhqQc3o1uXp8pBYUA7wG2Ez4SBDFJCWJr3vaP2ysauHX6f68iWzVBzWMkc4BBz9DhFZ9MpKVZHGBLKo", + "ice stool great wine enough odor vocal crane owner magnet absent scare", + "m/84'/0'/0'/0") + + def test_bip44_firstfour(self): + # an xpub at path m/44'/0'/0', as Mycelium for Android would export + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx", + "cert come keen coll slab gaug phot insi mech deny lead drop") + + def test_bip44_ja(self): + # an xpub at path m/44'/0'/0' + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6BfYc7HCQuKNxRMfmUhtkJ8HQ5A4t4zTy8cAQWjD7x5SZAdUD2QM2WoymmGfAD84mgbXbxyWiR922dyRtZUK2JPtBr8YLTzcQod3orvGB3k", + u"あんまり おんがく いとこ ひくい こくはく あらゆる てあし げどく はしる げどく そぼろ はみがき") + + def test_bip44_pass(self): + # an xpub at path m/44'/0'/0', as Mycelium for Android would export + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6D3uXJmdUg4xVnCUkNXJPCkk18gZAB8exGdQeb2rDwC5UJtraHHARSCc2Nz7rQ14godicjXiKxhUn39gbAw6Xb5eWb5srcbkhqPgAqoTMEY", + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + passphrases=[u"btcr-test-password",]) + + def test_bip44_pass_unicode(self): + # an xpub at path m/44'/0'/0', as Mycelium for Android would export + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6CZe1G1A1CaaSepbekLMSk1sBRNA9kHZzEQCedudHAQHHB21FW9fYpQWXBevrLVQfL8JFQVFWEw3aACdr6szksaGsLiHDKyRd1rPJ6ev5ig", + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + passphrases=[u"btcr-тест-пароль",]) + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_groestlcoinj_xpub_legacy(self): + # an xpub at path m/0', as Bitcoin Wallet for Android/BlackBerry would export + self.mpk_tester(btcrseed.WalletBitcoinj, + "xpub67tjk7ug7iNivs1f1pmDswDDbk6kRCe4U1AXSiYLbtp6a2GaodSUovt3kNrDJ2q18TBX65aJZ7VqRBpnVJsaVQaBY2SANYw6kgZf4PGcxjU", + "laundry foil reform disagree cotton hope loud mix wheel snow real board") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_grs_bip39_xpub(self): + # an xpub at path m/44'/17'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "xpub6FPF487W2VhCCKBUXuSAVtSTe8MxEJikuQTxicJxfHHAZbBQLsGHNdCYCHEbNmpzaXMvJWKQ6y93BtXSkte2oRmtvuYbm8bKcUUL5LCuQbo", + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + "m/44'/17'/0'/0") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_grs_bip39_ypub(self): + # an ypub at path m/49'/17'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "ypub6YwUoVLhxxKrNrvireT1onpSWXFRGvp4kHGceUqhK8Xja99tGAdmQqUSQceyGMAhK1c5mnFKMVUBokmS2Ka2C2jRTGZrm4nHzxVyDM48egV", + "ice stool great wine enough odor vocal crane owner magnet absent scare", + "m/49'/17'/0'/0") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_grs_bip39_zpub(self): + # an zpub at path m/84'/17'/0', as any native segwit BIP39 wallet would export + self.mpk_tester(btcrseed.WalletBIP39, + "zpub6u5Ro8kyXwV3zueN2G8fUwJ1hHAjYN6Ld1VCK9KGMw6m2R5M8ZtqBCrp6aQXZVh9cJWGvSm4J8mBwSsYboYfR5Ybsv8LeSYYWQk5ZhHJE4a", + "ice stool great wine enough odor vocal crane owner magnet absent scare", + "m/84'/17'/0'/0") + +class TestRecoveryFromCheckSum(unittest.TestCase): + + def checksum_tester(self, wallet_type, expected_len, correct_mnemonic, **kwds): + + # Don't call the wallet create with a path parameter if we don't have to. (for the same of compatibility across wallet types) + btcrseed.loaded_wallet = wallet_type.create_from_params() + + # Convert the mnemonic string into a mnemonic_ids_guessde + btcrseed.loaded_wallet.config_mnemonic(mnemonic_guess=correct_mnemonic, expected_len=expected_len, **kwds) + correct_mnemonic = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = btcrseed.loaded_wallet.performance_iterator() + + self.assertEqual(btcrseed.loaded_wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(btcrseed.loaded_wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), correct_mnemonic, wrong_mnemonic_iter.__next__())), (correct_mnemonic, 2)) + + # I don't have a test v2 seed to test + # def test_blockchain_password_seedv2(self): + # self.checksum_tester(btcrseed.BlockChainPasswordV2, 15, + # "hill long stupid finally dream taught tree twice tea together bar useless diamond sanity serve") + + def test_blockchain_password_seedv3(self): + self.checksum_tester(btcrseed.BlockChainPasswordV3, 17, + "carve witch manage yerevan yerevan yerevan yerevan yerevan yerevan yerevan yerevan hardly hamburgers insiders hamburgers ignite infernal") + +is_sha3_loadable = None +def can_load_keccak(): + global is_sha3_loadable + if is_sha3_loadable is None: + try: + from lib.eth_hash.auto import keccak + keccak(b'') + is_sha3_loadable = True + except ImportError: + is_sha3_loadable = False + return is_sha3_loadable + + +class TestRecoveryFromAddress(unittest.TestCase): + + def address_tester(self, wallet_type, the_address, the_address_limit, correct_mnemonic, test_path=None, + pathlist_file=None, addr_start_index = 0, force_p2sh = False, checksinglexpubaddress = False, force_p2tr = False, **kwds): + + if pathlist_file: + test_path = btcrseed.load_pathlist("./derivationpath-lists/" + pathlist_file) + + if isinstance(the_address, (list, tuple, set)): + address_list = list(the_address) + else: + address_list = [the_address] + + # Don't call the wallet create with a path parameter if we don't have to. (for the same of compatibility across wallet types) + if test_path == None: + wallet = wallet_type.create_from_params(addresses=address_list, + address_limit=the_address_limit, + address_start_index=addr_start_index, + force_p2sh=force_p2sh, + checksinglexpubaddress=checksinglexpubaddress, + force_p2tr=force_p2tr) + else: + wallet = wallet_type.create_from_params(addresses=address_list, + address_limit=the_address_limit, + address_start_index=addr_start_index, + force_p2sh=force_p2sh, + path=test_path, + checksinglexpubaddress=checksinglexpubaddress, + force_p2tr=force_p2tr,) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic, **kwds) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), correct_mnemonic_ids, wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + if the_address_limit > 1: + # Make sure the address_limit is respected (note the "the_address_limit-1" below) + if test_path == None: + wallet = wallet_type.create_from_params(addresses=[the_address], address_limit=the_address_limit - 1) + else: + wallet = wallet_type.create_from_params(addresses=[the_address], address_limit=the_address_limit - 1, + path=test_path) + + wallet.config_mnemonic(correct_mnemonic, **kwds) + self.assertEqual(wallet.return_verified_password_or_false( + (correct_mnemonic_ids,)), (False, 1)) + + def address_tester_cardano(self, the_address, correct_mnemonic): + + test_path = btcrseed.load_pathlist("./derivationpath-lists/ADA.txt") + + wallet = btcrseed.WalletCardano.create_from_params(addresses=[the_address]) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_cpu( + (wrong_mnemonic_iter.__next__(), correct_mnemonic_ids, wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + def address_tester_cardano_opencl(self, the_address, correct_mnemonic): + + test_path = btcrseed.load_pathlist("./derivationpath-lists/ADA.txt") + + wallet = btcrseed.WalletCardano.create_from_params(addresses=[the_address]) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet._return_verified_password_or_false_opencl( + (wrong_mnemonic_iter.__next__(), correct_mnemonic_ids, wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + def test_cardano_icarus_baseaddress(self): + self.address_tester_cardano("addr1q9pv008mvhh22rney454j4z07nyyj9ygal57juv9xct4kayyk6y9htlyut67pks8j3s0jjs3f5z40rd9afd35ehwny4s4va2du", + "cave table seven there praise limit fat decorate middle gold ten battle trigger luggage demand") + + def test_cardano_icarus_stakeaddress(self): + self.address_tester_cardano("stake1uxztdzzm4ljw9a0qmgregc8efgg56p2h3kj75kc6vmhfj2cyg0jmy", + "cave table seven there praise limit fat decorate middle gold ten battle trigger luggage demand") + + def test_cardano_ledger_baseaddress(self): + self.address_tester_cardano("addr1q9wwzskx6c3mc4zh4mud9wrcg6yhj6pv96apf9hed0ewjr7aeyz04x3n0hpuw4c9882chhndfc47gk77kyqml5f4s38qeqlxk7", + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + def test_cardano_trezor_12word_baseaddress(self): + self.address_tester_cardano("addr1q8k0u70k6sxkcl6x539k84ntldh32de47ac8tn4us9q7hufv7g4xxwuezu9q6xqnx7mr3ejhg0jdlczkyv3fs6p477fqxwz930", + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + def test_cardano_trezor_24word_baseaddress(self): + self.address_tester_cardano("addr1q97tp64cz7ec7gx09a7caucf0drglwtane9v23f8g0w5yxj727mx0j8stldrvcuh6zh6dfkj407enp3hc39s338982xq5c0yaq", + "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique") + + # def test_cardano_icarus_15word_baseaddress_opencl(self): + # self.address_tester_cardano_opencl("addr1q9pv008mvhh22rney454j4z07nyyj9ygal57juv9xct4kayyk6y9htlyut67pks8j3s0jjs3f5z40rd9afd35ehwny4s4va2du", + # "cave table seven there praise limit fat decorate middle gold ten battle trigger luggage demand") + + # def test_cardano_icarus_15word_stakeaddress_opencl(self): + # self.address_tester_cardano_opencl("stake1uxztdzzm4ljw9a0qmgregc8efgg56p2h3kj75kc6vmhfj2cyg0jmy", + # "cave table seven there praise limit fat decorate middle gold ten battle trigger luggage demand") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_cardano_ledger_baseaddress_opencl(self): + self.address_tester_cardano_opencl("addr1q9wwzskx6c3mc4zh4mud9wrcg6yhj6pv96apf9hed0ewjr7aeyz04x3n0hpuw4c9882chhndfc47gk77kyqml5f4s38qeqlxk7", + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_cardano_trezor_12word_baseaddress_opencl(self): + self.address_tester_cardano_opencl("addr1q8k0u70k6sxkcl6x539k84ntldh32de47ac8tn4us9q7hufv7g4xxwuezu9q6xqnx7mr3ejhg0jdlczkyv3fs6p477fqxwz930", + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + # @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + # def test_cardano_trezor_24word_baseaddress_opencl(self): + # self.address_tester_cardano_opencl("addr1q97tp64cz7ec7gx09a7caucf0drglwtane9v23f8g0w5yxj727mx0j8stldrvcuh6zh6dfkj407enp3hc39s338982xq5c0yaq", + # "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_cardano_icarus_18word_baseaddress_opencl(self): + self.address_tester_cardano_opencl("addr1qypv06cpahxc0lv9az2wlexeupzztfdnnag5swgrjwp40eppwhmrsyru6y7auplxrautystcsav5e4hssr8pte2l6khsxeehlc", + "around lawn weird blanket sense near west depth speak boy tourist found chief easy cheese pulp stand coast") + + # @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + # def test_cardano_icarus_21word_baseaddress_opencl(self): + # self.address_tester_cardano_opencl("addr1q902er275re9qg9p7zdu7dud64px7uzkqxn53vyqnykz3rtkpsj7jrzhlwqh4x3u23cgn23jpkxhsualemyylfqxc60snmt3xp", + # "despair chimney canyon rather crunch crumble night write lab chest shove check pear spatial craft faint brother amused pony tank neutral") + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_cardano_icarus_24word_baseaddress_opencl(self): + self.address_tester_cardano_opencl("addr1qx3f4r3qqvynsnvhxrkkycp83v93jg2fqkn7scxnvpe6t99f4evt0tdad8cvsdvenma8t68gfdkyvf3efjzslcn7r4ys72w3qh", + "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique") + + def test_electrum1_addr_legacy_12word_BTC(self): + self.address_tester(btcrseed.WalletElectrum1, "12zAz6pAB6LhzGSZFCc6g9uBSWzwESEsPT", 3, + "straight subject wild ask clean possible age hurt squeeze cost stuck softly") + + def test_electrum1_addr_legacy_24_word_BTC(self): + self.address_tester(btcrseed.WalletElectrum1, "1MFu6Wyp6Gy3PDpz2PtoNVdiFWDHR8TMuS", 3, + "bowl especially tomorrow fan sail defeat scary knock ripple third cheek blind join mark rock scratch truth interest bone perfection curve milk taint terror") + + def test_electrum2_addr_legacy_BTC(self): + self.address_tester(btcrseed.WalletElectrum2, "14dpd9nayyoyCTNki5UUsm1KnAZ1x7o83E", 5, + "eagle pair eager human cage forget pony fall robot vague later bright acid", + expected_len=13) + + def test_electrum27_addr_legacy_BTC(self): + self.address_tester(btcrseed.WalletElectrum2, "1HQrNUBEsEqwEaZZzMqqLqCHSVCGF7dTVS", 5, + "spot deputy pencil nasty fire boss moral rubber bacon thumb thumb icon", + expected_len=12) + + def test_electrum27_addr_legacy_LTC(self): + self.address_tester(btcrseed.WalletElectrum2, "LcgWmmHWX3FdysFCFaNGDTywQBcCepvrQ8", 5, + "fiber bubble warm green banana blood program ship barrel tennis cigar song", + expected_len=12) + + def test_electrum27_addr_segwit_BTC(self): + self.address_tester(btcrseed.WalletElectrum2, "bc1qztc99re7ml7hv4q4ds3jv29w7u4evwqd6t76kz", 5, + "first focus motor give search custom grocery suspect myth popular trigger praise", + expected_len=12) + + def test_electrum27_addr_segwit_LTC(self): + self.address_tester(btcrseed.WalletElectrum2, "ltc1qk3rqeum7p9xn8kcr0hx8mapr8mgc5exx7fypeh", 5, + "reduce cactus invite ask athlete address area earth place price rural usual", + expected_len=12) + + def test_electrum27_electroncash_cashaddr_BCH(self): + + self.address_tester(btcrseed.WalletElectrum2, "bitcoincash:qqvnr88mcqff3uzyjgc2e87ncwpsjth9yyyqmhq457", 5, + "huge rifle suffer segment ankle negative turkey inhale notable bullet forest run", + expected_len=12) + + def test_bitcoinj_addr_legacy_BTC(self): + self.address_tester(btcrseed.WalletBitcoinj, "17Czu38CcLwWr8jFZrDJBHWiEDd2QWhPSU", 4, + "skin join dog sponsor camera puppy ritual diagram arrow poverty boy elbow") + + def test_bip44_addr_BTC_defaultderivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", ) + + def test_bip44_addr_BTC_passphraseList(self): + testPassphrases = btcrseed.load_passphraselist("./btcrecover/test/test-listfiles/BIP39PassphraseListTest.txt") + self.address_tester(btcrseed.WalletBIP39, "1FB1Zr39YefYVEQ8s3V9SWsaN8pxpLkboD", 2, + "helmet quote motor network swear rude horse fault throw egg atom assault", passphrases = testPassphrases) + + def test_bip49_addr_BTC_defaultderivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "3NiRFNztVLMZF21gx6eE1nL3Q57GMGuunG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate " + "sister uniform advice pen praise soap lizard festival connect baby") + + def test_p2sh_addr_BTC_forceP2SH(self): + self.address_tester(btcrseed.WalletBIP39, "37WQFyiQkMTcbzWfmWGRxD92EcnTvwiTDg", 2, + "ring age mushroom empty rib suggest empower taste exile cloud harbor elbow visual fence " + "loyal deposit drink lend inhale employ tissue swallow fresh kangaroo", force_p2sh=True) + + def test_bip49_addr_BTC_force_start_index(self): + self.address_tester(btcrseed.WalletBIP39, "3MtDzhXzsSSkn49WdYCno7o5ZqAVxsFmqj", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate " + "sister uniform advice pen praise soap lizard festival connect baby", addr_start_index = 18) + + def test_bip84_addr_BTC_defaultderivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "bc1qv87qf7prhjf2ld8vgm7l0mj59jggm6ae5jdkx2", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate " + "sister uniform advice pen praise soap lizard festival connect baby") + def test_p2tr_bip86_addr_BTC_defaultderivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "bc1prg35cfxqc23zwqfpnt3qxmay2xyw76jngxag0agpzj24lhs85qfqr8ualh", 1, + "word hurdle hello session tail grace police castle minimum equal apple crunch") + + def test_p2tr_addr_BTC_forceP2TR(self): + self.address_tester(btcrseed.WalletBIP39, "bc1pqgsnwqe99ug0ygndc3g4cpc680ze9fraex6ud2lcpktphr0xxkusq2tmpj", 1, + "calm great hip soda enhance abuse tiny summer gloom depth shrug chronic", force_p2tr=True) + + def test_p2tr_bip86_addr_BTC_ordinalswallet(self): + self.address_tester(btcrseed.WalletBIP39, "bc1pmpa44tpufkq0fhw4m09el9uh98jchnhky62mrqwa74du6k5hy4xs43606x", 1, + "basket manage solve glide gravity deliver black wire spice gospel narrow seven", + ["m/86'/0'/0'"]) + + def test_bip44_addr_XRP(self): + self.address_tester(btcrseed.WalletBIP39, "rJGNUmwiYDwXEsLzUFV9njhP3syrDvA6hs", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + ["m/44'/144'/0'/0"]) + + def test_bip44_addr_BTC(self): + self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + ["m/44'/0'/0'/0"]) + + @skipUnless(can_load_bundled_bitcoinlib_mod,"Unable to load modified bitcoinlib in this environment") + def test_bip44_addr_TerraLuna(self): + self.address_tester(btcrseed.WalletBIP39, "terra1negkjtkr6wu2uzcwcuz0kj8w4z64uax3w0dv5u", 2, + "earth jelly weapon word focus shaft danger cruel inflict strong palace barrel peace strike timber orbit orphan tower size series scatter kiwi fat filter", + ["m/44'/330'/0'/0"]) + + def test_bip44_addr_BTC_multi_coin_derivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + ["m/44'/4'/0'/0","m/44'/3'/0'/0","m/44'/2'/0'/0","m/44'/1'/0'/0","m/44'/0'/0'/0"]) + + def test_bip44_addr_BTC_multi_account_derivationpaths(self): + self.address_tester(btcrseed.WalletBIP39, "1Bi4fRZTPna1nbBJ8KLxaFfWV3BFDV9xj3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + ["m/44'/0'/0'/0","m/44'/0'/1'/0","m/44'/0'/2'/0","m/44'/0'/3'/0","m/44'/0'/4'/0"]) + + def test_bip49_addr_BTC(self): + self.address_tester(btcrseed.WalletBIP39, "3NiRFNztVLMZF21gx6eE1nL3Q57GMGuunG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/49'/0'/0'/0"]) + + def test_bip84_addr_BTC(self): + self.address_tester(btcrseed.WalletBIP39, "bc1qv87qf7prhjf2ld8vgm7l0mj59jggm6ae5jdkx2", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/84'/0'/0'/0"]) + + def test_bip44_addr_LTC(self): + self.address_tester(btcrseed.WalletBIP39, "LhHbcBk84JpB41otvD7qqWzyGgyr8yDJ2a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/2'/0'/0"]) + + def test_bip49_addr_LTC(self): + self.address_tester(btcrseed.WalletBIP39, "MQT8szKNYyJU1hUPLnsfCYXkqLQbTewsj9", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/49'/2'/0'/0"]) + + def test_bip84_addr_LTC(self): + self.address_tester(btcrseed.WalletBIP39, "ltc1q2dzc0u75p5aule30w5t5hjdzhgh2kmgqyh2t0f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/84'/2'/0'/0"]) + + def test_bip44_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "VwrYFHeKbneYZdkPWTpXsUs3ZQ4ERan9tG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/28'/0'/0"]) + + def test_bip49_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "33DUUsVoodofnbrxFhqCSBkKaqjCHzQyYU", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/49'/28'/0'/0"]) + + def test_bip84_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "vtc1q4r6d6w0xnd4t2rlj8njcl7m7a9k0ezk9rjnc77", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/84'/28'/0'/0"]) + + def test_bip44_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "M9BBjQC5vWktdbrfZZorybzUY75wtNB7JC", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/22'/0'/0"]) + + def test_bip49_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "P8gv2vrMyVhDdjHgJf6yxH3vGarM9fCZ9f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/49'/22'/0'/0"]) + + def test_bip84_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "monacoin1q9v93ngm8srxtq7lwzypehax7xvewh2vch68m2f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/84'/22'/0'/0"]) + + def test_bip44_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "D8uui9mGXztcpZy5t5jWpSimCCyEDjYRHY", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + ["m/44'/20'/0'/0"]) + + def test_bip49_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "SjM4p9vWB7GvsiNMgyZef67SJz3SgmPwhj", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + ["m/49'/20'/0'/0"]) + + def test_bip84_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "dgb1qmtpcmpt5amuvvwvpelh220ec2ck7q4prsy2tqy", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + ["m/84'/20'/0'/0"]) + + def test_bip44_addr_BCH_CashAddr(self): + self.address_tester(btcrseed.WalletBIP39, "bitcoincash:qrdupm96x04u3ssjnuj7lpy7adt9y34p5vzh95y0y7", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/145'/0'/0"]) + + def test_bip44_addr_BCH_CashAddr_NoPrefix(self): + self.address_tester(btcrseed.WalletBIP39, "qrdupm96x04u3ssjnuj7lpy7adt9y34p5vzh95y0y7", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/145'/0'/0"]) + + def test_bip44_addr_DASH(self): + self.address_tester(btcrseed.WalletBIP39, "XkRVBsXz1UG7LP48QKT4ZEbyUS54oRjYpM", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/5'/0'/0"]) + + def test_bip44_addr_DOGE(self): + self.address_tester(btcrseed.WalletBIP39, "DANb1e9B2WtHJNDJUsiu1fTrtAzGJhqkPa", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/3'/0'/0"]) + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_bip44_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "FqGMQvKCb2idGbDd6SUBFuugynXRACEzuQ", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/44'/17'/0'/0"]) + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_bip49_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "384swZndJ7CjZhqx7JL29Whnommy9s9phF", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/49'/17'/0'/0"]) + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + def test_bip84_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "grs1qy9qewq3x843gss8z6h22gmc03gfzuuj7hz505a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby", + ["m/84'/17'/0'/0"]) + + @unittest.skipUnless(can_load_keccak(), "requires pycryptodome") + def test_ethereum_addr(self): + self.address_tester(btcrseed.WalletEthereum, "0x9544a5BD7D9AACDc0A12c360C1ec6182C84bab11", 3, + "cable top mango offer mule air lounge refuse stove text cattle opera") + + # tests for a bug affecting certain seeds/wallets in v0.7.1 + @unittest.skipUnless(can_load_keccak(), "requires pycryptodome") + def test_ethereum_addr_padding_bug(self): + self.address_tester(btcrseed.WalletEthereum, "0xaeaa91ba7235dc2d90e28875d3e466aaa27e076d", 2, + "appear section card oak mercy output person grab rotate sort where rural") + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_private_key(self): + self.address_tester( + btcrseed.WalletHederaEd25519, + "41f7d3cf6db29968d2ec6b74cc70530ebeb5adb65ee9196be69f44b9184e10d1", + 1, + "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup", + ) + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_evm_address(self): + self.address_tester( + btcrseed.WalletHederaEd25519, + [ + "0x000000000000000000000000000000000098d10f", + "f5b22efd7869364a2b4af38c91324427ef1d291a", + ], + 1, + "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup", + addr_start_index=10014991, + ) + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_account_id(self): + self.address_tester( + btcrseed.WalletHederaEd25519, + [ + "0.0.10014991", + "f5b22efd7869364a2b4af38c91324427ef1d291a", + ], + 1, + "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup", + addr_start_index=10014991, + ) + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_account_id_with_checksum(self): + self.address_tester( + btcrseed.WalletHederaEd25519, + [ + "0.0.10014991-coiln", + "f5b22efd7869364a2b4af38c91324427ef1d291a", + ], + 1, + "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup", + addr_start_index=10014991, + ) + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_infers_start_index(self): + wallet = btcrseed.WalletHederaEd25519.create_from_params( + addresses=["0.0.10014995", "0.0.10014991"], + address_limit=1, + ) + + self.assertEqual(wallet._address_start_index, 10014991) + self.assertEqual((wallet._hedera_shard, wallet._hedera_realm), (0, 0)) + + @skipUnless(can_load_bip_utils, "requires bip_utils") + def test_hedera_ed25519_preserves_manual_start_index(self): + wallet = btcrseed.WalletHederaEd25519.create_from_params( + addresses=["0.0.10014991", "0.0.10014995"], + address_limit=1, + address_start_index=42, + ) + + self.assertEqual(wallet._address_start_index, 42) + + def test_walletripple_bip44(self): + self.address_tester(btcrseed.WalletRipple, "rJGNUmwiYDwXEsLzUFV9njhP3syrDvA6hs", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop") + + def test_walletstacks_bip44(self): + self.address_tester(btcrseed.WalletStacks, "SP11KHP08F4KQ06MWESBY48VMXRBK5NB0FSCRP779", 2, + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + def test_walletvertcoin_addr_bip44(self): + self.address_tester(btcrseed.WalletVertcoin, "VwrYFHeKbneYZdkPWTpXsUs3ZQ4ERan9tG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletvertcoin_addr_bip49(self): + self.address_tester(btcrseed.WalletVertcoin, "33DUUsVoodofnbrxFhqCSBkKaqjCHzQyYU", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletvertcoin_addr_bip84(self): + self.address_tester(btcrseed.WalletVertcoin, "vtc1q4r6d6w0xnd4t2rlj8njcl7m7a9k0ezk9rjnc77", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletmonacoin_addr_bip44(self): + self.address_tester(btcrseed.WalletMonacoin, "M9BBjQC5vWktdbrfZZorybzUY75wtNB7JC", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletmonacoin_addr_bip49(self): + self.address_tester(btcrseed.WalletMonacoin, "P8gv2vrMyVhDdjHgJf6yxH3vGarM9fCZ9f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletmonacoin_addr_bip84(self): + self.address_tester(btcrseed.WalletMonacoin, "monacoin1q9v93ngm8srxtq7lwzypehax7xvewh2vch68m2f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletdigibyte_addr_bip44(self): + self.address_tester(btcrseed.WalletDigiByte, "D8uui9mGXztcpZy5t5jWpSimCCyEDjYRHY", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current") + + def test_walletdigibyte_addr_bip49(self): + self.address_tester(btcrseed.WalletDigiByte, "SjM4p9vWB7GvsiNMgyZef67SJz3SgmPwhj", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current") + + def test_walletdigibyte_addr_bip84(self): + self.address_tester(btcrseed.WalletDigiByte, "dgb1qmtpcmpt5amuvvwvpelh220ec2ck7q4prsy2tqy", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current") + + def test_walletbch_addr_bip44_CashAddr(self): + self.address_tester(btcrseed.WalletBCH, "bitcoincash:qrdupm96x04u3ssjnuj7lpy7adt9y34p5vzh95y0y7", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletbch_addr_bip44_CashAddr_NoPrefix(self): + self.address_tester(btcrseed.WalletBCH, "qrdupm96x04u3ssjnuj7lpy7adt9y34p5vzh95y0y7", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletdash_addr_bip44(self): + self.address_tester(btcrseed.WalletDash, "XkRVBsXz1UG7LP48QKT4ZEbyUS54oRjYpM", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletdogecoin_addr_bip44(self): + self.address_tester(btcrseed.WalletDogecoin, "DANb1e9B2WtHJNDJUsiu1fTrtAzGJhqkPa", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_walletgroestlecoin_addr_bip44(self): + self.address_tester(btcrseed.WalletGroestlecoin, "FqGMQvKCb2idGbDd6SUBFuugynXRACEzuQ", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_walletgroestlecoin_addr_bip49(self): + self.address_tester(btcrseed.WalletGroestlecoin, "384swZndJ7CjZhqx7JL29Whnommy9s9phF", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_walletgroestlecoin_addr_bip84(self): + self.address_tester(btcrseed.WalletGroestlecoin, "grs1qy9qewq3x843gss8z6h22gmc03gfzuuj7hz505a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform " + "advice pen praise soap lizard festival connect baby") + + def test_walletzilliqa_addr_legacy(self): + self.address_tester(btcrseed.WalletZilliqa, "0x61cac31f637fa3a7e7b0984efe930cddf2070171", 3, + "perfect pottery lens service hurry wood danger cannon empower know cloth buffalo") + + def test_walletzilliqa_addr_bech32(self): + self.address_tester(btcrseed.WalletZilliqa, "zil1v89vx8mr07360easnp80aycvmheqwqt3880guh", 3, + "perfect pottery lens service hurry wood danger cannon empower know cloth buffalo") + + def test_walletlitecoin_addr_bip44(self): + self.address_tester(btcrseed.WalletLitecoin, "LhHbcBk84JpB41otvD7qqWzyGgyr8yDJ2a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate" + " sister uniform advice pen praise soap lizard festival connect baby") + + def test_walletlitecoin_addr_atomic(self): + self.address_tester(btcrseed.WalletLitecoin, "LZzJsDgidaRQXicyd5Rb2LbRZd5SR6QqrS", 2, + "keen term crouch physical together vital oak predict royal quantum tomorrow chunk") + + def test_walletlitecoin_addr_bip49(self): + self.address_tester(btcrseed.WalletLitecoin, "MQT8szKNYyJU1hUPLnsfCYXkqLQbTewsj9", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate" + " sister uniform advice pen praise soap lizard festival connect baby") + + def test_walletlitecoin_addr_bip84(self): + self.address_tester(btcrseed.WalletLitecoin, "ltc1q2dzc0u75p5aule30w5t5hjdzhgh2kmgqyh2t0f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate" + " sister uniform advice pen praise soap lizard festival connect baby") + + def test_walletbch_BCH_Unsplit(self): + self.address_tester(btcrseed.WalletBCH, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop") + + def test_walletbch(self): + self.address_tester(btcrseed.WalletBCH, "bitcoincash:qz7753xzek843j50cgtc526wdmlpm5v5eyt92gznrt", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop") + + def test_singlexpubaddress_atomic_eth(self): + self.address_tester(wallet_type = btcrseed.WalletEthereum, + the_address = "0xfa5E4Bb54b4f45841140b2EF03198EBA64ABa9DD", + the_address_limit = 1, + correct_mnemonic = "keen term crouch physical together vital oak predict royal quantum tomorrow chunk", + checksinglexpubaddress = True) + + def test_singlexpubaddress_mybitcoinwallet_single_legacy(self): + self.address_tester(wallet_type = btcrseed.WalletBIP39, + the_address = "1EaGSR7uWp2hok3jTtNypjUuV3G4YyMxgt", + the_address_limit = 1, + correct_mnemonic = "spatial stereo thrive reform shallow blouse minimum foster eagle game answer worth size stumble theme crater bounce stay extra duty man weather awesome search", + checksinglexpubaddress = True) + + def test_singlexpubaddress_mybitcoinwallet_single_bech32(self): + self.address_tester(wallet_type = btcrseed.WalletBIP39, + the_address = "bc1qymj3j8qkyk8ukhczg80tm0jyfh4rzxyqnngsqh", + the_address_limit = 1, + correct_mnemonic = "spatial stereo thrive reform shallow blouse minimum foster eagle game answer worth size stumble theme crater bounce stay extra duty man weather awesome search", + checksinglexpubaddress = True) + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_MultiverseX(self): + self.address_tester(btcrseed.WalletMultiversX, "erd16jn439kmwgqj9j0xjnwk2swg0p7j2jrnvpp4p7htc7wypnx27ttqe9l98m", 2, + "agree process hard hello artefact govern obtain wedding become robust fish bar alcohol about speak unveil mind bike shift latin pole base ugly artefact") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Tron(self): + self.address_tester(btcrseed.WalletTron, "TLDrhbxkBGa1doxtez2bEx4iQ3DmKg9UdM", 2, + "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Cosmos(self): + self.address_tester(btcrseed.WalletCosmos, "cosmos1t47f66q50ft66ypwn9x7laeectyvh23aqedfmq", 1, + "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Cosmos_NYM(self): + self.address_tester(btcrseed.WalletCosmos, "n1g35xm8264cw8gay757ctyqeuqyc0st2muvktx9", 1, + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Cosmos_GravityBridge(self): + self.address_tester(btcrseed.WalletCosmos, "gravity1g35xm8264cw8gay757ctyqeuqyc0st2m2cn3ug", 1, + "ocean hidden kidney famous rich season gloom husband spring convince attitude boy") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_SecretNetworkNew(self): + self.address_tester(btcrseed.WalletSecretNetworkNew, "secret1788gts0a69v5fckayds5cz9n3y4zfmtqct5qxc", 1, + "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_SecretNetworkOld(self): + self.address_tester(btcrseed.WalletSecretNetworkOld, "secret1t47f66q50ft66ypwn9x7laeectyvh23azueqxu", 1, + "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season") + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Tezos(self): + self.address_tester(btcrseed.WalletTezos, "tz1UXZKEq7SsveAi1jpKBeigcdoFHmVopHKq", 1, + "cake return enhance slender swap butter code cram fashion warm uphold adapt swarm slight misery enhance almost ability artefact lava sugar regret example lake") + + + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Avalanche(self): + self.address_tester(btcrseed.WalletAvalanche, "X-avax1mpf7j47w7t3xt32g3vzm0zvzy35d7t5twv2ax3", 2, + "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Solana(self): + self.address_tester(btcrseed.WalletSolana, "HDnS8HELzQ4oef1TLzxyifhiWgmnWALvJXBjkva9JMyU", 2, + "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_Stellar(self): + self.address_tester(btcrseed.WalletStellar, "GAV7E2PHIPDS3PM3BWN6DIHC623ONTZUDGXPJ7TT3EREYJRLTMENCK6Z", 2, + "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season") + + @skipUnless(can_load_slip10, "requires slip10 module") + @skipUnless(can_load_stellarsdk, "requires stellar_sdk module") + def test_WalletXLM(self): + self.address_tester(btcrseed.WalletXLM, "GAV7E2PHIPDS3PM3BWN6DIHC623ONTZUDGXPJ7TT3EREYJRLTMENCK6Z", 2, + "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season") + + @skipUnless(can_load_PyCryptoHDWallet, "requires Py_Crypto_HD_Wallet module") + def test_WalletPyCryptoHDWallet_PolkadotSubstrate(self): + self.address_tester(btcrseed.WalletPolkadotSubstrate, "13SsWBQSN6Se72PCaMa6huPXEosRNUXN3316yAycS6rpy3tK", 1, + "toilet assume drama keen dust warrior stick quote palace imitate music disease") + + @skipUnless(can_load_nacl, "requires nacl module") + @skipUnless(can_load_bitstring, "requires bitstring module") + def test_Helium_mobile(self): + self.address_tester(btcrseed.WalletHelium, "13hP2Vb1XVcMYrVNdwUW4pF3ZDj8CnET92zzUHqYp7DxxzVASbB", 1, + "arm hundred pride female steel describe tip physical weapon peace write advice") + + @skipUnless(can_load_nacl, "requires nacl module") + @skipUnless(can_load_bitstring, "requires bitstring module") + def test_Helium_bip39(self): + self.address_tester(btcrseed.WalletHelium, "14qWwWH3JZcYkqvbmziU4J12nKQPabp5GkKUmmZi4n94YQ7LbwS", 1, + "rather ensure noble bargain armor hold embody friend ahead senior earth result") + + # Test to ensure that bundled derivation path files work correctly + def test_pathfile_BTC_Electrum_Legacy(self): + self.address_tester(btcrseed.WalletElectrum2, "LcgWmmHWX3FdysFCFaNGDTywQBcCepvrQ8", 5, + "fiber bubble warm green banana blood program ship barrel tennis cigar song", + pathlist_file="Electrum.txt", + expected_len=12) + + def test_pathfile_BTC_Electrum_Segwit(self): + self.address_tester(btcrseed.WalletElectrum2, "bc1qztc99re7ml7hv4q4ds3jv29w7u4evwqd6t76kz", 5, + "first focus motor give search custom grocery suspect myth popular trigger praise", + pathlist_file="Electrum.txt", + expected_len=12) + + def test_pathfile_BTC_Electrum_Cakewallet(self): + self.address_tester(btcrseed.WalletElectrum2, "bc1qdffmstsyhg36z3quqr36e0qupn28eazwnctpa7", 5, + "index convince purpose truly warfare super vendor cheap maid juice runway normal virus toddler invite hammer trumpet health heavy relax degree glide unveil fury", + pathlist_file="Electrum.txt", + expected_len=24) + + def test_pathfile_BTC_BRD(self): + self.address_tester(btcrseed.WalletBIP39, "1FpWokPArYJKkWWiTqsnoVaFJL4PM3Nqdf", 2, + "talk swamp tool right wide vital midnight cushion fiber blouse field transfer", + pathlist_file="BTC.txt") + + def test_pathfile_BTC_BIP44(self): + self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + pathlist_file="BTC.txt") + + def test_pathfile_BTC_BIP49(self): + self.address_tester(btcrseed.WalletBIP39, "3NiRFNztVLMZF21gx6eE1nL3Q57GMGuunG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="BTC.txt") + + def test_pathfile_BTC_BIP84(self): + self.address_tester(btcrseed.WalletBIP39, "bc1qv87qf7prhjf2ld8vgm7l0mj59jggm6ae5jdkx2", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="BTC.txt") + + def test_pathfile_LTC_BIP44(self): + self.address_tester(btcrseed.WalletBIP39, "LhHbcBk84JpB41otvD7qqWzyGgyr8yDJ2a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="LTC.txt") + + def test_pathfile_LTC_Atomic(self): + self.address_tester(btcrseed.WalletBIP39, "LZzJsDgidaRQXicyd5Rb2LbRZd5SR6QqrS", 2, + "keen term crouch physical together vital oak predict royal quantum tomorrow chunk", + pathlist_file="LTC.txt") + + def test_pathfile_LTC_BIP49(self): + self.address_tester(btcrseed.WalletBIP39, "MQT8szKNYyJU1hUPLnsfCYXkqLQbTewsj9", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="LTC.txt") + + def test_pathfile_LTC_BIP84(self): + self.address_tester(btcrseed.WalletBIP39, "ltc1q2dzc0u75p5aule30w5t5hjdzhgh2kmgqyh2t0f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="LTC.txt") + + @unittest.skipUnless(can_load_keccak(), "requires pycryptodome") + def test_pathfile_Eth_Coinomi(self): + self.address_tester(btcrseed.WalletEthereum, "0xE16fCCbBa5EC2C2e4584A846ce3b77a6F37E863c", 2, + "talk swamp tool right wide vital midnight cushion fiber blouse field transfer", + pathlist_file="ETH.txt") + + @unittest.skipUnless(can_load_keccak(), "requires pycryptodome") + def test_pathfile_Eth_Default(self): + self.address_tester(btcrseed.WalletEthereum, "0x1a05a75E4041eFB46A34F208b677F82C079197D8", 2, + "talk swamp tool right wide vital midnight cushion fiber blouse field transfer", + pathlist_file="ETH.txt") + + @unittest.skipUnless(can_load_staking_deposit(), "requires staking-deposit and py_ecc") + def test_eth_validator(self): + self.address_tester(btcrseed.WalletEthereumValidator, "94172eb62472af0fb61dc8f66cde031d06b7bd39bda86dd2213b2eb283f710d16f38009bc2e03dc967b2c3548dd4f73f", 2, + "spatial evolve range inform burst screen session kind clap goat force sort") + + def test_pathfile_BCH_Unsplit(self): + self.address_tester(btcrseed.WalletBIP39, "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + pathlist_file="BCH.txt") + + def test_pathfile_BCH(self): + self.address_tester(btcrseed.WalletBIP39, "bitcoincash:qz7753xzek843j50cgtc526wdmlpm5v5eyt92gznrt", 2, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + pathlist_file="BCH.txt") + + def test_pathfile_bip44_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "VwrYFHeKbneYZdkPWTpXsUs3ZQ4ERan9tG", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="VTC.txt") + + def test_pathfile_bip49_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "33DUUsVoodofnbrxFhqCSBkKaqjCHzQyYU", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="VTC.txt") + + def test_pathfile_bip84_addr_VTC(self): + self.address_tester(btcrseed.WalletBIP39, "vtc1q4r6d6w0xnd4t2rlj8njcl7m7a9k0ezk9rjnc77", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="VTC.txt") + + def test_pathfile_bip44_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "M9BBjQC5vWktdbrfZZorybzUY75wtNB7JC", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="MONA.txt") + + def test_pathfile_bip49_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "P8gv2vrMyVhDdjHgJf6yxH3vGarM9fCZ9f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="MONA.txt") + + def test_pathfile_bip84_addr_MONA(self): + self.address_tester(btcrseed.WalletBIP39, "monacoin1q9v93ngm8srxtq7lwzypehax7xvewh2vch68m2f", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="MONA.txt") + + def test_bip44_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "D8uui9mGXztcpZy5t5jWpSimCCyEDjYRHY", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + pathlist_file="DGB.txt") + + def test_pathfile_bip49_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "SjM4p9vWB7GvsiNMgyZef67SJz3SgmPwhj", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + pathlist_file="DGB.txt") + + def test_pathfile_bip84_addr_DGB(self): + self.address_tester(btcrseed.WalletBIP39, "dgb1qmtpcmpt5amuvvwvpelh220ec2ck7q4prsy2tqy", 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + pathlist_file="DGB.txt") + + def test_pathfile_bip44_addr_DASH(self): + self.address_tester(btcrseed.WalletBIP39, "XkRVBsXz1UG7LP48QKT4ZEbyUS54oRjYpM", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="DASH.txt") + + def test_pathfile_bip44_addr_DOGE(self): + self.address_tester(btcrseed.WalletBIP39, "DANb1e9B2WtHJNDJUsiu1fTrtAzGJhqkPa", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="DOGE.txt") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_pathfile_bip44_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "FqGMQvKCb2idGbDd6SUBFuugynXRACEzuQ", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="GRS.txt") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + @skipUnless(can_load_bundled_bitcoinlib_mod, "Unable to load modified bitcoinlib in this environment") + def test_pathfile_bip49_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "384swZndJ7CjZhqx7JL29Whnommy9s9phF", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="GRS.txt") + + @skipUnless(can_load_groestlcoin_hash, "requires groestlcoin_hash") + def test_pathfile_bip84_addr_GRS(self): + self.address_tester(btcrseed.WalletBIP39, "grs1qy9qewq3x843gss8z6h22gmc03gfzuuj7hz505a", 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + pathlist_file="GRS.txt") + + def test_bip44_addr_en(self): + self.address_tester(btcrseed.WalletBIP39, "14phjB1jQKNvXnuq16f7rMe2uz87j8mxoq", 2, + "juice exchange session account protect pottery immense satisfy wood arm old hello", ) + + def test_bip44_addr_en_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "14phjB1jQKNvXnuq16f7rMe2uz87j8mxoq", 2, + "juic exch sess acco prot pott imme sati wood arm old hell", ) + + def test_bip44_addr_en(self): + self.address_tester(btcrseed.WalletBIP39, "1N1nFiNA7fXAoRNXfLZTQDtbNCoZKMV3hF", 2, + "kilo equipo reducir academia pasta pájaro imitar queja voraz ámbito nevar hebra", ) + + def test_bip44_addr_en_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "1N1nFiNA7fXAoRNXfLZTQDtbNCoZKMV3hF", 2, + "kilo equi redu acad past pája imit quej vora ámbi neva hebr", ) + + def test_bip44_addr_fr(self): + self.address_tester(btcrseed.WalletBIP39, "1E59dAh2q7mbJM5eu1w3DojN9m71P5vfyw", 2, + "harmonie effectif pulpe abrupt opinion observer géranium pouce vivipare amidon mercredi fortune", ) + + def test_bip44_addr_fr_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "1E59dAh2q7mbJM5eu1w3DojN9m71P5vfyw", 2, + "harm effe pulp abru opin obse géra pouc vivi amid mercr fort", ) + + def test_bip44_addr_it(self): + self.address_tester(btcrseed.WalletBIP39, "14DiUcMBtnj9Hzn1j6rVEHr4sJGaf6uydm", 2, + "mangiare fascia scatenare achille quasi privato letterale salivare volpe analista partire intasato", ) + + def test_bip44_addr_it_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "14DiUcMBtnj9Hzn1j6rVEHr4sJGaf6uydm", 2, + "mang fasc scat achi quas priv lett sali volp anal part inta", ) + + def test_bip44_addr_cs(self): + self.address_tester(btcrseed.WalletBIP39, "13q5tRryW8FZ1qhrS8AmN5sxqa8ntEWLEa", 2, + "jahoda budka podepsat sledovat zubr heslo maminka humr bezmoc trubec vibrace povaha", ) + + def test_bip44_addr_cs_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "13q5tRryW8FZ1qhrS8AmN5sxqa8ntEWLEa", 2, + "jaho budk pode sled zubr hesl mami humr bezm trub vibr pova", ) + + def test_bip44_addr_ja(self): + self.address_tester(btcrseed.WalletBIP39, "18yGPGc5TvjmancDMTnPNCFyjMJRrUXZnZ", 2, + "くやくしょ いふく つよい はいち わかめ ぎじたいけん しのぐ くさき いきもの ふりる みがく でんりょく", ) + + def test_bip44_addr_ja(self): + self.address_tester(btcrseed.WalletBIP39, "18yGPGc5TvjmancDMTnPNCFyjMJRrUXZnZ", 2, + "くやくしょ いふく つよい はいち わかめ ぎじたいけん しのぐ くさき いきもの ふりる みがく でんりょく", ) + + def test_bip44_addr_zh_hans(self): + self.address_tester(btcrseed.WalletBIP39, "1H47vZSaZ25LqcJSmK6eZokgWL4cXfJ248", 2, + "端 悉 瘦 任 鸿 纠 诸 罩 斤 与 语 柔", ) + + def test_bip44_addr_zh_hans(self): + self.address_tester(btcrseed.WalletBIP39, "1Hc8Pf86Zy52qY1Pp2fdSvvewfXcp1k6CM", 2, + "退 命 倆 冠 扇 往 雛 句 振 鉤 登 葡", ) + + def test_bip44_addr_ko(self): + self.address_tester(btcrseed.WalletBIP39, "1BDvMRDwM9ht5SrooQ4uVKhwYAyz5Z2e64", 2, + "암시 관념 형제 차선 칠십 마누라 학습 성별 수면 횡단보도 위법 한눈", ) + + def test_bip44_addr_pt(self): + self.address_tester(btcrseed.WalletBIP39, "1QmUCi3yv1A8ZWd3Xd14D5dVKdCEQruKi", 2, + "fanfarra tubular boxeador almofada quarto beldade campanha gasoduto arenito pasmo roseira crua", ) + + def test_bip44_addr_pt_firstfour(self): + self.address_tester(btcrseed.WalletBIP39, "1QmUCi3yv1A8ZWd3Xd14D5dVKdCEQruKi", 2, + "fanf tubu boxe almo quar beld camp gaso aren pasm rose crua", ) + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_BIP39_BTC_OpenCL_Brute(self): + the_address = "1AiAYaVJ7SCkDeNqgFz7UDecycgzb6LoT3" + the_address_limit = 2 + correct_mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + wallet = btcrseed.WalletBIP39.create_from_params(addresses=[the_address], address_limit=the_address_limit) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletBIP39._return_verified_password_or_false_opencl(wallet, + (wrong_mnemonic_iter.__next__(), + wrong_mnemonic_iter.__next__())), + (False, 2)) + self.assertEqual(btcrseed.WalletBIP39._return_verified_password_or_false_opencl(wallet, + (wrong_mnemonic_iter.__next__(), + correct_mnemonic_ids, + wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + # Make sure the address_limit is respected (note the "the_address_limit-1" below) + wallet = btcrseed.WalletBIP39.create_from_params(addresses=[the_address], address_limit=the_address_limit - 1) + wallet.config_mnemonic(correct_mnemonic) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletBIP39._return_verified_password_or_false_opencl(wallet, + (correct_mnemonic_ids,)), + (False, 1)) + + del wallet + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_BIP39_Eth_OpenCL_Brute(self): + the_address = "0x38b132519c151f602964Bf6bF348aF6C92d35d28" + the_address_limit = 2 + correct_mnemonic = "certain come keen collect slab gauge photo inside mechanic deny leader drop" + wallet = btcrseed.WalletEthereum.create_from_params(addresses=[the_address], address_limit=the_address_limit) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletEthereum._return_verified_password_or_false_opencl(wallet, + ( + wrong_mnemonic_iter.__next__(), + wrong_mnemonic_iter.__next__())), + (False, 2)) + self.assertEqual(btcrseed.WalletEthereum._return_verified_password_or_false_opencl(wallet, + ( + wrong_mnemonic_iter.__next__(), + correct_mnemonic_ids, + wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + # Make sure the address_limit is respected (note the "the_address_limit-1" below) + wallet = btcrseed.WalletEthereum.create_from_params(addresses=[the_address], + address_limit=the_address_limit - 1) + wallet.config_mnemonic(correct_mnemonic) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletEthereum._return_verified_password_or_false_opencl(wallet, + (correct_mnemonic_ids,)), + (False, 1)) + + del wallet + + @skipUnless(has_any_opencl_devices, "requires OpenCL and a compatible device") + def test_Electrum_OpenCL_Brute(self): + the_address = "bc1qztc99re7ml7hv4q4ds3jv29w7u4evwqd6t76kz" + the_address_limit = 5 + correct_mnemonic = "first focus motor give search custom grocery suspect myth popular trigger praise" + wallet = btcrseed.WalletElectrum2.create_from_params(addresses=[the_address], address_limit=the_address_limit) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic, expected_len=12) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletElectrum2._return_verified_password_or_false_opencl(wallet, + ( + wrong_mnemonic_iter.__next__(), + wrong_mnemonic_iter.__next__())), + (False, 2)) + self.assertEqual(btcrseed.WalletElectrum2._return_verified_password_or_false_opencl(wallet, + ( + wrong_mnemonic_iter.__next__(), + correct_mnemonic_ids, + wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + # Make sure the address_limit is respected (note the "the_address_limit-1" below) + wallet = btcrseed.WalletElectrum2.create_from_params(addresses=[the_address], + address_limit=the_address_limit - 1) + wallet.config_mnemonic(correct_mnemonic, expected_len=12) + + btcrecover.opencl_helpers.auto_select_opencl_platform(wallet) + + btcrecover.opencl_helpers.init_opencl_contexts(wallet) + + self.assertEqual(btcrseed.WalletElectrum2._return_verified_password_or_false_opencl(wallet, + (correct_mnemonic_ids,)), + (False, 1)) + + del wallet + + +class OpenCL_Tests(unittest.TestSuite): + def __init__(self): + super(OpenCL_Tests, self).__init__() + self.addTest(unittest.defaultTestLoader.loadTestsFromNames(("TestRecoveryFromAddress." + method_name + for method_name in ( + "test_BIP39_BTC_OpenCL_Brute", + "test_BIP39_Eth_OpenCL_Brute", + "test_Electrum_OpenCL_Brute")), + module=sys.modules[__name__] + )) + + +class TestAddressSet(unittest.TestCase): + HASH_BYTES = 1 + TABLE_LEN = 2 ** (8 * HASH_BYTES) + BYTES_PER_ADDR = AddressSet(1)._bytes_per_addr + + def test_add(self): + aset = AddressSet(self.TABLE_LEN) + addr = "".join(chr(b) for b in range(20)) + self.assertNotIn(addr, aset) + aset.add(addr) + self.assertIn(addr, aset) + self.assertEqual(len(aset), 1) + + def collision_tester(self, aset, addr1, addr2): + aset.add(addr1) + self.assertIn(addr1, aset) + self.assertNotIn(addr2, aset) + self.assertEqual(len(aset), 1) + aset.add(addr2) + self.assertIn(addr1, aset) + self.assertIn(addr2, aset) + self.assertEqual(len(aset), 2) + return aset + + # + def test_collision(self): + aset = AddressSet(self.TABLE_LEN) + # the last HASH_BYTES (1) bytes are the "hash", and only the next BYTES_PER_ADDR (8) rightmost bytes are stored + addr1 = "".join(chr(b) for b in range(20)) + addr2 = addr1.replace(chr(20 - self.HASH_BYTES - self.BYTES_PER_ADDR), "\0") # the leftmost byte that's stored + self.collision_tester(aset, addr1, addr2) + + # + def test_collision_fail(self): + aset = AddressSet(self.TABLE_LEN) + # the last 1 (HASH_BYTES) bytes are the "hash", and only the next 8 (BYTES_PER_ADDR) rightmost bytes are stored + addr1 = "".join(chr(b) for b in range(20)) + addr2 = addr1.replace(chr(20 - self.HASH_BYTES - self.BYTES_PER_ADDR - 1), + "\0") # the rightmost byte not stored + self.assertRaises(unittest.TestCase.failureException, self.collision_tester, aset, addr1, addr2) + self.assertEqual(len(aset), 1) + + def test_null(self): + aset = AddressSet(self.TABLE_LEN) + addr = 20 * "\0" + aset.add(addr) + self.assertNotIn(addr, aset) + self.assertEqual(len(aset), 0) + + # very unlikely to fail, though it isn't deterministic, so may fail somtimes. + # If it fails repeatedly, there's probably a significant problem + def test_false_positives(self): + aset = AddressSet(1024, bytes_per_addr=8) + rand_byte_count = aset._hash_bytes + aset._bytes_per_addr + nonrand_prefix = (20 - rand_byte_count) * "\0" + for i in range(aset._max_len): + aset.add(nonrand_prefix + "".join(chr(random.randrange(256)) for i in range(rand_byte_count))) + for i in range(8192): + self.assertNotIn( + nonrand_prefix + "".join(chr(random.randrange(256)) for i in range(rand_byte_count)), + aset) + + def test_file(self): + aset = AddressSet(self.TABLE_LEN) + addr = "".join(chr(b) for b in range(20)) + aset.add(addr) + dbfile = tempfile.TemporaryFile() + aset.tofile(dbfile) + dbfile.seek(0) + aset = AddressSet.fromfile(dbfile) + self.assertTrue(dbfile.closed) # should be closed by AddressSet in read-only mode + self.assertIn(addr, aset) + self.assertEqual(len(aset), 1) + + def test_file_update(self): + aset = AddressSet(self.TABLE_LEN) + dbfile = tempfile.NamedTemporaryFile(delete=False) + try: + aset.tofile(dbfile) + dbfile.seek(0) + aset = AddressSet.fromfile(dbfile, mmap_access=mmap.ACCESS_WRITE) + addr = "".join(chr(b) for b in range(20)) + aset.add(addr) + aset.close() + self.assertTrue(dbfile.closed) + dbfile = open(dbfile.name, "rb") + aset = AddressSet.fromfile(dbfile) + self.assertIn(addr, aset) + self.assertEqual(len(aset), 1) + finally: + aset.close() + dbfile.close() + os.remove(dbfile.name) + + def test_pickle_mmap(self): + aset = AddressSet(self.TABLE_LEN) + addr = "".join(chr(b) for b in range(20)) + aset.add(addr) + dbfile = tempfile.NamedTemporaryFile(delete=False) + try: + aset.tofile(dbfile) + dbfile.seek(0) + aset = AddressSet.fromfile(dbfile) # now it's an mmap + pickled = pickle.dumps(aset, protocol=pickle.HIGHEST_PROTOCOL) + aset.close() # also closes the file + aset = pickle.loads(pickled) + self.assertIn(addr, aset) + self.assertEqual(len(aset), 1) + finally: + aset.close() + dbfile.close() + os.remove(dbfile.name) + + +class TestRecoveryFromAddressDB(unittest.TestCase): + + def addressdb_tester(self, wallet_type, the_address_limit, correct_mnemonic, test_path, test_address_db, **kwds): + assert the_address_limit > 1 + + # Check to see if the AddressDB exists (and if not, skip) + if not os.path.isfile("./btcrecover/test/test-addressdbs/" + test_address_db): + raise unittest.SkipTest("requires ./btcrecover/test/test-addressdbs/" + test_address_db) + + # Test Basic BIP44 AddressDB Search + addressdb = AddressSet.fromfile(open("./btcrecover/test/test-addressdbs/" + test_address_db, "rb"), + preload=False) + wallet = wallet_type.create_from_params(hash160s=addressdb, address_limit=the_address_limit, path=[test_path]) + + # Convert the mnemonic string into a mnemonic_ids_guess + wallet.config_mnemonic(correct_mnemonic, **kwds) + correct_mnemonic_ids = btcrseed.mnemonic_ids_guess + + # Creates wrong mnemonic id guesses + wrong_mnemonic_iter = wallet.performance_iterator() + + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), wrong_mnemonic_iter.__next__())), (False, 2)) + self.assertEqual(wallet.return_verified_password_or_false( + (wrong_mnemonic_iter.__next__(), correct_mnemonic_ids, wrong_mnemonic_iter.__next__())), + (correct_mnemonic_ids, 2)) + + # Make sure the address_limit is respected (note the "the_address_limit-1" below) + wallet = wallet_type.create_from_params(hash160s=addressdb, address_limit=the_address_limit - 1, path=[test_path]) + wallet.config_mnemonic(correct_mnemonic, **kwds) + self.assertEqual(wallet.return_verified_password_or_false( + (correct_mnemonic_ids,)), (False, 1)) + + # BCH AddressDB Tests + # m/44'/145'/0'/0/1 bitcoincash:qrdupm96x04u3ssjnuj7lpy7adt9y34p5vzh95y0y7 + def test_addressdb_bip44_bch(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/145'/0'/0", "addresses-BCH-Test.db") + + # BCH AddressDB + BIP39 Passphrase Test + # m/44'/145'/0'/0/1 bitcoincash:qprwa49yg44mj7geswgdmlylkp9pff32c5kr8a2wq3 + def test_addressdb_bip44_bch_passphrase(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/145'/0'/0", "addresses-BCH-Test.db", passphrases=[u"youtube",]) + + # BTC AddressDB Tests + # m/44'/0'/1'/0/1 1Bi3vKepTDmrRYC59WjaGDVDrg8qPsrc31 + def test_addressdb_bip44_btc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/0'/1'/0", "addresses-BTC-Test.db") + + # m/49'/0'/1'/0/1 3GHFddEy3hPdwqh6gsTRfAZX83FfHKDNqF + def test_addressdb_bip49_btc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/49'/0'/1'/0", "addresses-BTC-Test.db") + + # m/84'/0'/1'/0/1 bc1ql4vgz4f8qef29x224935yxtun44prgr3eh06jh + def test_addressdb_bip84_btc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/84'/0'/1'/0", "addresses-BTC-Test.db") + + # m/86'/0'/0'/0/1 bc1pqx93u4lpl38fkqe7z89tuswahzug0zvtc4jzpecw0c420n0n9wlq4euhxp + def test_addressdb_bip86_btc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "swing wedding strike accuse walk reduce immense blur rotate south myself memory", + "m/86'/0'/0'/0", "addresses-BTC-P2TR.db") + + # LTC AddressDB Tests + # m/44'/2'/1'/0/1 LgXiUTLMKcoaqvUPMNJo1RmpAGFMHD75tr + def test_addressdb_bip44_ltc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/2'/1'/0", "addresses-LTC-Test.db") + + # m/49'/2'/1'/0/1 MQ9ucyhhaEncRmdL3uq9XhzDre37mvFTCf + def test_addressdb_bip49_ltc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/49'/2'/1'/0", "addresses-LTC-Test.db") + + # m/84'/2'/1'/0/1 ltc1qgpn2phk8c7k966xjufrrll59qa8wnvnx68jtt6 + def test_addressdb_bip84_ltc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/84'/2'/1'/0", "addresses-LTC-Test.db") + + # VTC AddressDB Tests + # m/44'/28'/1'/0/1 VuMksxrDy48HZr15WR3Lwn6yvLKhuHgEUc + def test_addressdb_bip44_vtc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/28'/1'/0", "addresses-VTC-Test.db") + + # m/49'/28'/1'/0/1 3LSAzLG2WuzHABHoi3FiGvv4BqvvwnADCq + def test_addressdb_bip49_vtc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/49'/28'/1'/0", "addresses-VTC-Test.db") + + # m/84'/28'/1'/0/1 vtc1qpuw3nh0xfa4tcvxp3q8dc2cqhqtgsf4xg6r273 + def test_addressdb_bip84_vtc(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/84'/28'/1'/0", "addresses-VTC-Test.db") + + # MONA AddressDB Tests + # m/44'/22'/1'/0/1 MPEbQUqKXPf8A9TCQTiGPhMcRBPwySroHg + def test_addressdb_bip44_mona(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/44'/22'/1'/0", "addresses-MONA-Test.db") + + # m/49'/22'/1'/0/1 PNJmRN936aqgzuyXaRKiEHsy5mHKw4QWqn + def test_addressdb_bip49_mona(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/49'/22'/1'/0", "addresses-MONA-Test.db") + + # m/84'/22'/1'/0/1 mona1qx9kllhxc4u4evjdhyejsseyqntjursxtewdcmm + def test_addressdb_bip84_mona(self): + self.addressdb_tester(btcrseed.WalletBIP39, 2, + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby", + "m/84'/22'/1'/0", "addresses-MONA-Test.db") + + # DGB AddressDB Tests + # m/44'/20'/0'/4 D8uui9mGXztcpZy5t5jWpSimCCyEDjYRHY + def test_addressdb_bip44_dgb(self): + self.addressdb_tester(btcrseed.WalletBIP39, 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + "m/44'/20'/0'/0", "addresses-DGB-Test.db") + + # m/49'/20'/0'/4 SjM4p9vWB7GvsiNMgyZef67SJz3SgmPwhj + def test_addressdb_bip49_dgb(self): + self.addressdb_tester(btcrseed.WalletBIP39, 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + "m/49'/20'/0'/0", "addresses-DGB-Test.db") + + # m/84'/20'/0'/4 dgb1qmtpcmpt5amuvvwvpelh220ec2ck7q4prsy2tqy + def test_addressdb_bip84_dgb(self): + self.addressdb_tester(btcrseed.WalletBIP39, 5, + "barrel tag debate reopen federal fee soda fog twelve garage sweet current", + "m/84'/20'/0'/0", "addresses-DGB-Test.db") + + +class TestSeedTypos(unittest.TestCase): + XPUB = "xpub6BgCDhMefYxRS1gbVbxyokYzQji65v1eGJXGEiGdoobvFBShcNeJt97zoJBkNtbASLyTPYXJHRvkb3ahxaVVGEtC1AD4LyuBXULZcfCjBZx" + + def seed_tester(self, the_mpk, correct_mnemonic, mnemonic_guess, typos=None, big_typos=0, mnemonic_length=None): + correct_mnemonic = correct_mnemonic.split() + assert mnemonic_guess.split() != correct_mnemonic + assert typos or big_typos + btcrseed.loaded_wallet = btcrseed.WalletBIP39.create_from_params(mpk=the_mpk) + if mnemonic_length: + btcrseed.loaded_wallet.config_mnemonic(mnemonic_guess, expected_len=mnemonic_length) + else: + btcrseed.loaded_wallet.config_mnemonic(mnemonic_guess) + self.assertEqual( + btcrseed.run_btcrecover(typos or big_typos, big_typos, extra_args="--threads 1".split()), + tuple(correct_mnemonic)) + + def test_delete(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "certain come come keen collect slab gauge photo inside mechanic deny leader drop", # guess + typos=1, + mnemonic_length=12) + + def test_replacewrong(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "certain X keen collect slab gauge photo inside mechanic deny leader drop", # guess + big_typos=1) + + def test_insert(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + " come keen collect slab gauge photo inside mechanic deny leader drop", # guess + big_typos=1) + + def test_swap(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "certain keen come collect slab gauge photo inside mechanic deny leader drop", # guess + typos=1) + + def test_replace(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "disagree come keen collect slab gauge photo inside mechanic deny leader drop", # guess + big_typos=1) + + def test_replaceclose(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "certain become keen collect slab gauge photo inside mechanic deny leader drop", # guess + typos=1) + + def test_replaceclose_firstfour(self): + self.seed_tester(self.XPUB, + "certain come keen collect slab gauge photo inside mechanic deny leader drop", # correct + "cere come keen coll slab gaug phot insi mech deny lead drop", # guess + # "cere" is close to "cert" in the en-firstfour language, even though "cereal" is not close to "certain" + typos=1) + + +class TestRecoverySeedListsGenerators(unittest.TestCase): + # Both the tokenlist generator and seedlist generator should generate the same output, the list of passwords below. + expected_passwordlist = [[ + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'attitude', + 'convince'], + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'boy', 'convince', + 'attitude'], + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'boy', + 'convince'], + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'attitude', 'convince', + 'boy'], + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'boy', + 'attitude'], + ['ocean', 'hidden', 'kidney', 'famous', 'rich', 'season', 'gloom', 'husband', 'spring', 'convince', 'attitude', + 'boy'] + ]] + + def seedlist_tester(self, seedlistfile, correct_seedlist=None): + if correct_seedlist is None: + correct_seedlist = self.expected_passwordlist + # Check to see if the Seed List file exists (and if not, skip) + if not os.path.isfile("./btcrecover/test/test-listfiles/" + seedlistfile): + raise unittest.SkipTest("requires ./btcrecover/test/test-listfiles/" + seedlistfile) + + args = " --listpass --seedgenerator".split() + + btcrpass.parse_arguments(["--passwordlist"] + ["./btcrecover/test/test-listfiles/" + seedlistfile] + args, + disable_security_warning_param=True) + pwl_it, skipped = btcrpass.password_generator_factory(sys.maxsize) + generated_passwords = list(pwl_it) + self.assertEqual(generated_passwords, correct_seedlist) + def test_seedlist_allpositional(self): + self.tokenlist_tester("tokenlist-allpositional.txt", [[['elbow', 'text', 'print', 'census', 'battle', 'push', + 'oyster', 'team', 'home', 'april', 'travel', + 'barrel']]]) + + def test_seedlist_allpositional_tokenblocks(self): + self.tokenlist_tester("tokenlist-tokenblocks.txt", + [[['elbow', 'text', 'print', 'census', 'battle', 'push', + 'oyster', 'team', 'home', 'april', 'travel', + 'barrel']]], + max_tokens = 3, + min_tokens = 3) + + def test_tokenlist(self): + self.tokenlist_tester("SeedTokenListTest.txt") + + def tokenlist_tester(self, tokenlistfile, correct_seedlist=None, max_tokens = 12, min_tokens = 12): + if correct_seedlist is None: + correct_seedlist = self.expected_passwordlist + # Check to see if the Token List file exists (and if not, skip) + if not os.path.isfile("./btcrecover/test/test-listfiles/" + tokenlistfile): + raise unittest.SkipTest("requires ./btcrecover/test/test-listfiles/" + tokenlistfile) + + args = (" --listpass --seedgenerator --max-tokens " + str(max_tokens) + " --min-tokens " + str(min_tokens)).split() + + btcrpass.parse_arguments(["--tokenlist"] + ["./btcrecover/test/test-listfiles/" + tokenlistfile] + args, + disable_security_warning_param=True) + tok_it, skipped = btcrpass.password_generator_factory(sys.maxsize) + generated_passwords = list(tok_it) + self.assertEqual(generated_passwords, correct_seedlist) + + def test_seed_transforms_swaps_1(self): + self.seed_transform_tester(correct_seedlist= + [[('1', '2', '3'), + ('2', '1', '3'), + ('3', '2', '1'), + ('1', '3', '2')]], + transformArgument = "--seed-transform-wordswaps 1") + + def test_seed_transforms_swaps_2(self): + self.seed_transform_tester(correct_seedlist= + [[('1', '2', '3'), + ('2', '1', '3'), + ('1', '2', '3'), + ('3', '1', '2'), + ('2', '3', '1'), + ('3', '2', '1'), + ('2', '3', '1'), + ('1', '2', '3'), + ('3', '1', '2'), + ('1', '3', '2'), + ('3', '1', '2'), + ('2', '3', '1'), + ('1', '2', '3')]], + transformArgument = "--seed-transform-wordswaps 2") + + def test_seed_transforms_trezor_common_mistakes_1(self): + self.seed_transform_tester( + correct_seedlist=[ + [ + ('able', 'across', 'age'), + ('cable', 'across', 'age'), + ('table', 'across', 'age'), + ('able', 'cross', 'age'), + ('able', 'across', 'cage'), + ('able', 'across', 'page'), + ('able', 'across', 'wage'), + ] + ], + transformArgument="--seed-transform-trezor-common-mistakes 1", + tokenlist_filename="Seed-Transform-Trezor.txt", + ) + + def test_seed_transforms_trezor_common_mistakes_2(self): + self.seed_transform_tester( + correct_seedlist=[ + [ + ('able', 'across', 'age'), + ('cable', 'across', 'age'), + ('cable', 'cross', 'age'), + ('cable', 'across', 'cage'), + ('cable', 'across', 'page'), + ('cable', 'across', 'wage'), + ('table', 'across', 'age'), + ('table', 'cross', 'age'), + ('table', 'across', 'cage'), + ('table', 'across', 'page'), + ('table', 'across', 'wage'), + ('able', 'cross', 'age'), + ('able', 'cross', 'cage'), + ('able', 'cross', 'page'), + ('able', 'cross', 'wage'), + ('able', 'across', 'cage'), + ('able', 'across', 'page'), + ('able', 'across', 'wage'), + ] + ], + transformArgument="--seed-transform-trezor-common-mistakes 2", + tokenlist_filename="Seed-Transform-Trezor.txt", + ) + def seed_transform_tester( + self, + correct_seedlist=None, + transformArgument=None, + tokenlist_filename="Seed-Transform-Base.txt", + ): + if correct_seedlist is None: + correct_seedlist = self.expected_passwordlist + + # Check to see if the Token List file exists (and if not, skip) + tokenlist_path = "./btcrecover/test/test-listfiles/" + tokenlist_filename + if not os.path.isfile(tokenlist_path): + raise unittest.SkipTest("requires " + tokenlist_path) + + if transformArgument is None: + transformArgument = "" + + args = ( + " --listpass --seedgenerator --max-tokens 1 --min-tokens 1 " + + transformArgument + ).split() + + btcrpass.parse_arguments(["--tokenlist", tokenlist_path] + args, + disable_security_warning_param=True) + + tok_it, skipped = btcrpass.password_generator_factory(sys.maxsize) + generated_passwords = list(tok_it) + self.assertEqual(generated_passwords, correct_seedlist) + +class TestPhaseTransforms(unittest.TestCase): + class DummyWallet: + def __init__(self, speed): + self._speed = speed + + def passwords_per_seconds(self, _): + return self._speed + + def test_wordswap_transform_preserves_default_typos(self): + wallet = self.DummyWallet(100) + phases = btcrseed.build_search_phases( + wallet, {}, {"seed_transform_wordswaps": 2} + ) + + self.assertEqual(len(phases), 4) + self.assertTrue(all("typos" in phase for phase in phases)) + self.assertTrue( + all(phase["seed_transform_wordswaps"] == 2 for phase in phases) + ) + + def test_trezor_transform_preserves_default_typos(self): + wallet = self.DummyWallet(1) + phases = btcrseed.build_search_phases( + wallet, {}, {"seed_transform_trezor_common_mistakes": 3} + ) + + self.assertEqual(len(phases), 5) + self.assertEqual(phases[0]["typos"], 1) + self.assertEqual(phases[1]["typos"], 2) + self.assertTrue(all("typos" in phase for phase in phases)) + self.assertTrue( + all( + phase["seed_transform_trezor_common_mistakes"] == 3 + for phase in phases + ) + ) + + def test_custom_phase_keeps_typos_with_transform(self): + wallet = self.DummyWallet(50) + phase = {"typos": 3} + phases = btcrseed.build_search_phases( + wallet, phase, {"seed_transform_wordswaps": 1} + ) + + self.assertEqual(len(phases), 1) + self.assertIs(phases[0], phase) + self.assertEqual(phase["typos"], 3) + self.assertEqual(phase["seed_transform_wordswaps"], 1) + +# All seed tests except TestAddressSet.test_false_positives are quick +class QuickTests(unittest.TestSuite): + def __init__(self): + super(QuickTests, self).__init__() + for suite in unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]): + if isinstance(suite._tests[0], TestAddressSet): + for test_num in range(len(suite._tests)): + if suite._tests[test_num]._testMethodName == "test_false_positives": + del suite._tests[test_num] + break + self.addTests(suite) +@unittest.skipUnless(can_load_ShamirMnemonic(), "requires Shamir-Mnemonic module") +class TestSLIP39Seed(unittest.TestCase): + """Tests for SLIP39 share recovery""" + def test_share_checksum(self): + share = "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing" + wallet = btcrseed.WalletSLIP39Seed.create_from_params() + wallet.config_mnemonic(share) + self.assertEqual(wallet.return_verified_password_or_false((btcrseed.mnemonic_ids_guess,)), + (btcrseed.mnemonic_ids_guess, 1)) + + def test_insert_missing_word(self): + share = "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion" + wallet = btcrseed.WalletSLIP39Seed.create_from_params() + wallet.config_mnemonic(share) + # append the missing word to create the correct mnemonic ids + missing_id = wallet._word_to_id["losing"] + candidate = btcrseed.mnemonic_ids_guess + (missing_id,) + self.assertTrue(wallet.verify_mnemonic_syntax(candidate)) + self.assertEqual(wallet.return_verified_password_or_false((candidate,)), + (candidate, 1)) + + def test_assume_33_word_share(self): + share = ( + "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard " + "adjust muscle parcel sled crucial amazing mansion losing admit adorn adult advance advocate " + "afraid again agency agree aide" + ) + wallet = btcrseed.WalletSLIP39Seed.create_from_params() + wallet.config_mnemonic(share) + self.assertEqual(btcrseed.num_inserts, 33 - len(share.split())) + + + +if __name__ == '__main__': + import argparse + + # Add one new argument to those already provided by unittest.main() + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--no-buffer", action="store_true") + args, unittest_args = parser.parse_known_args() + sys.argv[1:] = unittest_args + + unittest.main(buffer=not args.no_buffer) diff --git a/btcrecover/test/test_usage_examples.py b/btcrecover/test/test_usage_examples.py new file mode 100644 index 000000000..9ff1605cb --- /dev/null +++ b/btcrecover/test/test_usage_examples.py @@ -0,0 +1,202 @@ +"""End-to-end tests for a subset of the basic usage examples.""" + +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path +from typing import Iterable, Sequence, Tuple + +import unittest + + +REPO_ROOT = Path(__file__).resolve().parents[2] +PYTHON_EXECUTABLE = sys.executable + +COMMON_PASSWORDLIST = REPO_ROOT / "docs" / "Usage_Examples" / "common_passwordlist.txt" + + +def _command(script: str, *args: str) -> list[str]: + """Return a subprocess command invoking *script* with *args*.""" + + return [PYTHON_EXECUTABLE, str(REPO_ROOT / script), *args] + + +def _run_command(command: Sequence[str]) -> subprocess.CompletedProcess[str]: + """Execute *command* in the repository root and capture output.""" + + env = os.environ.copy() + pythonpath_entries = [str(REPO_ROOT)] + if env.get("PYTHONPATH"): + pythonpath_entries.append(env["PYTHONPATH"]) + env["PYTHONPATH"] = os.pathsep.join(pythonpath_entries) + + return subprocess.run( + command, + cwd=str(REPO_ROOT), + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=False, + timeout=120, + ) + + +class CommandTestCase(unittest.TestCase): + """Base class that provides helpers for invoking CLI usage examples.""" + + expected_phrase: str + + def run_examples(self, examples: Iterable[Tuple[str, Sequence[str]]]) -> None: + for description, command in examples: + with self.subTest(description=description): + result = _run_command(command) + if result.returncode != 0: + self.fail( + "Command '{}' exited with {}\nOutput:\n{}".format( + " ".join(command), result.returncode, result.stdout + ) + ) + self.assertIn( + self.expected_phrase, + result.stdout, + msg="Command '{}' did not report {}\nOutput:\n{}".format( + " ".join(command), self.expected_phrase, result.stdout + ), + ) + + +class TestBasicPasswordUsageExamples(CommandTestCase): + """Smoke tests for representative password recovery usage commands.""" + + expected_phrase = "Password found" + + def test_basic_password_examples(self) -> None: + examples = [ + ( + "BIP38 paper wallet (Bitcoin)", + _command( + "btcrecover.py", + "--skip-pre-start", + "--dsw", + "--bip38-enc-privkey", + "6PnM7h9sBC9EMZxLVsKzpafvBN8zjKp8MZj6h9mfvYEQRMkKBTPTyWZHHx", + "--passwordlist", + str(COMMON_PASSWORDLIST), + ), + ), + ( + "BIP39 passphrase (Bitcoin)", + _command( + "btcrecover.py", + "--skip-pre-start", + "--dsw", + "--bip39", + "--addrs", + "1AmugMgC6pBbJGYuYmuRrEpQVB9BBMvCCn", + "--addr-limit", + "10", + "--passwordlist", + str(COMMON_PASSWORDLIST), + "--mnemonic", + "certain come keen collect slab gauge photo inside mechanic deny leader drop", + ), + ), + ( + "Electrum extra words", + _command( + "btcrecover.py", + "--skip-pre-start", + "--dsw", + "--wallet-type", + "electrum2", + "--addrs", + "bc1q6n3u9aar3vgydfr6q23fzcfadh4zlp2ns2ljp6", + "--addr-limit", + "10", + "--passwordlist", + str(COMMON_PASSWORDLIST), + "--mnemonic", + "quote voice evidence aspect warfare hire system black rate wing ask rug", + ), + ), + ] + + self.run_examples(examples) + + +class TestBasicSeedUsageExamples(CommandTestCase): + """Smoke tests for representative seed recovery usage commands.""" + + expected_phrase = "Seed found" + + def test_basic_seed_examples(self) -> None: + examples = [ + ( + "BIP39 native segwit", + _command( + "seedrecover.py", + "--skip-pre-start", + "--dsw", + "--wallet-type", + "bip39", + "--addrs", + "bc1qv87qf7prhjf2ld8vgm7l0mj59jggm6ae5jdkx2", + "--mnemonic", + "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect", + "--addr-limit", + "5", + ), + ), + ( + "aezeed", + _command( + "seedrecover.py", + "--skip-pre-start", + "--dsw", + "--wallet-type", + "aezeed", + "--addrs", + "1Hp6UXuJjzt9eSBa9LhtW97KPb44bq4CAQ", + "--mnemonic", + "absorb original enlist once climb erode kid thrive kitchen giant define tube orange leader harbor comfort olive fatal success suggest drink penalty chimney", + "--addr-limit", + "5", + ), + ), + ( + "Cardano base address", + _command( + "seedrecover.py", + "--skip-pre-start", + "--dsw", + "--wallet-type", + "cardano", + "--addrs", + "addr1qyr2c43g33hgwzyufdd6fztpvn5uq5lwc74j0kuqr7gdrq5dgrztddqtl8qhw93ay8r3g8kw67xs097u6gdspyfcrx5qfv739l", + "--mnemonic", + "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon basket fine culture boil render special enforce dish middle antique", + ), + ), + ( + "SLIP39 damaged share", + _command( + "seedrecover.py", + "--skip-pre-start", + "--dsw", + "--slip39", + "--mnemonic", + "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing", + "--typos", + "2", + ), + ), + ] + + self.run_examples(examples) + + +if __name__ == "__main__": + unittest.main() diff --git a/btcrecover/trezor_common_mistakes.py b/btcrecover/trezor_common_mistakes.py new file mode 100644 index 000000000..6ef70f3db --- /dev/null +++ b/btcrecover/trezor_common_mistakes.py @@ -0,0 +1,192 @@ +"""Mappings for Trezor's commonly misspelled BIP39 backup words. + +The source data mirrors the list published by Trezor and groups together +words which are frequently confused with each other. The +``TREZOR_COMMON_MISTAKE_GROUPS`` constant preserves the original groupings, +while ``TREZOR_COMMON_MISTAKES`` expands the groups into a mapping that can be +used by the seed transformation generators. +""" + +from __future__ import annotations + +from typing import Dict, Iterable, List, Sequence, Tuple + +TREZOR_COMMON_MISTAKE_GROUPS: Sequence[Tuple[str, ...]] = ( + ("able", "cable", "table"), + ("across", "cross"), + ("act", "art", "cat", "pact"), + ("action", "auction"), + ("add", "dad"), + ("again", "gain"), + ("age", "cage", "page", "wage"), + ("ahead", "head"), + ("aim", "air", "arm"), + ("air", "hair", "pair"), + ("all", "ball", "call", "fall", "ill", "wall"), + ("alley", "valley"), + ("alter", "later"), + ("anger", "danger"), + ("angle", "ankle"), + ("arch", "march"), + ("area", "arena"), + ("arm", "army", "art", "farm", "warm"), + ("around", "round"), + ("arrow", "narrow"), + ("art", "cart"), + ("ask", "mask", "task"), + ("aunt", "hunt"), + ("avoid", "void"), + ("awake", "aware"), + ("away", "way"), + ("bag", "bar", "tag"), + ("ball", "call", "fall", "wall"), + ("bar", "car", "jar"), + ("base", "case"), + ("battle", "cattle"), + ("beach", "bench", "teach"), + ("bean", "mean"), + ("belt", "best", "melt"), + ("best", "nest", "test", "west"), + ("better", "bitter", "butter", "letter"), + ("bid", "bind", "bird", "kid"), + ("bike", "like"), + ("bind", "bird", "blind", "find", "kind", "mind"), + ("bitter", "butter"), + ("blade", "blame"), + ("blame", "flame"), + ("blue", "blur", "glue"), + ("blush", "brush", "flush", "slush"), + ("boat", "goat"), + ("body", "boy"), + ("boil", "coil", "foil", "oil"), + ("bone", "one", "tone", "zone"), + ("book", "cook"), + ("border", "order"), + ("boring", "bring"), + ("boss", "toss"), + ("box", "boy", "fox"), + ("boy", "joy", "toy"), + ("brain", "grain", "rain", "train"), + ("brass", "grass"), + ("brick", "brisk", "trick"), + ("bridge", "ridge"), + ("brief", "grief"), + ("bright", "right"), + ("bring", "ring"), + ("brisk", "risk"), + ("broom", "room"), + ("brown", "frown"), + ("brush", "crush"), + ("bulb", "bulk"), + ("bus", "busy"), + ("cable", "table"), + ("cage", "cake", "case", "cave", "page", "wage"), + ("cake", "case", "cave", "lake", "make"), + ("call", "calm", "fall", "wall"), + ("calm", "palm"), + ("camp", "damp", "lamp", "ramp"), + ("can", "car", "cat", "fan", "man", "scan", "van"), + ("cannon", "canyon"), + ("car", "card", "cart", "cat", "jar"), + ("card", "cart", "hard", "yard"), + ("cart", "cat"), + ("case", "cash", "cause", "cave", "chase"), + ("cash", "crash", "dash", "wash"), + ("castle", "cattle"), + ("cat", "chat", "fat", "hat"), + ("catch", "match", "patch"), + ("cause", "pause"), + ("cave", "have", "pave", "save", "wave"), + ("certain", "curtain"), + ("chair", "hair"), + ("change", "charge"), + ("chat", "hat", "that", "what"), + ("chef", "chief"), + ("clap", "claw", "clay", "clip"), + ("claw", "clay", "law"), + ("clay", "play", "day"), + ("click", "clock"), + ("climb", "limb"), + ("clip", "flip"), + ("clock", "flock", "lock"), + ("cloud", "loud"), + ("clog", "dog"), + ("clutch", "dutch"), + ("coach", "couch"), + ("coast", "cost", "roast", "toast"), + ("code", "come", "core"), + ("coil", "coin", "cool", "foil", "oil"), + ("coin", "corn", "join"), + ("come", "core", "home"), + ("cook", "cool"), + ("cool", "pool", "tool", "wool"), + ("coral", "moral"), + ("core", "corn", "more"), + ("corn", "horn"), + ("cost", "host", "post"), + ("couch", "crouch"), + ("cover", "hover", "over"), + ("crack", "rack", "track"), + ("craft", "draft"), + ("cram", "cream"), + ("crash", "crush", "trash"), + ("cream", "dream"), + ("crop", "drop"), + ("cry", "dry", "try"), + ("cube", "cute", "tube"), + ("dad", "day", "mad", "sad"), + ("damp", "lamp", "ramp"), + ("daring", "during"), + ("dash", "dish", "wash"), + ("dawn", "lawn"), + ("day", "dry", "say", "way"), + ("deal", "dial", "real"), + ("decade", "decide"), + ("defy", "deny"), + ("derive", "drive"), + ("dice", "ice", "nice", "rice"), + ("diet", "dirt"), + ("dinner", "inner", "winner"), + ("dish", "fish", "wish"), + ("dog", "fog", "clog"), + ("donkey", "monkey"), + ("donor", "door"), + ("door", "odor"), + ("dose", "dove", "nose", "rose", "close"), + ("dove", "love", "move"), + ("draft", "drift"), + ("draw", "raw"), + ("drip", "drop", "trip"), + ("dry", "try"), + ("dust", "just", "must"), + ("earn", "learn"), + ("east", "easy", "vast"), + ("edit", "exit"), +) + + +def _build_mistake_mapping( + groups: Iterable[Sequence[str]], +) -> Dict[str, Tuple[str, ...]]: + mapping: Dict[str, List[str]] = {} + for group in groups: + normalized = tuple(word.strip().lower() for word in group if word) + for idx, word in enumerate(normalized): + others = normalized[:idx] + normalized[idx + 1 :] + if not others: + continue + alternatives = mapping.setdefault(word, []) + for other in others: + if other not in alternatives: + alternatives.append(other) + return {word: tuple(alternatives) for word, alternatives in mapping.items()} + + +TREZOR_COMMON_MISTAKES: Dict[str, Tuple[str, ...]] = _build_mistake_mapping( + TREZOR_COMMON_MISTAKE_GROUPS +) + +__all__ = [ + "TREZOR_COMMON_MISTAKE_GROUPS", + "TREZOR_COMMON_MISTAKES", +] diff --git a/btcrecover/wallet.proto b/btcrecover/wallet.proto.bitcoinj similarity index 99% rename from btcrecover/wallet.proto rename to btcrecover/wallet.proto.bitcoinj index 2025dbe32..459d0c131 100644 --- a/btcrecover/wallet.proto +++ b/btcrecover/wallet.proto.bitcoinj @@ -1,6 +1,6 @@ /* This file is copied from version 0.12 of bitcoinj. Aside from this * introductory message, the rest remains unchanged from the original. - * To generate wallet_pb2.py, run: protoc --python_out=. wallet.proto + * To generate wallet_pb2.py, run: protoc --python_out=. wallet.proto.bitcoinj */ /** Copyright 2013 Google Inc. diff --git a/btcrecover/wallet.proto.coinomi b/btcrecover/wallet.proto.coinomi new file mode 100644 index 000000000..22a65ffad --- /dev/null +++ b/btcrecover/wallet.proto.coinomi @@ -0,0 +1,345 @@ +/* This file is copied from the last open source version of Coinomi. Aside from this + * introductory message, the rest remains unchanged from the original. + * To generate wallet_pb2.py, run: protoc --python_out=. wallet.proto.bitcoinj + */ + +/** Copyright 2013 Google Inc. + * Copyright 2014 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Authors: Jim Burton, Miron Cuperman, Andreas Schildbach, John L. Jegutanis + */ + +/* Notes: + * - Endianness: All byte arrays that represent numbers (such as hashes and private keys) are Big Endian + * - To regenerate after editing, run: mvn generate-sources -DupdateProtobuf + */ + +package com.coinomi.core.protos; + +option java_package = "com.coinomi.core.protos"; + +option java_outer_classname = "Protos"; + +message PeerAddress { + required bytes ip_address = 1; + required uint32 port = 2; + required uint64 services = 3; +} + +message EncryptedData { + required bytes initialisation_vector = 1; // The initialisation vector for the AES encryption (16 bytes) + required bytes encrypted_private_key = 2; // The encrypted private key +} + +/** + * Data attached to a Key message that defines the data needed by the BIP32 deterministic key hierarchy algorithm. + */ +message DeterministicKey { + // Random data that allows us to extend a key. Without this, we can't figure out the next key in the chain and + // should just treat it as a regular ORIGINAL type key. + required bytes chain_code = 1; + + // The path through the key tree. Each number is encoded in the standard form: high bit set for private derivation + // and high bit unset for public derivation. + repeated uint32 path = 2; + + // How many children of this key have been issued, that is, given to the user when they requested a fresh key? + // For the parents of keys being handed out, this is always less than the true number of children: the difference is + // called the lookahead zone. These keys are put into Bloom filters so we can spot transactions made by clones of + // this wallet - for instance when restoring from backup or if the seed was shared between devices. + // + // If this field is missing it means we're not issuing subkeys of this key to users. + optional uint32 issued_subkeys = 3; + optional uint32 lookahead_size = 4; + + /** + * Flag indicating that this key is a root of a following chain. This chain is following the next non-following chain. + * Following/followed chains concept is used for married keychains, where the set of keys combined together to produce + * a single P2SH multisignature address + */ + optional bool isFollowing = 5; +} + +/** + * A key used to control Bitcoin spending. + * + * Either the private key, the public key or both may be present. It is recommended that + * if the private key is provided that the public key is provided too because deriving it is slow. + * + * If only the public key is provided, the key can only be used to watch the blockchain and verify + * transactions, and not for spending. + */ +message Key { + enum Type { + /** Unencrypted - Original bitcoin secp256k1 curve */ + ORIGINAL = 1; + + /** Encrypted with Scrypt and AES - Original bitcoin secp256k1 curve */ + ENCRYPTED_SCRYPT_AES = 2; + + /** + * Not really a key, but rather contains the mnemonic phrase for a deterministic key hierarchy in the private_key field. + * The label and public_key fields are missing. Creation timestamp will exist. + */ + DETERMINISTIC_MNEMONIC = 3; + + /** + * A key that was derived deterministically. Note that the root seed that created it may NOT be present in the + * wallet, for the case of watching wallets. A deterministic key may or may not have the private key bytes present. + * However the public key bytes and the deterministic_key field are guaranteed to exist. In a wallet where there + * is a path from this key up to a key that has (possibly encrypted) private bytes, it's expected that the private + * key can be rederived on the fly. + */ + DETERMINISTIC_KEY = 4; + } + required Type type = 1; + + // Either the private EC key bytes (without any ASN.1 wrapping), or the deterministic root seed. + // If the secret is encrypted, or this is a "watching entry" then this is missing. + optional bytes secret_bytes = 2; + // If the secret data is encrypted, then secret_bytes is missing and this field is set. + optional EncryptedData encrypted_data = 3; + + // The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to + // do lots of slow EC math on startup. For DETERMINISTIC_MNEMONIC entries this is missing. + optional bytes public_key = 4; + + // User-provided label associated with the key. + optional string label = 5; + + optional DeterministicKey deterministic_key = 6; +} + +message TransactionInput { + // Hash of the transaction this input is using. + required bytes transaction_out_point_hash = 1; + // Index of transaction output used by this input. + required uint32 transaction_out_point_index = 2; + // Script that contains the signatures/pubkeys. + required bytes script_bytes = 3; + // Sequence number. Currently unused, but intended for contracts in future. + optional uint32 sequence = 4; + // Value of connected output, if known + optional int64 value = 5; +} + +message TransactionOutput { + required int64 value = 1; + required bytes script_bytes = 2; // script of transaction output + // If spent, the hash of the transaction doing the spend. + optional bytes spent_by_transaction_hash = 3; + // If spent, the index of the transaction input of the transaction doing the spend. + optional int32 spent_by_transaction_index = 4; + // If the output is generally spent + optional bool is_spent = 5 [default = false]; + // The index of this output, used in trimmed transactions + optional int32 index = 6; +} + +message UnspentOutput { + // Hash of the transaction this input is using. + required bytes out_point_hash = 1; + // Index of transaction output used by this input. + required uint32 out_point_index = 2; + // Script of transaction output + required bytes script_bytes = 3; + // Value of theoutput + required int64 value = 4; + // If this output is from a coin base or coin stake transaction + optional bool is_generated = 5 [default = false]; +} + +/** + * A description of the confidence we have that a transaction cannot be reversed in the future. + * + * Parsing should be lenient, since this could change for different applications yet we should + * maintain backward compatibility. + */ +message TransactionConfidence { + enum Type { + UNKNOWN = 0; + BUILDING = 1; // In best chain. If and only if appeared_at_height is present. + PENDING = 2; // Unconfirmed and sitting in the networks memory pools, waiting to be included in the chain. + DEAD = 3; // Either if overriding_transaction is present or transaction is dead coinbase + } + + // This is optional in case we add confidence types to prevent parse errors - backwards compatible. + optional Type type = 1; + + // If type == BUILDING then this is the chain height at which the transaction was included. + optional int32 appeared_at_height = 2; + + // If set, hash of the transaction that double spent this one into oblivion. A transaction can be double spent by + // multiple transactions in the case of several inputs being re-spent by several transactions but we don't + // bother to track them all, just the first. This only makes sense if type = DEAD. + optional bytes overriding_transaction = 3; + + // If type == BUILDING then this is the depth of the transaction in the blockchain. + // Zero confirmations: depth = 0, one confirmation: depth = 1 etc. + optional int32 depth = 4; + + repeated PeerAddress broadcast_by = 5; + + // Where did we get this transaction from? Knowing the source may help us to risk analyze pending transactions. + enum Source { + SOURCE_UNKNOWN = 0; // We don't know where it came from, or this is a wallet from the future. + SOURCE_NETWORK = 1; // We received it from a network broadcast. This is the normal way to get payments. + SOURCE_SELF = 2; // We made it ourselves, so we know it should be valid. + SOURCE_TRUSTED = 3; // We received it from a trusted source. + } + optional Source source = 6; +} + +/** A bitcoin transaction */ + +message Transaction { + /** + * This is a bitfield oriented enum, with the following bits: + * + * bit 0 - spent + * bit 1 - appears in alt chain + * bit 2 - appears in best chain + * bit 3 - double-spent + * bit 4 - pending (we would like the tx to go into the best chain) + * + * Not all combinations are interesting, just the ones actually used in the enum. + */ + enum Pool { + UNSPENT = 4; // In best chain, not all outputs spent + SPENT = 5; // In best chain, all outputs spent + DEAD = 10; // Double-spent by a transaction in the best chain + PENDING = 16; // Our transaction, not in any chain + } + + // See Wallet.java for detailed description of pool semantics + required int32 version = 1; + optional int32 time = 11; // Used by Peercoin family + required bytes hash = 2; + + // If pool is not present, that means either: + // - This Transaction is either not in a wallet at all (the proto is re-used elsewhere) + // - Or it is stored but for other purposes, for example, because it is the overriding transaction of a double spend. + // - Or the Pool enum got a new value which your software is too old to parse. + optional Pool pool = 3; + + optional uint32 lock_time = 4; // The nLockTime field is useful for contracts. + optional int64 updated_at = 5; // millis since epoch the transaction was last updated + + repeated TransactionInput transaction_input = 6; + optional int32 num_of_outputs = 18; // The original number of outputs, used in trimmed transactions + repeated TransactionOutput transaction_output = 7; + + // A list of blocks in which the transaction has been observed (on any chain). Also, a number used to disambiguate + // ordering within a block. + repeated bytes block_hash = 8; + repeated int32 block_relativity_offsets = 9; + + // Data describing where the transaction is in the chain. + optional TransactionConfidence confidence = 10; + + optional int32 token_id = 12; + + optional bytes extra_bytes = 13; + + // A transaction can be stored in a trimmed state where some inputs or outputs are stripped to save space. + optional bool is_trimmed = 14 [default = false]; + + // The value that the wallet sends, it is the sum of the inputs that are spending coins with keys in the wallet. + optional int64 value_sent = 15 [default = 0]; + // The value that the wallet received, it is the sum of the outputs that are sending coins to a key in the wallet. + optional int64 value_received = 16 [default = 0]; + // The transaction fee + optional int64 fee = 17; + + // Next tag: 19 +} + +message AddressStatus { + required string address = 1; // The address that the wallet watches + required string status = 2; // The address status +} + +/** A wallet pocket*/ +message WalletPocket { + required string network_identifier = 1; // the network used by this wallet + + // A UTF8 encoded text description of the wallet that is intended for end user provided text. + optional string description = 2; + + repeated Key key = 3; + + // The SHA256 hash of the head of the best chain seen by this wallet. + optional bytes last_seen_block_hash = 4; + // The height in the chain of the last seen block. + optional uint32 last_seen_block_height = 5; + optional int64 last_seen_block_time_secs = 6; + + repeated Transaction transaction = 7; + + repeated AddressStatus address_status = 8; + + optional string id = 9; + + repeated UnspentOutput unspent_output = 10; + + // Next tag: 11 +} + +/** The parameters used in the scrypt key derivation function. + * The default values are taken from http://www.tarsnap.com/scrypt/scrypt-slides.pdf. + * They can be increased - n is the number of iterations performed and + * r and p can be used to tweak the algorithm - see: + * http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors + */ +message ScryptParameters { + required bytes salt = 1; // Salt to use in generation of the wallet password (8 bytes) + optional int64 n = 2 [default = 16384]; // CPU/ memory cost parameter + optional int32 r = 3 [default = 8]; // Block size parameter + optional int32 p = 4 [default = 1]; // Parallelisation parameter +} + +/** A bitcoin wallet */ +message Wallet { + /** + * The encryption type of the wallet. + * + * The encryption type is UNENCRYPTED for wallets where the wallet does not support encryption - wallets prior to + * encryption support are grandfathered in as this wallet type. + * When a wallet is ENCRYPTED_SCRYPT_AES the keys are either encrypted with the wallet password or are unencrypted. + */ + enum EncryptionType { + UNENCRYPTED = 1; // All keys in the wallet are unencrypted + ENCRYPTED_SCRYPT_AES = 2; // All keys are encrypted with a passphrase based KDF of scrypt and AES encryption + ENCRYPTED_AES = 3; // All keys are encrypted with AES encryption + } + + // The version number of the wallet - used to detect wallets that were produced in the future + // (i.e the wallet may contain some future format this protobuf/ code does not know about) + optional int32 version = 1 [default = 1]; + + optional Key seed = 2; + optional bool seed_password_protected = 7; + + required Key master_key = 3; + + optional EncryptionType encryption_type = 4 [default = UNENCRYPTED]; + optional ScryptParameters encryption_parameters = 5; + + repeated WalletPocket pockets = 6; + + // Next tag: 8 +} diff --git a/btcrecover/wordlists/bip39-cs.txt b/btcrecover/wordlists/bip39-cs.txt new file mode 100644 index 000000000..fdab4a2ae --- /dev/null +++ b/btcrecover/wordlists/bip39-cs.txt @@ -0,0 +1,2048 @@ +abdikace +abeceda +adresa +agrese +akce +aktovka +alej +alkohol +amputace +ananas +andulka +anekdota +anketa +antika +anulovat +archa +arogance +asfalt +asistent +aspirace +astma +astronom +atlas +atletika +atol +autobus +azyl +babka +bachor +bacil +baculka +badatel +bageta +bagr +bahno +bakterie +balada +baletka +balkon +balonek +balvan +balza +bambus +bankomat +barbar +baret +barman +baroko +barva +baterka +batoh +bavlna +bazalka +bazilika +bazuka +bedna +beran +beseda +bestie +beton +bezinka +bezmoc +beztak +bicykl +bidlo +biftek +bikiny +bilance +biograf +biolog +bitva +bizon +blahobyt +blatouch +blecha +bledule +blesk +blikat +blizna +blokovat +bloudit +blud +bobek +bobr +bodlina +bodnout +bohatost +bojkot +bojovat +bokorys +bolest +borec +borovice +bota +boubel +bouchat +bouda +boule +bourat +boxer +bradavka +brambora +branka +bratr +brepta +briketa +brko +brloh +bronz +broskev +brunetka +brusinka +brzda +brzy +bublina +bubnovat +buchta +buditel +budka +budova +bufet +bujarost +bukvice +buldok +bulva +bunda +bunkr +burza +butik +buvol +buzola +bydlet +bylina +bytovka +bzukot +capart +carevna +cedr +cedule +cejch +cejn +cela +celer +celkem +celnice +cenina +cennost +cenovka +centrum +cenzor +cestopis +cetka +chalupa +chapadlo +charita +chata +chechtat +chemie +chichot +chirurg +chlad +chleba +chlubit +chmel +chmura +chobot +chochol +chodba +cholera +chomout +chopit +choroba +chov +chrapot +chrlit +chrt +chrup +chtivost +chudina +chutnat +chvat +chvilka +chvost +chyba +chystat +chytit +cibule +cigareta +cihelna +cihla +cinkot +cirkus +cisterna +citace +citrus +cizinec +cizost +clona +cokoliv +couvat +ctitel +ctnost +cudnost +cuketa +cukr +cupot +cvaknout +cval +cvik +cvrkot +cyklista +daleko +dareba +datel +datum +dcera +debata +dechovka +decibel +deficit +deflace +dekl +dekret +demokrat +deprese +derby +deska +detektiv +dikobraz +diktovat +dioda +diplom +disk +displej +divadlo +divoch +dlaha +dlouho +dluhopis +dnes +dobro +dobytek +docent +dochutit +dodnes +dohled +dohoda +dohra +dojem +dojnice +doklad +dokola +doktor +dokument +dolar +doleva +dolina +doma +dominant +domluvit +domov +donutit +dopad +dopis +doplnit +doposud +doprovod +dopustit +dorazit +dorost +dort +dosah +doslov +dostatek +dosud +dosyta +dotaz +dotek +dotknout +doufat +doutnat +dovozce +dozadu +doznat +dozorce +drahota +drak +dramatik +dravec +draze +drdol +drobnost +drogerie +drozd +drsnost +drtit +drzost +duben +duchovno +dudek +duha +duhovka +dusit +dusno +dutost +dvojice +dvorec +dynamit +ekolog +ekonomie +elektron +elipsa +email +emise +emoce +empatie +epizoda +epocha +epopej +epos +esej +esence +eskorta +eskymo +etiketa +euforie +evoluce +exekuce +exkurze +expedice +exploze +export +extrakt +facka +fajfka +fakulta +fanatik +fantazie +farmacie +favorit +fazole +federace +fejeton +fenka +fialka +figurant +filozof +filtr +finance +finta +fixace +fjord +flanel +flirt +flotila +fond +fosfor +fotbal +fotka +foton +frakce +freska +fronta +fukar +funkce +fyzika +galeje +garant +genetika +geolog +gilotina +glazura +glejt +golem +golfista +gotika +graf +gramofon +granule +grep +gril +grog +groteska +guma +hadice +hadr +hala +halenka +hanba +hanopis +harfa +harpuna +havran +hebkost +hejkal +hejno +hejtman +hektar +helma +hematom +herec +herna +heslo +hezky +historik +hladovka +hlasivky +hlava +hledat +hlen +hlodavec +hloh +hloupost +hltat +hlubina +hluchota +hmat +hmota +hmyz +hnis +hnojivo +hnout +hoblina +hoboj +hoch +hodiny +hodlat +hodnota +hodovat +hojnost +hokej +holinka +holka +holub +homole +honitba +honorace +horal +horda +horizont +horko +horlivec +hormon +hornina +horoskop +horstvo +hospoda +hostina +hotovost +houba +houf +houpat +houska +hovor +hradba +hranice +hravost +hrazda +hrbolek +hrdina +hrdlo +hrdost +hrnek +hrobka +hromada +hrot +hrouda +hrozen +hrstka +hrubost +hryzat +hubenost +hubnout +hudba +hukot +humr +husita +hustota +hvozd +hybnost +hydrant +hygiena +hymna +hysterik +idylka +ihned +ikona +iluze +imunita +infekce +inflace +inkaso +inovace +inspekce +internet +invalida +investor +inzerce +ironie +jablko +jachta +jahoda +jakmile +jakost +jalovec +jantar +jarmark +jaro +jasan +jasno +jatka +javor +jazyk +jedinec +jedle +jednatel +jehlan +jekot +jelen +jelito +jemnost +jenom +jepice +jeseter +jevit +jezdec +jezero +jinak +jindy +jinoch +jiskra +jistota +jitrnice +jizva +jmenovat +jogurt +jurta +kabaret +kabel +kabinet +kachna +kadet +kadidlo +kahan +kajak +kajuta +kakao +kaktus +kalamita +kalhoty +kalibr +kalnost +kamera +kamkoliv +kamna +kanibal +kanoe +kantor +kapalina +kapela +kapitola +kapka +kaple +kapota +kapr +kapusta +kapybara +karamel +karotka +karton +kasa +katalog +katedra +kauce +kauza +kavalec +kazajka +kazeta +kazivost +kdekoliv +kdesi +kedluben +kemp +keramika +kino +klacek +kladivo +klam +klapot +klasika +klaun +klec +klenba +klepat +klesnout +klid +klima +klisna +klobouk +klokan +klopa +kloub +klubovna +klusat +kluzkost +kmen +kmitat +kmotr +kniha +knot +koalice +koberec +kobka +kobliha +kobyla +kocour +kohout +kojenec +kokos +koktejl +kolaps +koleda +kolize +kolo +komando +kometa +komik +komnata +komora +kompas +komunita +konat +koncept +kondice +konec +konfese +kongres +konina +konkurs +kontakt +konzerva +kopanec +kopie +kopnout +koprovka +korbel +korektor +kormidlo +koroptev +korpus +koruna +koryto +korzet +kosatec +kostka +kotel +kotleta +kotoul +koukat +koupelna +kousek +kouzlo +kovboj +koza +kozoroh +krabice +krach +krajina +kralovat +krasopis +kravata +kredit +krejcar +kresba +kreveta +kriket +kritik +krize +krkavec +krmelec +krmivo +krocan +krok +kronika +kropit +kroupa +krovka +krtek +kruhadlo +krupice +krutost +krvinka +krychle +krypta +krystal +kryt +kudlanka +kufr +kujnost +kukla +kulajda +kulich +kulka +kulomet +kultura +kuna +kupodivu +kurt +kurzor +kutil +kvalita +kvasinka +kvestor +kynolog +kyselina +kytara +kytice +kytka +kytovec +kyvadlo +labrador +lachtan +ladnost +laik +lakomec +lamela +lampa +lanovka +lasice +laso +lastura +latinka +lavina +lebka +leckdy +leden +lednice +ledovka +ledvina +legenda +legie +legrace +lehce +lehkost +lehnout +lektvar +lenochod +lentilka +lepenka +lepidlo +letadlo +letec +letmo +letokruh +levhart +levitace +levobok +libra +lichotka +lidojed +lidskost +lihovina +lijavec +lilek +limetka +linie +linka +linoleum +listopad +litina +litovat +lobista +lodivod +logika +logoped +lokalita +loket +lomcovat +lopata +lopuch +lord +losos +lotr +loudal +louh +louka +louskat +lovec +lstivost +lucerna +lucifer +lump +lusk +lustrace +lvice +lyra +lyrika +lysina +madam +madlo +magistr +mahagon +majetek +majitel +majorita +makak +makovice +makrela +malba +malina +malovat +malvice +maminka +mandle +manko +marnost +masakr +maskot +masopust +matice +matrika +maturita +mazanec +mazivo +mazlit +mazurka +mdloba +mechanik +meditace +medovina +melasa +meloun +mentolka +metla +metoda +metr +mezera +migrace +mihnout +mihule +mikina +mikrofon +milenec +milimetr +milost +mimika +mincovna +minibar +minomet +minulost +miska +mistr +mixovat +mladost +mlha +mlhovina +mlok +mlsat +mluvit +mnich +mnohem +mobil +mocnost +modelka +modlitba +mohyla +mokro +molekula +momentka +monarcha +monokl +monstrum +montovat +monzun +mosaz +moskyt +most +motivace +motorka +motyka +moucha +moudrost +mozaika +mozek +mozol +mramor +mravenec +mrkev +mrtvola +mrzet +mrzutost +mstitel +mudrc +muflon +mulat +mumie +munice +muset +mutace +muzeum +muzikant +myslivec +mzda +nabourat +nachytat +nadace +nadbytek +nadhoz +nadobro +nadpis +nahlas +nahnat +nahodile +nahradit +naivita +najednou +najisto +najmout +naklonit +nakonec +nakrmit +nalevo +namazat +namluvit +nanometr +naoko +naopak +naostro +napadat +napevno +naplnit +napnout +naposled +naprosto +narodit +naruby +narychlo +nasadit +nasekat +naslepo +nastat +natolik +navenek +navrch +navzdory +nazvat +nebe +nechat +necky +nedaleko +nedbat +neduh +negace +nehet +nehoda +nejen +nejprve +neklid +nelibost +nemilost +nemoc +neochota +neonka +nepokoj +nerost +nerv +nesmysl +nesoulad +netvor +neuron +nevina +nezvykle +nicota +nijak +nikam +nikdy +nikl +nikterak +nitro +nocleh +nohavice +nominace +nora +norek +nositel +nosnost +nouze +noviny +novota +nozdra +nuda +nudle +nuget +nutit +nutnost +nutrie +nymfa +obal +obarvit +obava +obdiv +obec +obehnat +obejmout +obezita +obhajoba +obilnice +objasnit +objekt +obklopit +oblast +oblek +obliba +obloha +obluda +obnos +obohatit +obojek +obout +obrazec +obrna +obruba +obrys +obsah +obsluha +obstarat +obuv +obvaz +obvinit +obvod +obvykle +obyvatel +obzor +ocas +ocel +ocenit +ochladit +ochota +ochrana +ocitnout +odboj +odbyt +odchod +odcizit +odebrat +odeslat +odevzdat +odezva +odhadce +odhodit +odjet +odjinud +odkaz +odkoupit +odliv +odluka +odmlka +odolnost +odpad +odpis +odplout +odpor +odpustit +odpykat +odrazka +odsoudit +odstup +odsun +odtok +odtud +odvaha +odveta +odvolat +odvracet +odznak +ofina +ofsajd +ohlas +ohnisko +ohrada +ohrozit +ohryzek +okap +okenice +oklika +okno +okouzlit +okovy +okrasa +okres +okrsek +okruh +okupant +okurka +okusit +olejnina +olizovat +omak +omeleta +omezit +omladina +omlouvat +omluva +omyl +onehdy +opakovat +opasek +operace +opice +opilost +opisovat +opora +opozice +opravdu +oproti +orbital +orchestr +orgie +orlice +orloj +ortel +osada +oschnout +osika +osivo +oslava +oslepit +oslnit +oslovit +osnova +osoba +osolit +ospalec +osten +ostraha +ostuda +ostych +osvojit +oteplit +otisk +otop +otrhat +otrlost +otrok +otruby +otvor +ovanout +ovar +oves +ovlivnit +ovoce +oxid +ozdoba +pachatel +pacient +padouch +pahorek +pakt +palanda +palec +palivo +paluba +pamflet +pamlsek +panenka +panika +panna +panovat +panstvo +pantofle +paprika +parketa +parodie +parta +paruka +paryba +paseka +pasivita +pastelka +patent +patrona +pavouk +pazneht +pazourek +pecka +pedagog +pejsek +peklo +peloton +penalta +pendrek +penze +periskop +pero +pestrost +petarda +petice +petrolej +pevnina +pexeso +pianista +piha +pijavice +pikle +piknik +pilina +pilnost +pilulka +pinzeta +pipeta +pisatel +pistole +pitevna +pivnice +pivovar +placenta +plakat +plamen +planeta +plastika +platit +plavidlo +plaz +plech +plemeno +plenta +ples +pletivo +plevel +plivat +plnit +plno +plocha +plodina +plomba +plout +pluk +plyn +pobavit +pobyt +pochod +pocit +poctivec +podat +podcenit +podepsat +podhled +podivit +podklad +podmanit +podnik +podoba +podpora +podraz +podstata +podvod +podzim +poezie +pohanka +pohnutka +pohovor +pohroma +pohyb +pointa +pojistka +pojmout +pokazit +pokles +pokoj +pokrok +pokuta +pokyn +poledne +polibek +polknout +poloha +polynom +pomalu +pominout +pomlka +pomoc +pomsta +pomyslet +ponechat +ponorka +ponurost +popadat +popel +popisek +poplach +poprosit +popsat +popud +poradce +porce +porod +porucha +poryv +posadit +posed +posila +poskok +poslanec +posoudit +pospolu +postava +posudek +posyp +potah +potkan +potlesk +potomek +potrava +potupa +potvora +poukaz +pouto +pouzdro +povaha +povidla +povlak +povoz +povrch +povstat +povyk +povzdech +pozdrav +pozemek +poznatek +pozor +pozvat +pracovat +prahory +praktika +prales +praotec +praporek +prase +pravda +princip +prkno +probudit +procento +prodej +profese +prohra +projekt +prolomit +promile +pronikat +propad +prorok +prosba +proton +proutek +provaz +prskavka +prsten +prudkost +prut +prvek +prvohory +psanec +psovod +pstruh +ptactvo +puberta +puch +pudl +pukavec +puklina +pukrle +pult +pumpa +punc +pupen +pusa +pusinka +pustina +putovat +putyka +pyramida +pysk +pytel +racek +rachot +radiace +radnice +radon +raft +ragby +raketa +rakovina +rameno +rampouch +rande +rarach +rarita +rasovna +rastr +ratolest +razance +razidlo +reagovat +reakce +recept +redaktor +referent +reflex +rejnok +reklama +rekord +rekrut +rektor +reputace +revize +revma +revolver +rezerva +riskovat +riziko +robotika +rodokmen +rohovka +rokle +rokoko +romaneto +ropovod +ropucha +rorejs +rosol +rostlina +rotmistr +rotoped +rotunda +roubenka +roucho +roup +roura +rovina +rovnice +rozbor +rozchod +rozdat +rozeznat +rozhodce +rozinka +rozjezd +rozkaz +rozloha +rozmar +rozpad +rozruch +rozsah +roztok +rozum +rozvod +rubrika +ruchadlo +rukavice +rukopis +ryba +rybolov +rychlost +rydlo +rypadlo +rytina +ryzost +sadista +sahat +sako +samec +samizdat +samota +sanitka +sardinka +sasanka +satelit +sazba +sazenice +sbor +schovat +sebranka +secese +sedadlo +sediment +sedlo +sehnat +sejmout +sekera +sekta +sekunda +sekvoje +semeno +seno +servis +sesadit +seshora +seskok +seslat +sestra +sesuv +sesypat +setba +setina +setkat +setnout +setrvat +sever +seznam +shoda +shrnout +sifon +silnice +sirka +sirotek +sirup +situace +skafandr +skalisko +skanzen +skaut +skeptik +skica +skladba +sklenice +sklo +skluz +skoba +skokan +skoro +skripta +skrz +skupina +skvost +skvrna +slabika +sladidlo +slanina +slast +slavnost +sledovat +slepec +sleva +slezina +slib +slina +sliznice +slon +sloupek +slovo +sluch +sluha +slunce +slupka +slza +smaragd +smetana +smilstvo +smlouva +smog +smrad +smrk +smrtka +smutek +smysl +snad +snaha +snob +sobota +socha +sodovka +sokol +sopka +sotva +souboj +soucit +soudce +souhlas +soulad +soumrak +souprava +soused +soutok +souviset +spalovna +spasitel +spis +splav +spodek +spojenec +spolu +sponzor +spornost +spousta +sprcha +spustit +sranda +sraz +srdce +srna +srnec +srovnat +srpen +srst +srub +stanice +starosta +statika +stavba +stehno +stezka +stodola +stolek +stopa +storno +stoupat +strach +stres +strhnout +strom +struna +studna +stupnice +stvol +styk +subjekt +subtropy +suchar +sudost +sukno +sundat +sunout +surikata +surovina +svah +svalstvo +svetr +svatba +svazek +svisle +svitek +svoboda +svodidlo +svorka +svrab +sykavka +sykot +synek +synovec +sypat +sypkost +syrovost +sysel +sytost +tabletka +tabule +tahoun +tajemno +tajfun +tajga +tajit +tajnost +taktika +tamhle +tampon +tancovat +tanec +tanker +tapeta +tavenina +tazatel +technika +tehdy +tekutina +telefon +temnota +tendence +tenista +tenor +teplota +tepna +teprve +terapie +termoska +textil +ticho +tiskopis +titulek +tkadlec +tkanina +tlapka +tleskat +tlukot +tlupa +tmel +toaleta +topinka +topol +torzo +touha +toulec +tradice +traktor +tramp +trasa +traverza +trefit +trest +trezor +trhavina +trhlina +trochu +trojice +troska +trouba +trpce +trpitel +trpkost +trubec +truchlit +truhlice +trus +trvat +tudy +tuhnout +tuhost +tundra +turista +turnaj +tuzemsko +tvaroh +tvorba +tvrdost +tvrz +tygr +tykev +ubohost +uboze +ubrat +ubrousek +ubrus +ubytovna +ucho +uctivost +udivit +uhradit +ujednat +ujistit +ujmout +ukazatel +uklidnit +uklonit +ukotvit +ukrojit +ulice +ulita +ulovit +umyvadlo +unavit +uniforma +uniknout +upadnout +uplatnit +uplynout +upoutat +upravit +uran +urazit +usednout +usilovat +usmrtit +usnadnit +usnout +usoudit +ustlat +ustrnout +utahovat +utkat +utlumit +utonout +utopenec +utrousit +uvalit +uvolnit +uvozovka +uzdravit +uzel +uzenina +uzlina +uznat +vagon +valcha +valoun +vana +vandal +vanilka +varan +varhany +varovat +vcelku +vchod +vdova +vedro +vegetace +vejce +velbloud +veletrh +velitel +velmoc +velryba +venkov +veranda +verze +veselka +veskrze +vesnice +vespodu +vesta +veterina +veverka +vibrace +vichr +videohra +vidina +vidle +vila +vinice +viset +vitalita +vize +vizitka +vjezd +vklad +vkus +vlajka +vlak +vlasec +vlevo +vlhkost +vliv +vlnovka +vloupat +vnucovat +vnuk +voda +vodivost +vodoznak +vodstvo +vojensky +vojna +vojsko +volant +volba +volit +volno +voskovka +vozidlo +vozovna +vpravo +vrabec +vracet +vrah +vrata +vrba +vrcholek +vrhat +vrstva +vrtule +vsadit +vstoupit +vstup +vtip +vybavit +vybrat +vychovat +vydat +vydra +vyfotit +vyhledat +vyhnout +vyhodit +vyhradit +vyhubit +vyjasnit +vyjet +vyjmout +vyklopit +vykonat +vylekat +vymazat +vymezit +vymizet +vymyslet +vynechat +vynikat +vynutit +vypadat +vyplatit +vypravit +vypustit +vyrazit +vyrovnat +vyrvat +vyslovit +vysoko +vystavit +vysunout +vysypat +vytasit +vytesat +vytratit +vyvinout +vyvolat +vyvrhel +vyzdobit +vyznat +vzadu +vzbudit +vzchopit +vzdor +vzduch +vzdychat +vzestup +vzhledem +vzkaz +vzlykat +vznik +vzorek +vzpoura +vztah +vztek +xylofon +zabrat +zabydlet +zachovat +zadarmo +zadusit +zafoukat +zahltit +zahodit +zahrada +zahynout +zajatec +zajet +zajistit +zaklepat +zakoupit +zalepit +zamezit +zamotat +zamyslet +zanechat +zanikat +zaplatit +zapojit +zapsat +zarazit +zastavit +zasunout +zatajit +zatemnit +zatknout +zaujmout +zavalit +zavelet +zavinit +zavolat +zavrtat +zazvonit +zbavit +zbrusu +zbudovat +zbytek +zdaleka +zdarma +zdatnost +zdivo +zdobit +zdroj +zdvih +zdymadlo +zelenina +zeman +zemina +zeptat +zezadu +zezdola +zhatit +zhltnout +zhluboka +zhotovit +zhruba +zima +zimnice +zjemnit +zklamat +zkoumat +zkratka +zkumavka +zlato +zlehka +zloba +zlom +zlost +zlozvyk +zmapovat +zmar +zmatek +zmije +zmizet +zmocnit +zmodrat +zmrzlina +zmutovat +znak +znalost +znamenat +znovu +zobrazit +zotavit +zoubek +zoufale +zplodit +zpomalit +zprava +zprostit +zprudka +zprvu +zrada +zranit +zrcadlo +zrnitost +zrno +zrovna +zrychlit +zrzavost +zticha +ztratit +zubovina +zubr +zvednout +zvenku +zvesela +zvon +zvrat +zvukovod +zvyk diff --git a/btcrecover/wordlists/bip39-pt.txt b/btcrecover/wordlists/bip39-pt.txt new file mode 100644 index 000000000..4a8910550 --- /dev/null +++ b/btcrecover/wordlists/bip39-pt.txt @@ -0,0 +1,2048 @@ +abacate +abaixo +abalar +abater +abduzir +abelha +aberto +abismo +abotoar +abranger +abreviar +abrigar +abrupto +absinto +absoluto +absurdo +abutre +acabado +acalmar +acampar +acanhar +acaso +aceitar +acelerar +acenar +acervo +acessar +acetona +achatar +acidez +acima +acionado +acirrar +aclamar +aclive +acolhida +acomodar +acoplar +acordar +acumular +acusador +adaptar +adega +adentro +adepto +adequar +aderente +adesivo +adeus +adiante +aditivo +adjetivo +adjunto +admirar +adorar +adquirir +adubo +adverso +advogado +aeronave +afastar +aferir +afetivo +afinador +afivelar +aflito +afluente +afrontar +agachar +agarrar +agasalho +agenciar +agilizar +agiota +agitado +agora +agradar +agreste +agrupar +aguardar +agulha +ajoelhar +ajudar +ajustar +alameda +alarme +alastrar +alavanca +albergue +albino +alcatra +aldeia +alecrim +alegria +alertar +alface +alfinete +algum +alheio +aliar +alicate +alienar +alinhar +aliviar +almofada +alocar +alpiste +alterar +altitude +alucinar +alugar +aluno +alusivo +alvo +amaciar +amador +amarelo +amassar +ambas +ambiente +ameixa +amenizar +amido +amistoso +amizade +amolador +amontoar +amoroso +amostra +amparar +ampliar +ampola +anagrama +analisar +anarquia +anatomia +andaime +anel +anexo +angular +animar +anjo +anomalia +anotado +ansioso +anterior +anuidade +anunciar +anzol +apagador +apalpar +apanhado +apego +apelido +apertada +apesar +apetite +apito +aplauso +aplicada +apoio +apontar +aposta +aprendiz +aprovar +aquecer +arame +aranha +arara +arcada +ardente +areia +arejar +arenito +aresta +argiloso +argola +arma +arquivo +arraial +arrebate +arriscar +arroba +arrumar +arsenal +arterial +artigo +arvoredo +asfaltar +asilado +aspirar +assador +assinar +assoalho +assunto +astral +atacado +atadura +atalho +atarefar +atear +atender +aterro +ateu +atingir +atirador +ativo +atoleiro +atracar +atrevido +atriz +atual +atum +auditor +aumentar +aura +aurora +autismo +autoria +autuar +avaliar +avante +avaria +avental +avesso +aviador +avisar +avulso +axila +azarar +azedo +azeite +azulejo +babar +babosa +bacalhau +bacharel +bacia +bagagem +baiano +bailar +baioneta +bairro +baixista +bajular +baleia +baliza +balsa +banal +bandeira +banho +banir +banquete +barato +barbado +baronesa +barraca +barulho +baseado +bastante +batata +batedor +batida +batom +batucar +baunilha +beber +beijo +beirada +beisebol +beldade +beleza +belga +beliscar +bendito +bengala +benzer +berimbau +berlinda +berro +besouro +bexiga +bezerro +bico +bicudo +bienal +bifocal +bifurcar +bigorna +bilhete +bimestre +bimotor +biologia +biombo +biosfera +bipolar +birrento +biscoito +bisneto +bispo +bissexto +bitola +bizarro +blindado +bloco +bloquear +boato +bobagem +bocado +bocejo +bochecha +boicotar +bolada +boletim +bolha +bolo +bombeiro +bonde +boneco +bonita +borbulha +borda +boreal +borracha +bovino +boxeador +branco +brasa +braveza +breu +briga +brilho +brincar +broa +brochura +bronzear +broto +bruxo +bucha +budismo +bufar +bule +buraco +busca +busto +buzina +cabana +cabelo +cabide +cabo +cabrito +cacau +cacetada +cachorro +cacique +cadastro +cadeado +cafezal +caiaque +caipira +caixote +cajado +caju +calafrio +calcular +caldeira +calibrar +calmante +calota +camada +cambista +camisa +camomila +campanha +camuflar +canavial +cancelar +caneta +canguru +canhoto +canivete +canoa +cansado +cantar +canudo +capacho +capela +capinar +capotar +capricho +captador +capuz +caracol +carbono +cardeal +careca +carimbar +carneiro +carpete +carreira +cartaz +carvalho +casaco +casca +casebre +castelo +casulo +catarata +cativar +caule +causador +cautelar +cavalo +caverna +cebola +cedilha +cegonha +celebrar +celular +cenoura +censo +centeio +cercar +cerrado +certeiro +cerveja +cetim +cevada +chacota +chaleira +chamado +chapada +charme +chatice +chave +chefe +chegada +cheiro +cheque +chicote +chifre +chinelo +chocalho +chover +chumbo +chutar +chuva +cicatriz +ciclone +cidade +cidreira +ciente +cigana +cimento +cinto +cinza +ciranda +circuito +cirurgia +citar +clareza +clero +clicar +clone +clube +coado +coagir +cobaia +cobertor +cobrar +cocada +coelho +coentro +coeso +cogumelo +coibir +coifa +coiote +colar +coleira +colher +colidir +colmeia +colono +coluna +comando +combinar +comentar +comitiva +comover +complexo +comum +concha +condor +conectar +confuso +congelar +conhecer +conjugar +consumir +contrato +convite +cooperar +copeiro +copiador +copo +coquetel +coragem +cordial +corneta +coronha +corporal +correio +cortejo +coruja +corvo +cosseno +costela +cotonete +couro +couve +covil +cozinha +cratera +cravo +creche +credor +creme +crer +crespo +criada +criminal +crioulo +crise +criticar +crosta +crua +cruzeiro +cubano +cueca +cuidado +cujo +culatra +culminar +culpar +cultura +cumprir +cunhado +cupido +curativo +curral +cursar +curto +cuspir +custear +cutelo +damasco +datar +debater +debitar +deboche +debulhar +decalque +decimal +declive +decote +decretar +dedal +dedicado +deduzir +defesa +defumar +degelo +degrau +degustar +deitado +deixar +delator +delegado +delinear +delonga +demanda +demitir +demolido +dentista +depenado +depilar +depois +depressa +depurar +deriva +derramar +desafio +desbotar +descanso +desenho +desfiado +desgaste +desigual +deslize +desmamar +desova +despesa +destaque +desviar +detalhar +detentor +detonar +detrito +deusa +dever +devido +devotado +dezena +diagrama +dialeto +didata +difuso +digitar +dilatado +diluente +diminuir +dinastia +dinheiro +diocese +direto +discreta +disfarce +disparo +disquete +dissipar +distante +ditador +diurno +diverso +divisor +divulgar +dizer +dobrador +dolorido +domador +dominado +donativo +donzela +dormente +dorsal +dosagem +dourado +doutor +drenagem +drible +drogaria +duelar +duende +dueto +duplo +duquesa +durante +duvidoso +eclodir +ecoar +ecologia +edificar +edital +educado +efeito +efetivar +ejetar +elaborar +eleger +eleitor +elenco +elevador +eliminar +elogiar +embargo +embolado +embrulho +embutido +emenda +emergir +emissor +empatia +empenho +empinado +empolgar +emprego +empurrar +emulador +encaixe +encenado +enchente +encontro +endeusar +endossar +enfaixar +enfeite +enfim +engajado +engenho +englobar +engomado +engraxar +enguia +enjoar +enlatar +enquanto +enraizar +enrolado +enrugar +ensaio +enseada +ensino +ensopado +entanto +enteado +entidade +entortar +entrada +entulho +envergar +enviado +envolver +enxame +enxerto +enxofre +enxuto +epiderme +equipar +ereto +erguido +errata +erva +ervilha +esbanjar +esbelto +escama +escola +escrita +escuta +esfinge +esfolar +esfregar +esfumado +esgrima +esmalte +espanto +espelho +espiga +esponja +espreita +espumar +esquerda +estaca +esteira +esticar +estofado +estrela +estudo +esvaziar +etanol +etiqueta +euforia +europeu +evacuar +evaporar +evasivo +eventual +evidente +evoluir +exagero +exalar +examinar +exato +exausto +excesso +excitar +exclamar +executar +exemplo +exibir +exigente +exonerar +expandir +expelir +expirar +explanar +exposto +expresso +expulsar +externo +extinto +extrato +fabricar +fabuloso +faceta +facial +fada +fadiga +faixa +falar +falta +familiar +fandango +fanfarra +fantoche +fardado +farelo +farinha +farofa +farpa +fartura +fatia +fator +favorita +faxina +fazenda +fechado +feijoada +feirante +felino +feminino +fenda +feno +fera +feriado +ferrugem +ferver +festejar +fetal +feudal +fiapo +fibrose +ficar +ficheiro +figurado +fileira +filho +filme +filtrar +firmeza +fisgada +fissura +fita +fivela +fixador +fixo +flacidez +flamingo +flanela +flechada +flora +flutuar +fluxo +focal +focinho +fofocar +fogo +foguete +foice +folgado +folheto +forjar +formiga +forno +forte +fosco +fossa +fragata +fralda +frango +frasco +fraterno +freira +frente +fretar +frieza +friso +fritura +fronha +frustrar +fruteira +fugir +fulano +fuligem +fundar +fungo +funil +furador +furioso +futebol +gabarito +gabinete +gado +gaiato +gaiola +gaivota +galega +galho +galinha +galocha +ganhar +garagem +garfo +gargalo +garimpo +garoupa +garrafa +gasoduto +gasto +gata +gatilho +gaveta +gazela +gelado +geleia +gelo +gemada +gemer +gemido +generoso +gengiva +genial +genoma +genro +geologia +gerador +germinar +gesso +gestor +ginasta +gincana +gingado +girafa +girino +glacial +glicose +global +glorioso +goela +goiaba +golfe +golpear +gordura +gorjeta +gorro +gostoso +goteira +governar +gracejo +gradual +grafite +gralha +grampo +granada +gratuito +graveto +graxa +grego +grelhar +greve +grilo +grisalho +gritaria +grosso +grotesco +grudado +grunhido +gruta +guache +guarani +guaxinim +guerrear +guiar +guincho +guisado +gula +guloso +guru +habitar +harmonia +haste +haver +hectare +herdar +heresia +hesitar +hiato +hibernar +hidratar +hiena +hino +hipismo +hipnose +hipoteca +hoje +holofote +homem +honesto +honrado +hormonal +hospedar +humorado +iate +ideia +idoso +ignorado +igreja +iguana +ileso +ilha +iludido +iluminar +ilustrar +imagem +imediato +imenso +imersivo +iminente +imitador +imortal +impacto +impedir +implante +impor +imprensa +impune +imunizar +inalador +inapto +inativo +incenso +inchar +incidir +incluir +incolor +indeciso +indireto +indutor +ineficaz +inerente +infantil +infestar +infinito +inflamar +informal +infrator +ingerir +inibido +inicial +inimigo +injetar +inocente +inodoro +inovador +inox +inquieto +inscrito +inseto +insistir +inspetor +instalar +insulto +intacto +integral +intimar +intocado +intriga +invasor +inverno +invicto +invocar +iogurte +iraniano +ironizar +irreal +irritado +isca +isento +isolado +isqueiro +italiano +janeiro +jangada +janta +jararaca +jardim +jarro +jasmim +jato +javali +jazida +jejum +joaninha +joelhada +jogador +joia +jornal +jorrar +jovem +juba +judeu +judoca +juiz +julgador +julho +jurado +jurista +juro +justa +labareda +laboral +lacre +lactante +ladrilho +lagarta +lagoa +laje +lamber +lamentar +laminar +lampejo +lanche +lapidar +lapso +laranja +lareira +largura +lasanha +lastro +lateral +latido +lavanda +lavoura +lavrador +laxante +lazer +lealdade +lebre +legado +legendar +legista +leigo +leiloar +leitura +lembrete +leme +lenhador +lentilha +leoa +lesma +leste +letivo +letreiro +levar +leveza +levitar +liberal +libido +liderar +ligar +ligeiro +limitar +limoeiro +limpador +linda +linear +linhagem +liquidez +listagem +lisura +litoral +livro +lixa +lixeira +locador +locutor +lojista +lombo +lona +longe +lontra +lorde +lotado +loteria +loucura +lousa +louvar +luar +lucidez +lucro +luneta +lustre +lutador +luva +macaco +macete +machado +macio +madeira +madrinha +magnata +magreza +maior +mais +malandro +malha +malote +maluco +mamilo +mamoeiro +mamute +manada +mancha +mandato +manequim +manhoso +manivela +manobrar +mansa +manter +manusear +mapeado +maquinar +marcador +maresia +marfim +margem +marinho +marmita +maroto +marquise +marreco +martelo +marujo +mascote +masmorra +massagem +mastigar +matagal +materno +matinal +matutar +maxilar +medalha +medida +medusa +megafone +meiga +melancia +melhor +membro +memorial +menino +menos +mensagem +mental +merecer +mergulho +mesada +mesclar +mesmo +mesquita +mestre +metade +meteoro +metragem +mexer +mexicano +micro +migalha +migrar +milagre +milenar +milhar +mimado +minerar +minhoca +ministro +minoria +miolo +mirante +mirtilo +misturar +mocidade +moderno +modular +moeda +moer +moinho +moita +moldura +moleza +molho +molinete +molusco +montanha +moqueca +morango +morcego +mordomo +morena +mosaico +mosquete +mostarda +motel +motim +moto +motriz +muda +muito +mulata +mulher +multar +mundial +munido +muralha +murcho +muscular +museu +musical +nacional +nadador +naja +namoro +narina +narrado +nascer +nativa +natureza +navalha +navegar +navio +neblina +nebuloso +negativa +negociar +negrito +nervoso +neta +neural +nevasca +nevoeiro +ninar +ninho +nitidez +nivelar +nobreza +noite +noiva +nomear +nominal +nordeste +nortear +notar +noticiar +noturno +novelo +novilho +novo +nublado +nudez +numeral +nupcial +nutrir +nuvem +obcecado +obedecer +objetivo +obrigado +obscuro +obstetra +obter +obturar +ocidente +ocioso +ocorrer +oculista +ocupado +ofegante +ofensiva +oferenda +oficina +ofuscado +ogiva +olaria +oleoso +olhar +oliveira +ombro +omelete +omisso +omitir +ondulado +oneroso +ontem +opcional +operador +oponente +oportuno +oposto +orar +orbitar +ordem +ordinal +orfanato +orgasmo +orgulho +oriental +origem +oriundo +orla +ortodoxo +orvalho +oscilar +ossada +osso +ostentar +otimismo +ousadia +outono +outubro +ouvido +ovelha +ovular +oxidar +oxigenar +pacato +paciente +pacote +pactuar +padaria +padrinho +pagar +pagode +painel +pairar +paisagem +palavra +palestra +palheta +palito +palmada +palpitar +pancada +panela +panfleto +panqueca +pantanal +papagaio +papelada +papiro +parafina +parcial +pardal +parede +partida +pasmo +passado +pastel +patamar +patente +patinar +patrono +paulada +pausar +peculiar +pedalar +pedestre +pediatra +pedra +pegada +peitoral +peixe +pele +pelicano +penca +pendurar +peneira +penhasco +pensador +pente +perceber +perfeito +pergunta +perito +permitir +perna +perplexo +persiana +pertence +peruca +pescado +pesquisa +pessoa +petiscar +piada +picado +piedade +pigmento +pilastra +pilhado +pilotar +pimenta +pincel +pinguim +pinha +pinote +pintar +pioneiro +pipoca +piquete +piranha +pires +pirueta +piscar +pistola +pitanga +pivete +planta +plaqueta +platina +plebeu +plumagem +pluvial +pneu +poda +poeira +poetisa +polegada +policiar +poluente +polvilho +pomar +pomba +ponderar +pontaria +populoso +porta +possuir +postal +pote +poupar +pouso +povoar +praia +prancha +prato +praxe +prece +predador +prefeito +premiar +prensar +preparar +presilha +pretexto +prevenir +prezar +primata +princesa +prisma +privado +processo +produto +profeta +proibido +projeto +prometer +propagar +prosa +protetor +provador +publicar +pudim +pular +pulmonar +pulseira +punhal +punir +pupilo +pureza +puxador +quadra +quantia +quarto +quase +quebrar +queda +queijo +quente +querido +quimono +quina +quiosque +rabanada +rabisco +rachar +racionar +radial +raiar +rainha +raio +raiva +rajada +ralado +ramal +ranger +ranhura +rapadura +rapel +rapidez +raposa +raquete +raridade +rasante +rascunho +rasgar +raspador +rasteira +rasurar +ratazana +ratoeira +realeza +reanimar +reaver +rebaixar +rebelde +rebolar +recado +recente +recheio +recibo +recordar +recrutar +recuar +rede +redimir +redonda +reduzida +reenvio +refinar +refletir +refogar +refresco +refugiar +regalia +regime +regra +reinado +reitor +rejeitar +relativo +remador +remendo +remorso +renovado +reparo +repelir +repleto +repolho +represa +repudiar +requerer +resenha +resfriar +resgatar +residir +resolver +respeito +ressaca +restante +resumir +retalho +reter +retirar +retomada +retratar +revelar +revisor +revolta +riacho +rica +rigidez +rigoroso +rimar +ringue +risada +risco +risonho +robalo +rochedo +rodada +rodeio +rodovia +roedor +roleta +romano +roncar +rosado +roseira +rosto +rota +roteiro +rotina +rotular +rouco +roupa +roxo +rubro +rugido +rugoso +ruivo +rumo +rupestre +russo +sabor +saciar +sacola +sacudir +sadio +safira +saga +sagrada +saibro +salada +saleiro +salgado +saliva +salpicar +salsicha +saltar +salvador +sambar +samurai +sanar +sanfona +sangue +sanidade +sapato +sarda +sargento +sarjeta +saturar +saudade +saxofone +sazonal +secar +secular +seda +sedento +sediado +sedoso +sedutor +segmento +segredo +segundo +seiva +seleto +selvagem +semanal +semente +senador +senhor +sensual +sentado +separado +sereia +seringa +serra +servo +setembro +setor +sigilo +silhueta +silicone +simetria +simpatia +simular +sinal +sincero +singular +sinopse +sintonia +sirene +siri +situado +soberano +sobra +socorro +sogro +soja +solda +soletrar +solteiro +sombrio +sonata +sondar +sonegar +sonhador +sono +soprano +soquete +sorrir +sorteio +sossego +sotaque +soterrar +sovado +sozinho +suavizar +subida +submerso +subsolo +subtrair +sucata +sucesso +suco +sudeste +sufixo +sugador +sugerir +sujeito +sulfato +sumir +suor +superior +suplicar +suposto +suprimir +surdina +surfista +surpresa +surreal +surtir +suspiro +sustento +tabela +tablete +tabuada +tacho +tagarela +talher +talo +talvez +tamanho +tamborim +tampa +tangente +tanto +tapar +tapioca +tardio +tarefa +tarja +tarraxa +tatuagem +taurino +taxativo +taxista +teatral +tecer +tecido +teclado +tedioso +teia +teimar +telefone +telhado +tempero +tenente +tensor +tentar +termal +terno +terreno +tese +tesoura +testado +teto +textura +texugo +tiara +tigela +tijolo +timbrar +timidez +tingido +tinteiro +tiragem +titular +toalha +tocha +tolerar +tolice +tomada +tomilho +tonel +tontura +topete +tora +torcido +torneio +torque +torrada +torto +tostar +touca +toupeira +toxina +trabalho +tracejar +tradutor +trafegar +trajeto +trama +trancar +trapo +traseiro +tratador +travar +treino +tremer +trepidar +trevo +triagem +tribo +triciclo +tridente +trilogia +trindade +triplo +triturar +triunfal +trocar +trombeta +trova +trunfo +truque +tubular +tucano +tudo +tulipa +tupi +turbo +turma +turquesa +tutelar +tutorial +uivar +umbigo +unha +unidade +uniforme +urologia +urso +urtiga +urubu +usado +usina +usufruir +vacina +vadiar +vagaroso +vaidoso +vala +valente +validade +valores +vantagem +vaqueiro +varanda +vareta +varrer +vascular +vasilha +vassoura +vazar +vazio +veado +vedar +vegetar +veicular +veleiro +velhice +veludo +vencedor +vendaval +venerar +ventre +verbal +verdade +vereador +vergonha +vermelho +verniz +versar +vertente +vespa +vestido +vetorial +viaduto +viagem +viajar +viatura +vibrador +videira +vidraria +viela +viga +vigente +vigiar +vigorar +vilarejo +vinco +vinheta +vinil +violeta +virada +virtude +visitar +visto +vitral +viveiro +vizinho +voador +voar +vogal +volante +voleibol +voltagem +volumoso +vontade +vulto +vuvuzela +xadrez +xarope +xeque +xeretar +xerife +xingar +zangado +zarpar +zebu +zelador +zombar +zoologia +zumbido diff --git a/btcrecover/wordlists/blockchainpassword_words_v2-en.txt b/btcrecover/wordlists/blockchainpassword_words_v2-en.txt new file mode 100644 index 000000000..6bf412ce1 --- /dev/null +++ b/btcrecover/wordlists/blockchainpassword_words_v2-en.txt @@ -0,0 +1,1626 @@ +like +just +love +know +never +want +time +out +there +make +look +eye +down +only +think +heart +back +then +into +about +more +away +still +them +take +thing +even +through +long +always +world +too +friend +tell +try +hand +thought +over +here +other +need +smile +again +much +cry +been +night +ever +little +said +end +some +those +around +mind +people +girl +leave +dream +left +turn +myself +give +nothing +really +off +before +something +find +walk +wish +good +once +place +ask +stop +keep +watch +seem +everything +wait +got +yet +made +remember +start +alone +run +hope +maybe +believe +body +hate +after +close +talk +stand +own +each +hurt +help +home +god +soul +new +many +two +inside +should +true +first +fear +mean +better +play +another +gone +change +use +wonder +someone +hair +cold +open +best +any +behind +happen +water +dark +laugh +stay +forever +name +work +show +sky +break +came +deep +door +put +black +together +upon +happy +such +great +white +matter +fill +past +please +burn +cause +enough +touch +moment +soon +voice +scream +anything +stare +sound +red +everyone +hide +kiss +truth +death +beautiful +mine +blood +broken +very +pass +next +forget +tree +wrong +air +mother +understand +lip +hit +wall +memory +sleep +free +high +realize +school +might +skin +sweet +perfect +blue +kill +breath +dance +against +fly +between +grow +strong +under +listen +bring +sometimes +speak +pull +person +become +family +begin +ground +real +small +father +sure +feet +rest +young +finally +land +across +today +different +guy +line +fire +reason +reach +second +slowly +write +eat +smell +mouth +step +learn +three +floor +promise +breathe +darkness +push +earth +guess +save +song +above +along +both +color +house +almost +sorry +anymore +brother +okay +dear +game +fade +already +apart +warm +beauty +heard +notice +question +shine +began +piece +whole +shadow +secret +street +within +finger +point +morning +whisper +child +moon +green +story +glass +kid +silence +since +soft +yourself +empty +shall +angel +answer +baby +bright +dad +path +worry +hour +drop +follow +power +war +half +flow +heaven +act +chance +fact +least +tired +children +near +quite +afraid +rise +sea +taste +window +cover +nice +trust +lot +sad +cool +force +peace +return +blind +easy +ready +roll +rose +drive +held +music +beneath +hang +mom +paint +emotion +quiet +clear +cloud +few +pretty +bird +outside +paper +picture +front +rock +simple +anyone +meant +reality +road +sense +waste +bit +leaf +thank +happiness +meet +men +smoke +truly +decide +self +age +book +form +alive +carry +escape +damn +instead +able +ice +minute +throw +catch +leg +ring +course +goodbye +lead +poem +sick +corner +desire +known +problem +remind +shoulder +suppose +toward +wave +drink +jump +woman +pretend +sister +week +human +joy +crack +grey +pray +surprise +dry +knee +less +search +bleed +caught +clean +embrace +future +king +son +sorrow +chest +hug +remain +sat +worth +blow +daddy +final +parent +tight +also +create +lonely +safe +cross +dress +evil +silent +bone +fate +perhaps +anger +class +scar +snow +tiny +tonight +continue +control +dog +edge +mirror +month +suddenly +comfort +given +loud +quickly +gaze +plan +rush +stone +town +battle +ignore +spirit +stood +stupid +yours +brown +build +dust +hey +kept +pay +phone +twist +although +ball +beyond +hidden +nose +taken +fail +float +pure +somehow +wash +wrap +angry +cheek +creature +forgotten +heat +rip +single +space +special +weak +whatever +yell +anyway +blame +job +choose +country +curse +drift +echo +figure +grew +laughter +neck +suffer +worse +yeah +disappear +foot +forward +knife +mess +somewhere +stomach +storm +beg +idea +lift +offer +breeze +field +five +often +simply +stuck +win +allow +confuse +enjoy +except +flower +seek +strength +calm +grin +gun +heavy +hill +large +ocean +shoe +sigh +straight +summer +tongue +accept +crazy +everyday +exist +grass +mistake +sent +shut +surround +table +ache +brain +destroy +heal +nature +shout +sign +stain +choice +doubt +glance +glow +mountain +queen +stranger +throat +tomorrow +city +either +fish +flame +rather +shape +spin +spread +ash +distance +finish +image +imagine +important +nobody +shatter +warmth +became +feed +flesh +funny +lust +shirt +trouble +yellow +attention +bare +bite +money +protect +amaze +appear +born +choke +completely +daughter +fresh +friendship +gentle +probably +six +deserve +expect +grab +middle +nightmare +river +thousand +weight +worst +wound +barely +bottle +cream +regret +relationship +stick +test +crush +endless +fault +itself +rule +spill +art +circle +join +kick +mask +master +passion +quick +raise +smooth +unless +wander +actually +broke +chair +deal +favorite +gift +note +number +sweat +box +chill +clothes +lady +mark +park +poor +sadness +tie +animal +belong +brush +consume +dawn +forest +innocent +pen +pride +stream +thick +clay +complete +count +draw +faith +press +silver +struggle +surface +taught +teach +wet +bless +chase +climb +enter +letter +melt +metal +movie +stretch +swing +vision +wife +beside +crash +forgot +guide +haunt +joke +knock +plant +pour +prove +reveal +steal +stuff +trip +wood +wrist +bother +bottom +crawl +crowd +fix +forgive +frown +grace +loose +lucky +party +release +surely +survive +teacher +gently +grip +speed +suicide +travel +treat +vein +written +cage +chain +conversation +date +enemy +however +interest +million +page +pink +proud +sway +themselves +winter +church +cruel +cup +demon +experience +freedom +pair +pop +purpose +respect +shoot +softly +state +strange +bar +birth +curl +dirt +excuse +lord +lovely +monster +order +pack +pants +pool +scene +seven +shame +slide +ugly +among +blade +blonde +closet +creek +deny +drug +eternity +gain +grade +handle +key +linger +pale +prepare +swallow +swim +tremble +wheel +won +cast +cigarette +claim +college +direction +dirty +gather +ghost +hundred +loss +lung +orange +present +swear +swirl +twice +wild +bitter +blanket +doctor +everywhere +flash +grown +knowledge +numb +pressure +radio +repeat +ruin +spend +unknown +buy +clock +devil +early +false +fantasy +pound +precious +refuse +sheet +teeth +welcome +add +ahead +block +bury +caress +content +depth +despite +distant +marry +purple +threw +whenever +bomb +dull +easily +grasp +hospital +innocence +normal +receive +reply +rhyme +shade +someday +sword +toe +visit +asleep +bought +center +consider +flat +hero +history +ink +insane +muscle +mystery +pocket +reflection +shove +silently +smart +soldier +spot +stress +train +type +view +whether +bus +energy +explain +holy +hunger +inch +magic +mix +noise +nowhere +prayer +presence +shock +snap +spider +study +thunder +trail +admit +agree +bag +bang +bound +butterfly +cute +exactly +explode +familiar +fold +further +pierce +reflect +scent +selfish +sharp +sink +spring +stumble +universe +weep +women +wonderful +action +ancient +attempt +avoid +birthday +branch +chocolate +core +depress +drunk +especially +focus +fruit +honest +match +palm +perfectly +pillow +pity +poison +roar +shift +slightly +thump +truck +tune +twenty +unable +wipe +wrote +coat +constant +dinner +drove +egg +eternal +flight +flood +frame +freak +gasp +glad +hollow +motion +peer +plastic +root +screen +season +sting +strike +team +unlike +victim +volume +warn +weird +attack +await +awake +built +charm +crave +despair +fought +grant +grief +horse +limit +message +ripple +sanity +scatter +serve +split +string +trick +annoy +blur +boat +brave +clearly +cling +connect +fist +forth +imagination +iron +jock +judge +lesson +milk +misery +nail +naked +ourselves +poet +possible +princess +sail +size +snake +society +stroke +torture +toss +trace +wise +bloom +bullet +cell +check +cost +darling +during +footstep +fragile +hallway +hardly +horizon +invisible +journey +midnight +mud +nod +pause +relax +shiver +sudden +value +youth +abuse +admire +blink +breast +bruise +constantly +couple +creep +curve +difference +dumb +emptiness +gotta +honor +plain +planet +recall +rub +ship +slam +soar +somebody +tightly +weather +adore +approach +bond +bread +burst +candle +coffee +cousin +crime +desert +flutter +frozen +grand +heel +hello +language +level +movement +pleasure +powerful +random +rhythm +settle +silly +slap +sort +spoken +steel +threaten +tumble +upset +aside +awkward +bee +blank +board +button +card +carefully +complain +crap +deeply +discover +drag +dread +effort +entire +fairy +giant +gotten +greet +illusion +jeans +leap +liquid +march +mend +nervous +nine +replace +rope +spine +stole +terror +accident +apple +balance +boom +childhood +collect +demand +depression +eventually +faint +glare +goal +group +honey +kitchen +laid +limb +machine +mere +mold +murder +nerve +painful +poetry +prince +rabbit +shelter +shore +shower +soothe +stair +steady +sunlight +tangle +tease +treasure +uncle +begun +bliss +canvas +cheer +claw +clutch +commit +crimson +crystal +delight +doll +existence +express +fog +football +gay +goose +guard +hatred +illuminate +mass +math +mourn +rich +rough +skip +stir +student +style +support +thorn +tough +yard +yearn +yesterday +advice +appreciate +autumn +bank +beam +bowl +capture +carve +collapse +confusion +creation +dove +feather +girlfriend +glory +government +harsh +hop +inner +loser +moonlight +neighbor +neither +peach +pig +praise +screw +shield +shimmer +sneak +stab +subject +throughout +thrown +tower +twirl +wow +army +arrive +bathroom +bump +cease +cookie +couch +courage +dim +guilt +howl +hum +husband +insult +led +lunch +mock +mostly +natural +nearly +needle +nerd +peaceful +perfection +pile +price +remove +roam +sanctuary +serious +shiny +shook +sob +stolen +tap +vain +void +warrior +wrinkle +affection +apologize +blossom +bounce +bridge +cheap +crumble +decision +descend +desperately +dig +dot +flip +frighten +heartbeat +huge +lazy +lick +odd +opinion +process +puzzle +quietly +retreat +score +sentence +separate +situation +skill +soak +square +stray +taint +task +tide +underneath +veil +whistle +anywhere +bedroom +bid +bloody +burden +careful +compare +concern +curtain +decay +defeat +describe +double +dreamer +driver +dwell +evening +flare +flicker +grandma +guitar +harm +horrible +hungry +indeed +lace +melody +monkey +nation +object +obviously +rainbow +salt +scratch +shown +shy +stage +stun +third +tickle +useless +weakness +worship +worthless +afternoon +beard +boyfriend +bubble +busy +certain +chin +concrete +desk +diamond +doom +drawn +due +felicity +freeze +frost +garden +glide +harmony +hopefully +hunt +jealous +lightning +mama +mercy +peel +physical +position +pulse +punch +quit +rant +respond +salty +sane +satisfy +savior +sheep +slept +social +sport +tuck +utter +valley +wolf +aim +alas +alter +arrow +awaken +beaten +belief +brand +ceiling +cheese +clue +confidence +connection +daily +disguise +eager +erase +essence +everytime +expression +fan +flag +flirt +foul +fur +giggle +glorious +ignorance +law +lifeless +measure +mighty +muse +north +opposite +paradise +patience +patient +pencil +petal +plate +ponder +possibly +practice +slice +spell +stock +strife +strip +suffocate +suit +tender +tool +trade +velvet +verse +waist +witch +aunt +bench +bold +cap +certainly +click +companion +creator +dart +delicate +determine +dish +dragon +drama +drum +dude +everybody +feast +forehead +former +fright +fully +gas +hook +hurl +invite +juice +manage +moral +possess +raw +rebel +royal +scale +scary +several +slight +stubborn +swell +talent +tea +terrible +thread +torment +trickle +usually +vast +violence +weave +acid +agony +ashamed +awe +belly +blend +blush +character +cheat +common +company +coward +creak +danger +deadly +defense +define +depend +desperate +destination +dew +duck +dusty +embarrass +engine +example +explore +foe +freely +frustrate +generation +glove +guilty +health +hurry +idiot +impossible +inhale +jaw +kingdom +mention +mist +moan +mumble +mutter +observe +ode +pathetic +pattern +pie +prefer +puff +rape +rare +revenge +rude +scrape +spiral +squeeze +strain +sunset +suspend +sympathy +thigh +throne +total +unseen +weapon +weary \ No newline at end of file diff --git a/btcrecover/wordlists/blockchainpassword_words_v3-en.txt b/btcrecover/wordlists/blockchainpassword_words_v3-en.txt new file mode 100644 index 000000000..8bbdb073d --- /dev/null +++ b/btcrecover/wordlists/blockchainpassword_words_v3-en.txt @@ -0,0 +1,65591 @@ +a +aa +aaa +aaaa +aachen +aah +aahs +aam +aardvark +aargh +aaron +aaronson +aas +ab +aba +ababa +abacha +aback +abacus +abad +abalone +abandon +abandoned +abandoning +abandonment +abandons +abas +abase +abashed +abate +abated +abatement +abatements +abating +abba +abbas +abbate +abbe +abbey +abbie +abbot +abbott +abboud +abbreviate +abbreviated +abbreviating +abbreviation +abbreviations +abby +abc +abd +abdallah +abdel +abdicate +abdicated +abdicating +abdication +abdomen +abdominal +abdominals +abduct +abducted +abducting +abduction +abductions +abductor +abductors +abducts +abdul +abdulaziz +abdulla +abdullah +abe +abed +abel +abele +abell +abelson +abend +abercrombie +aberdeen +abernathy +abernethy +aberrant +aberration +aberrations +abet +abetted +abetting +abeyance +abhor +abhorred +abhorrence +abhorrent +abhors +abide +abided +abides +abiding +abigail +abilene +abilities +ability +abingdon +abiola +abject +abjure +abjured +abkhazia +ablation +ablaze +able +abler +ablest +ablutions +ably +abn +abnegation +abner +abnormal +abnormalities +abnormality +abnormally +abo +aboard +abode +abolish +abolished +abolishes +abolishing +abolition +abolitionism +abolitionist +abolitionists +abominable +abomination +abominations +aboriginal +aborigine +aborigines +abort +aborted +abortifacient +aborting +abortion +abortionist +abortionists +abortions +abortive +aborts +abou +abound +abounded +abounds +about +abouts +above +aboveboard +abovementioned +abp +abra +abracadabra +abraham +abrahams +abram +abrams +abramson +abrasion +abrasions +abrasive +abrasives +abreast +abreu +abri +abridge +abridged +abridgement +abridging +abridgment +abril +abroad +abrogate +abrogated +abrogating +abrogation +abrupt +abruptly +abruptness +abs +abscess +abscond +absconded +absconding +absence +absences +absense +absent +absentee +absenteeism +absentees +absentia +absolut +absolute +absolutely +absoluteness +absolutes +absolution +absolutism +absolutist +absolutists +absolve +absolved +absolves +absolving +absorb +absorbed +absorbency +absorbent +absorber +absorbers +absorbing +absorbs +absorption +abstain +abstained +abstaining +abstention +abstentions +abstinence +abstinent +abstract +abstracted +abstraction +abstractions +abstracts +abstruse +absurd +absurdist +absurdities +absurdity +absurdly +absurdum +abt +abu +abundance +abundances +abundant +abundantly +abuse +abused +abuser +abusers +abuses +abusing +abusive +abusively +abut +abuts +abutted +abutting +abuzz +abysmal +abysmally +abyss +ac +aca +acacia +acad +academe +academia +academic +academically +academician +academicians +academics +academie +academies +academy +acadia +acadian +acanthus +acapulco +acc +acca +accede +acceded +acceding +accel +accelerant +accelerate +accelerated +accelerates +accelerating +acceleration +accelerations +accelerator +accelerators +accelerometer +accelerometers +accent +accented +accenting +accents +accentuate +accentuated +accentuates +accentuating +accept +acceptability +acceptable +acceptably +acceptance +acceptances +accepted +accepting +accepts +access +accessed +accesses +accessibility +accessible +accessing +accession +accessories +accessorize +accessorized +accessory +accident +accidental +accidentally +accidently +accidents +acclaim +acclaimed +acclamation +acclimate +acclimated +acclimation +acclimatization +acclimatize +accolade +accolades +accommodate +accommodated +accommodates +accommodating +accommodation +accommodations +accomodate +accomodation +accomodations +accompanied +accompanies +accompaniment +accompaniments +accompanist +accompany +accompanying +accompli +accomplice +accomplices +accomplis +accomplish +accomplished +accomplishes +accomplishing +accomplishment +accomplishments +accor +accord +accordance +accorded +according +accordingly +accordion +accordionist +accordions +accords +accosted +accosting +account +accountability +accountable +accountancy +accountant +accountants +accounted +accounting +accounts +accouterments +accoutrements +accra +accredit +accreditation +accredited +accrediting +accreted +accretion +accross +accrual +accruals +accrue +accrued +accrues +accruing +acct +accumulate +accumulated +accumulates +accumulating +accumulation +accumulations +accumulative +accumulator +accuracies +accuracy +accurate +accurately +accusation +accusations +accusative +accusatory +accuse +accused +accuser +accusers +accuses +accusing +accusingly +accustom +accustomed +ace +aced +acellular +acer +acerbic +aces +acetaminophen +acetate +acetic +acetone +acetyl +acetylcholine +acetylcholinesterase +acetylene +acevedo +aceves +ache +acheived +achenbach +aches +acheson +achievable +achieve +achieved +achievement +achievements +achiever +achievers +achieves +achieving +achille +achilles +aching +achingly +achtung +achy +acid +acidic +acidification +acidified +acidify +acidity +acidly +acidophilus +acidosis +acids +acing +ack +acker +ackerman +ackermann +ackman +acknowledge +acknowledged +acknowledgement +acknowledgements +acknowledges +acknowledging +acknowledgment +acknowledgments +acme +acne +acolyte +acolytes +aconite +acorn +acorns +acosta +acoustic +acoustical +acoustically +acoustics +acquaint +acquaintance +acquaintances +acquaintanceship +acquainted +acquainting +acquiesce +acquiesced +acquiescence +acquiescing +acquire +acquired +acquirer +acquirers +acquires +acquiring +acquisition +acquisitions +acquisitive +acquit +acquits +acquittal +acquittals +acquitted +acquitting +acre +acreage +acres +acrid +acrimonious +acrimony +acro +acrobat +acrobatic +acrobatics +acrobats +acronym +acronyms +acropolis +across +acrostic +acrylic +acrylics +act +acta +acted +actin +acting +action +actionable +actions +activate +activated +activates +activating +activation +activator +active +actively +actives +activism +activist +activists +activites +activities +activity +acton +actor +actors +actress +actresses +acts +actual +actuality +actualization +actualize +actualized +actually +actuarial +actuaries +actuary +actuator +actuators +actus +acuff +acuity +acumen +acupressure +acupuncture +acupuncturist +acupuncturists +acura +acute +acutely +acyclovir +ad +ada +adage +adagio +adair +adam +adamant +adamantly +adams +adamski +adamson +adana +adapt +adaptability +adaptable +adaptation +adaptations +adaptec +adapted +adapter +adapters +adapting +adaption +adaptive +adaptor +adaptors +adapts +adar +aday +adcock +add +addams +added +addenda +addendum +addendums +adder +adders +addict +addicted +addicting +addiction +addictions +addictive +addicts +adding +addington +addis +addison +addition +additional +additionally +additions +additive +additives +addled +address +addressable +addressed +addressee +addressees +addresses +addressing +adds +adduced +addy +ade +adel +adelaide +adele +adelman +adelson +adelstein +aden +adenine +adenocarcinoma +adenoids +adenosine +adept +adeptly +adepts +adequacy +adequate +adequately +adhere +adhered +adherence +adherent +adherents +adheres +adhering +adhesion +adhesive +adhesives +adidas +adieu +adina +adios +adipose +adirondack +adj +adjacent +adjective +adjectives +adjoin +adjoining +adjoins +adjourn +adjourned +adjourning +adjournment +adjourns +adjudged +adjudicate +adjudicated +adjudicating +adjudication +adjudicatory +adjunct +adjuncts +adjust +adjustable +adjusted +adjuster +adjusters +adjusting +adjustment +adjustments +adjusts +adjutant +adjuvant +adkins +adlai +adler +adm +adman +admin +administer +administered +administering +administers +administrating +administration +administrations +administrative +administratively +administrator +administrators +adminstration +adminstrator +admirable +admirably +admiral +admirals +admiralty +admiration +admire +admired +admirer +admirers +admires +admiring +admiringly +admissable +admissibility +admissible +admission +admissions +admit +admits +admittance +admitted +admittedly +admitting +admonish +admonished +admonishes +admonishing +admonishment +admonition +admonitions +adnan +ado +adobe +adolescence +adolescent +adolescents +adolf +adolfo +adolph +adonis +adopt +adopted +adoptee +adoptees +adopter +adopters +adopting +adoption +adoptions +adoptive +adopts +adorable +adoration +adore +adored +adores +adoring +adorn +adorned +adorning +adornment +adorno +adorns +adrenal +adrenalin +adrenaline +adrian +adriana +adriano +adriatic +adrienne +adrift +adroit +adroitly +ads +adulation +adult +adulterated +adulterer +adulterers +adulterous +adultery +adulthood +adults +adv +advance +advanced +advancement +advancements +advances +advancing +advantage +advantaged +advantageous +advantages +advent +adventist +adventists +adventure +adventurer +adventurers +adventures +adventuresome +adventurism +adventurous +adverb +adverbial +adverbs +adversarial +adversaries +adversary +adverse +adversely +adversities +adversity +advert +advertise +advertised +advertisement +advertisements +advertiser +advertisers +advertises +advertising +advertized +advertizing +advertorials +adverts +advice +advices +advisability +advisable +advise +advised +advisedly +advisement +adviser +advisers +advises +advising +advisor +advisories +advisors +advisory +advocacy +advocate +advocated +advocates +advocating +advocation +ae +aegean +aegis +aegon +aeneas +aeon +aer +aerial +aerially +aerie +aero +aerobatics +aerobic +aerobically +aerobics +aerodrome +aerodynamic +aerodynamics +aeronautic +aeronautical +aeronautics +aeroplane +aerosmith +aerosol +aerosols +aerospace +aerospatiale +aesop +aesthete +aesthetic +aesthetically +aestheticism +aesthetics +aetna +af +afar +afb +afer +aff +affable +affair +affaires +affairs +affect +affectation +affected +affecting +affection +affectionate +affectionately +affections +affective +affects +affidavit +affidavits +affiliate +affiliated +affiliates +affiliating +affiliation +affiliations +affinities +affinity +affirm +affirmation +affirmations +affirmative +affirmatively +affirmed +affirming +affirms +affix +affixed +affixes +affixing +affleck +afflict +afflicted +afflicting +affliction +afflictions +afflicts +affluence +affluent +afford +affordability +affordable +affordably +afforded +affording +affords +affront +affronted +affronts +afghan +afghani +afghanis +afghanistan +afghans +aficionado +aficionados +afield +afire +aflame +aflatoxin +afloat +afonso +afoot +afore +aforementioned +aforesaid +aforethought +afoul +afraid +afresh +africa +african +africanist +africans +afrikaans +afrikaner +afro +afros +aft +after +afterall +afterbirth +afterburner +afterburners +aftercare +aftereffect +aftereffects +afterglow +afterlife +aftermarket +aftermath +aftermaths +afternoon +afternoons +aftershock +aftershocks +aftertaste +afterthought +afterthoughts +afterward +afterwards +ag +aga +again +against +agamemnon +agape +agar +agarwal +agas +agassi +agate +agatha +agave +age +aged +agee +ageing +ageless +agen +agence +agencies +agency +agenda +agendas +agent +agents +ager +agers +ages +agfa +aggie +aggies +agglomerate +agglomeration +agglutination +aggrandize +aggrandizement +aggrandizing +aggravate +aggravated +aggravates +aggravating +aggravation +aggravations +aggregate +aggregated +aggregates +aggregating +aggregation +aggregations +aggression +aggressions +aggressive +aggressively +aggressiveness +aggressor +aggressors +aggrieved +agha +aghast +agile +agility +agin +agincourt +aging +agitate +agitated +agitating +agitation +agitator +agitators +aglow +agnes +agnew +agnostic +agnosticism +agnostics +agnus +ago +agog +agon +agonies +agonists +agonize +agonized +agonizing +agonizingly +agony +agora +agostino +agoura +agr +agra +agrarian +agre +agree +agreeable +agreed +agreeing +agreement +agreements +agrees +agressive +agressively +agribusiness +agricola +agricole +agricultural +agriculturally +agriculture +agriculturists +agronomist +aground +agua +ague +aguilar +aguilera +aguirre +agustin +ah +aha +ahab +aharon +ahead +ahearn +ahem +ahern +ahh +ahhh +ahi +ahimsa +ahl +ahluwalia +ahmad +ahmadi +ahmed +ahmet +ahold +ahoy +ahrens +ahs +ahuja +ahve +ai +aid +aida +aidan +aide +aided +aider +aiders +aides +aiding +aids +aiello +aiken +aikido +aikman +ail +ailed +aileen +ailerons +ailing +ailment +ailments +ails +aim +aime +aimed +aimee +aiming +aimless +aimlessly +aims +aine +air +airbag +airbags +airborne +airbrush +airbus +aircraft +aircrafts +aircrew +airdrop +airdrops +aire +aired +airedale +aires +airfare +airfares +airfield +airfields +airflow +airfoil +airforce +airframe +airfreight +airhead +airing +airless +airlift +airlifted +airlifting +airline +airliner +airliners +airlines +airlock +airmail +airman +airmen +airplane +airplanes +airport +airports +airs +airship +airships +airspace +airspaces +airspeed +airstrikes +airstrip +airstrips +airtight +airtime +airwaves +airway +airways +airworthiness +airworthy +airy +ais +aisle +aisles +ait +aitken +ajami +ajar +ajax +ajay +ak +aka +akbar +ake +aker +akerman +akers +akey +akimbo +akin +akio +akira +akita +akron +al +ala +alabama +alabaster +alacrity +aladdin +alain +alam +alameda +alamo +alamos +alan +alana +alane +alanis +alanna +alar +alarm +alarmed +alarming +alarmingly +alarmist +alarmists +alarms +alas +alaska +alaskan +alaskans +alastair +alayne +alba +albacore +alban +albania +albanian +albanians +albans +albany +albatross +albatrosses +albedo +albee +albeit +albemarle +albergo +albers +albert +alberta +albertina +albertini +alberto +alberts +albertson +albicans +albin +albino +albion +albrecht +albright +albritton +album +albumin +albums +albuquerque +albus +alcala +alcan +alcantara +alcatel +alcatraz +alchemical +alchemist +alchemists +alchemy +alchohol +alco +alcoa +alcohol +alcoholic +alcoholics +alcoholism +alcohols +alcon +alcott +alcove +alcoves +aldea +alden +alder +alderman +aldermen +alders +alderson +aldine +aldo +aldous +aldrich +aldridge +aldrin +aldus +ale +alec +alegre +alehouse +alejandro +alejo +aleksandr +alen +alena +alene +aleph +alert +alerted +alerting +alertness +alerts +ales +alessandra +alessandro +alessi +alessio +aleta +aleut +aleutian +aleutians +alewife +alex +alexa +alexander +alexanders +alexandra +alexandre +alexandria +alexei +alexia +alexis +alf +alfa +alfalfa +alfaro +alfie +alfonse +alfonso +alford +alfred +alfredo +algae +algal +algebra +algebraic +alger +algeria +algerian +algerians +algiers +algo +algonquian +algonquin +algorithm +algorithmic +algorithms +alhambra +ali +alia +alianza +alias +aliases +alibi +alibis +alice +alicea +alicia +alien +alienate +alienated +alienates +alienating +alienation +aliens +alif +alight +align +aligned +aligning +alignment +alignments +aligns +alike +alimentary +alimony +alina +aline +alison +alistair +alit +alittle +alive +alix +alka +alkali +alkaline +alkalinity +alkaloid +alkaloids +all +alla +allah +allaire +allan +allard +allay +allayed +allaying +allays +alle +alledged +allee +allegation +allegations +allege +alleged +allegedly +alleges +allegheny +allegiance +allegiances +alleging +allegorical +allegorically +allegories +allegory +allegra +allegro +allele +allen +allendale +allende +allentown +aller +allergan +allergen +allergenic +allergens +allergic +allergies +allergist +allergy +allerton +alles +alleviate +alleviated +alleviates +alleviating +alleviation +alley +alleys +alleyway +alleyways +allez +allgemeine +alliance +alliances +alliant +allie +allied +allies +alligator +alligators +allis +allison +alliteration +alliterative +allman +allocate +allocated +allocates +allocating +allocation +allocations +allocator +allot +alloted +allotment +allotments +allots +allotted +allotting +allow +allowable +allowance +allowances +allowed +allowing +allows +alloy +alloys +allred +allright +alls +allspice +allstate +allude +alluded +alludes +alluding +allure +alluring +allusion +allusions +allusive +alluvial +alluvium +ally +allying +allyn +allyson +alma +almaden +almanac +almeida +almighty +almon +almond +almonds +almost +alms +almy +aln +alo +aloe +aloft +aloha +alois +alomar +alon +alone +aloneness +along +alongside +alonso +alonzo +aloof +aloofness +alot +alou +aloud +aloysius +alpaca +alpenglow +alper +alpers +alpert +alpha +alphabet +alphabetic +alphabetical +alphabetically +alphanumeric +alpharetta +alphas +alphonse +alpine +alpo +alps +already +alright +alrighty +als +alsace +alsatian +also +alsop +alston +alt +alta +altair +altar +altarpiece +altars +alte +alter +alteration +alterations +altercation +altercations +altered +altering +alterman +alternate +alternated +alternately +alternates +alternating +alternation +alternative +alternatively +alternatives +alternator +alternators +alters +althea +altho +althoff +although +althought +altimeter +altiplano +altitude +altitudes +altman +alto +altogether +alton +altoona +altos +altruism +altruist +altruistic +altschul +altus +alu +alum +alumina +aluminium +aluminized +aluminum +alumna +alumnae +alumni +alumnus +alums +alva +alvarado +alvarez +alvaro +alveoli +alvin +alvis +alway +always +alwin +aly +alyce +alzheimer +am +ama +amabile +amadeus +amado +amador +amadou +amal +amalgam +amalgamate +amalgamated +amalgamation +amalgams +amalie +aman +amanda +amani +amann +amantadine +amante +amar +amara +amaral +amaranth +amarillo +amaro +amaryllis +amas +amass +amassed +amassing +amateur +amateurish +amateurs +amato +amaya +amaze +amazed +amazement +amazes +amazing +amazingly +amazon +amazonia +amazonian +amazons +amb +ambassador +ambassadorial +ambassadors +ambassadorship +amber +ambergris +ambiance +ambidextrous +ambience +ambient +ambiguities +ambiguity +ambiguous +ambit +ambition +ambitions +ambitious +ambitiously +ambivalence +ambivalent +amble +ambled +ambler +ambles +ambling +ambrose +ambrosia +ambulance +ambulances +ambulatory +ambush +ambushed +ambushes +ambushing +amdahl +ame +amelia +amelio +ameliorate +ameliorated +ameliorating +amelioration +amen +amenable +amend +amendable +amended +amending +amendment +amendments +amends +amenities +amenity +ament +amer +america +american +americana +americanism +americanisms +americanization +americanized +americans +americas +amerika +amerindian +ameritech +amersham +amery +ames +amethyst +amethysts +amex +amgen +amherst +ami +amiability +amiable +amiably +amicable +amicably +amico +amicus +amid +amidships +amidst +amie +amiga +amigo +amigos +amin +amine +amines +amino +amir +amiri +amis +amish +amiss +amit +amitai +amitriptyline +amity +amityville +amman +ammer +ammo +ammon +ammonia +ammonite +ammonium +ammunition +ammunitions +amnesia +amnesiac +amnesty +amniocentesis +amniotic +amnon +amoco +amoeba +amoebas +amoebic +amok +amon +among +amongst +amontillado +amor +amoral +amore +amoroso +amorous +amorphous +amortization +amortize +amortized +amortizing +amos +amoung +amount +amounted +amounting +amounts +amour +amp +amperage +ampere +ampersand +amphenol +amphetamine +amphetamines +amphibian +amphibians +amphibious +amphitheater +amphitheaters +amphitheatre +amphora +amphotericin +ample +amplification +amplifications +amplified +amplifier +amplifiers +amplifies +amplify +amplifying +amplitude +amply +amps +amputate +amputated +amputating +amputation +amputations +amputee +amputees +amr +amritsar +amro +ams +amsterdam +amtrack +amtrak +amuck +amulet +amulets +amun +amundsen +amuse +amused +amusement +amusements +amuses +amusing +amusingly +amway +amy +amyloid +amyotrophic +an +ana +anabolic +anachronism +anachronisms +anachronistic +anaconda +anaerobes +anaerobic +anaesthesia +anaesthetic +anagram +anaheim +anal +analagous +analgesic +analgesics +analog +analogic +analogical +analogies +analogous +analogue +analogy +analyse +analysed +analyses +analysing +analysis +analyst +analysts +analytic +analytical +analytically +analytics +analyze +analyzed +analyzer +analyzers +analyzes +analyzing +anan +anand +ananda +anania +anaphylactic +anarchic +anarchism +anarchist +anarchists +anarchy +anas +anasazi +anastasia +anastasio +anathema +anathematized +anatole +anatolia +anatolian +anatoly +anatomical +anatomically +anatomist +anatomy +anaya +ance +ancestor +ancestors +ancestral +ancestry +anchor +anchorage +anchorages +anchored +anchoring +anchorman +anchors +anchovies +anchovy +ancient +ancients +ancillary +ancona +and +anda +andalusia +andante +andean +ander +anders +andersen +anderson +andersons +andersson +anderton +andes +andi +andie +anding +ando +andor +andorra +andover +andrade +andre +andrea +andreas +andrei +andres +andress +andretti +andrew +andrews +androgynous +androgyny +android +andromeda +andrus +andrzej +ands +andy +anecdotal +anecdotally +anecdote +anecdotes +anemia +anemic +anemometer +anemone +anencephaly +anesthesia +anesthesiologist +anesthesiologists +anesthesiology +anesthetic +anesthetist +anette +aneurysm +aneurysms +anew +ang +ange +angel +angela +angelenos +angeles +angelfish +angeli +angelic +angelica +angelico +angelika +angelina +angeline +angelino +angell +angelo +angelos +angelou +angels +anger +angered +angering +angers +angie +angier +angina +angiogenesis +angiogram +angioplasty +angiotensin +angle +angled +angler +anglers +angles +angleton +anglia +anglican +anglicans +anglicized +angling +anglo +anglos +angola +angolan +angolans +angora +angostura +angrier +angriest +angrily +angry +angst +angstrom +angstroms +anguilla +anguish +anguished +angular +angus +anheuser +anhydride +ani +anibal +anil +anima +animal +animals +animate +animated +animatedly +animates +animating +animation +animations +animator +animators +animism +animist +animists +animosities +animosity +animus +anis +anise +aniseed +anita +anjelica +ankara +anker +ankle +ankles +anklets +anlage +ann +anna +annabel +annabelle +annals +annan +annandale +annapolis +annas +anne +annealing +annenberg +annett +annette +annex +annexation +annexed +annexes +annexing +anni +annick +annie +annihilate +annihilated +annihilating +annihilation +annis +anniston +anniversaries +anniversary +anno +annotate +annotated +annotates +annotating +annotation +annotations +announce +announced +announcement +announcements +announcer +announcers +announces +announcing +announcment +annoy +annoyance +annoyances +annoyed +annoying +annoyingly +annoys +annual +annualized +annually +annuals +annuities +annuity +annul +annular +annulled +annulment +annuls +annum +annunciation +annunziata +annus +anny +anode +anodized +anoint +anointed +anoints +anomalies +anomalous +anomaly +anomie +anon +anonymity +anonymous +anonymously +anorak +anorexia +anorexic +anorexics +another +ans +ansa +ansari +anse +ansel +ansell +anselm +anselmo +ansi +ansley +anson +answer +answerable +answered +answering +answers +ant +anta +antacid +antacids +antagonism +antagonisms +antagonist +antagonistic +antagonists +antagonize +antagonized +antagonizing +antarctic +antarctica +antares +antas +ante +anteater +anteaters +antebellum +antecedent +antecedents +anted +antelope +antenna +antennae +antennas +anterior +antes +anthem +anthems +anther +anthill +anthologies +anthology +anthony +anthracite +anthrax +anthropological +anthropologist +anthropologists +anthropology +anthropomorphic +anti +antiabortion +antiaircraft +antibacterial +antibiotic +antibiotics +antibodies +antibody +antic +antica +anticancer +antichrist +anticipate +anticipated +anticipates +anticipating +anticipation +anticipations +anticipatory +anticlimactic +anticommunist +anticompetitive +antics +antidepressant +antidepressants +antidote +antidotes +antifreeze +antifungal +antigay +antigen +antigens +antigone +antigua +antihistamine +antihistamines +antihypertensive +antilles +antimalarial +antimatter +antimicrobial +antimissile +antimony +antioch +antioxidant +antioxidants +antipathies +antipathy +antipoverty +antiquarian +antiquated +antique +antiqued +antiques +antiquities +antiquity +antis +antisemitic +antiseptic +antislavery +antisocial +antitank +antithesis +antithetical +antitrust +antiviral +antivirus +antiwar +antler +antlered +antlers +antoine +antoinette +anton +antoni +antonia +antonin +antonio +antony +antrim +ants +antsy +antwerp +anus +anvil +anwar +anxieties +anxiety +anxious +anxiously +any +anybody +anyhow +anymore +anyone +anyplace +anything +anytime +anyting +anyway +anyways +anywhere +aol +aorta +aortic +aoyama +ap +apace +apache +apaches +aparicio +apart +apartheid +apartment +apartments +apathetic +apathy +apc +ape +apec +apelike +aperitif +aperture +apertures +apes +apex +aphasia +aphid +aphids +aphorism +aphorisms +aphrodisiac +aphrodite +apiece +aping +apis +aplenty +aplomb +apnea +apocalypse +apocalyptic +apocrypha +apocryphal +apogee +apolitical +apollo +apollonian +apologetic +apologetically +apologetics +apologies +apologise +apologised +apologist +apologists +apologize +apologized +apologizes +apologizing +apology +apoplectic +apoplexy +apostasy +apostate +apostates +apostle +apostles +apostolate +apostolic +apostrophe +apothecary +apotheosis +app +appalachia +appalachian +appalachians +appall +appalled +appalling +appallingly +appalls +apparantly +apparatchik +apparatchiks +apparatus +apparatuses +apparel +apparent +apparently +apparition +apparitions +appeal +appealable +appealed +appealing +appealingly +appeals +appear +appearance +appearances +appeared +appearence +appearing +appears +appease +appeased +appeasement +appeasing +appel +appellate +appellation +appellations +append +appendage +appendages +appendectomy +appended +appendices +appendicitis +appending +appendix +appendixes +appends +appetit +appetite +appetites +appetizer +appetizers +appetizing +appia +appian +appl +applaud +applauded +applauding +applauds +applause +apple +applebaum +appleby +applegate +apples +applesauce +appleton +applets +appliance +appliances +applicability +applicable +applicant +applicants +application +applications +applicator +applied +applies +appling +applique +apply +applying +appoint +appointed +appointee +appointees +appointing +appointive +appointment +appointments +appoints +appomattox +apportion +apportioned +apportioning +apportionment +appraisal +appraisals +appraise +appraised +appraiser +appraisers +appraises +appraising +appreciable +appreciably +appreciate +appreciated +appreciates +appreciating +appreciation +appreciations +appreciative +appreciatively +apprehend +apprehended +apprehending +apprehension +apprehensions +apprehensive +apprentice +apprenticed +apprentices +apprenticeship +apprenticeships +apprise +apprised +approach +approachable +approached +approaches +approaching +approbation +appropriate +appropriated +appropriately +appropriateness +appropriates +appropriating +appropriation +appropriations +appropriator +appropriators +approvable +approval +approvals +approve +approved +approves +approving +approvingly +approx +approximate +approximated +approximately +approximates +approximating +approximation +approximations +apps +appurtenances +apr +apra +apres +apricot +apricots +april +aprile +apron +aprons +apropos +apse +apses +apt +apter +aptitude +aptitudes +aptly +aptness +aq +aqua +aquaculture +aquamarine +aquarium +aquariums +aquarius +aquatic +aquatics +aqueduct +aqueducts +aqueous +aquifer +aquifers +aquila +aquinas +aquisition +aquitaine +ar +ara +arab +arabe +arabia +arabian +arabians +arabic +arabica +arable +arabs +arachnid +arachnids +arafat +aragon +arai +araki +aramaic +aramco +aramis +arana +aranda +arango +arapaho +arata +arati +arb +arba +arbeiter +arber +arbiter +arbiters +arbitrage +arbitrageur +arbitrageurs +arbitrarily +arbitrariness +arbitrary +arbitrate +arbitrated +arbitrating +arbitration +arbitrations +arbitrator +arbitrators +arbitron +arbor +arboreal +arboretum +arbour +arbs +arbuckle +arby +arc +arca +arcade +arcades +arcadia +arcadian +arcana +arcane +arcanum +arce +arch +archaeological +archaeologist +archaeologists +archaeology +archaic +archangel +archbishop +archdiocese +archduke +arched +archenemy +archeological +archeologist +archeologists +archeology +archer +archers +archery +arches +archetypal +archetype +archetypes +archibald +archie +archimedes +arching +archipelago +architect +architects +architectural +architecturally +architecture +architectures +archival +archive +archived +archives +archiving +archivist +archivists +archly +archway +archways +arcing +arco +arcos +arcs +arctic +arcturus +arcus +arden +ardent +ardently +ardmore +ardor +ardsley +arduous +are +area +areas +arellano +aren +arena +arenas +arend +arendt +arens +arent +ares +arete +aretha +arf +arg +argent +argentina +argentine +argentines +argentinian +argentinians +argh +argo +argon +argonaut +argonauts +argonne +argos +argosy +argot +arguable +arguably +argue +argued +arguello +argues +arguing +argument +argumentation +argumentative +arguments +argus +argyle +argyll +ari +aria +ariadne +arian +ariana +ariane +arianespace +arianna +arias +arid +arie +ariel +aries +arif +aright +aris +arise +arisen +arises +arising +arista +aristide +aristocracy +aristocrat +aristocratic +aristocrats +aristotelian +aristotle +arithmetic +arizona +arizonans +arjun +ark +arkady +arkansans +arkansas +arkin +arkwright +arledge +arlen +arlene +arles +arlie +arlington +arm +armada +armadillo +armadillos +armageddon +armament +armaments +armand +armando +armani +armas +armature +armband +armbands +armchair +armchairs +armed +armen +armenia +armenian +armenians +armer +armies +armin +arming +armistead +armistice +armitage +armload +armoire +armonk +armor +armored +armories +armory +armour +armoured +armpit +armpits +armrest +arms +armstead +armstrong +army +arn +arnaud +arnault +arnaz +arndt +arne +arnett +arnie +arno +arnold +arnott +aroma +aromas +aromatic +aromatics +aron +aronson +arora +arose +around +arousal +arouse +aroused +arouses +arousing +arp +arpa +arpanet +arps +arraigned +arraignment +arrange +arranged +arrangement +arrangements +arranger +arrangers +arranges +arranging +arrant +arras +array +arrayed +arrays +arrearage +arrearages +arrears +arrest +arrested +arresting +arrests +arrhythmia +arrhythmias +arriaga +arriba +arrington +arris +arrival +arrivals +arrive +arrived +arrives +arriving +arrogance +arrogant +arrogantly +arrogate +arrogated +arrondissement +arrow +arrowhead +arrowheads +arrows +arroyo +arruda +ars +arse +arsenal +arsenals +arsenic +arsenide +arsenio +arson +arsonist +arsonists +arsons +art +arte +artemis +artemisia +arter +arterial +arteries +arteriosclerosis +artery +artesian +artful +artfully +arthritic +arthritis +arthropod +arthropods +arthur +arthurian +artic +artichoke +artichokes +article +articles +articulate +articulated +articulates +articulating +articulation +artie +artifact +artifacts +artifical +artifice +artifices +artificial +artificiality +artificially +artillery +artin +artis +artisan +artisans +artist +artistic +artistically +artistry +artists +arts +artsy +artur +arturo +artus +artwork +artworks +arty +aruba +arugula +arum +arun +arundel +arvey +arvin +arvind +ary +aryan +aryans +aryeh +as +asa +asaf +asahi +asante +asap +asbestos +asbestosis +asbury +ascap +ascend +ascendance +ascendancy +ascendant +ascended +ascending +ascends +ascension +ascent +ascertain +ascertained +ascertaining +ascetic +asch +ascher +ascii +ascorbic +ascot +ascribe +ascribed +ascribes +ase +asea +ash +asha +ashamed +ashburn +ashbury +ashby +ashcroft +ashdown +ashe +ashen +asher +ashes +asheville +ashford +ashish +ashkenazi +ashland +ashley +ashman +ashok +ashore +ashraf +ashrawi +ashton +ashtray +ashtrays +ashy +asia +asian +asians +asiatic +asic +asics +aside +asides +asimov +asinine +ask +askance +asked +asker +askew +askey +askin +asking +asks +asleep +asner +asocial +asp +asparagus +aspartame +aspartate +aspect +aspects +aspen +aspens +asper +aspergillus +aspersion +aspersions +asphalt +asphyxiated +asphyxiation +aspin +aspirant +aspirants +aspirate +aspirated +aspiration +aspirations +aspire +aspired +aspires +aspirin +aspiring +ass +assad +assail +assailant +assailants +assailed +assailing +assails +assam +assassin +assassinate +assassinated +assassinating +assassination +assassinations +assassins +assault +assaulted +assaulting +assaults +assay +assays +assed +assemblage +assemble +assembled +assembler +assemblers +assembles +assemblies +assembling +assembly +assemblyman +assemblymen +assemblywoman +assent +assert +asserted +asserting +assertion +assertions +assertive +assertively +assertiveness +asserts +asses +assess +assessed +assesses +assessing +assessment +assessments +assessor +assessors +asset +assets +asshole +assholes +assiduous +assiduously +assign +assignable +assignations +assigned +assignees +assigning +assignment +assignments +assigns +assimilate +assimilated +assimilating +assimilation +assisi +assist +assistance +assistances +assistant +assistants +assistantships +assisted +assisting +assistive +assists +assn +assoc +assocation +associate +associated +associates +associating +association +associations +assorted +assortment +assortments +asst +assuage +assuaged +assume +assumed +assumes +assuming +assumption +assumptions +assunta +assurance +assurances +assure +assured +assuredly +assures +assuring +assyria +assyrian +assyrians +ast +asta +astaire +astarte +aster +asterisk +asterisks +asterix +astern +asteroid +asteroids +asters +asthma +asthmatic +asthmatics +astigmatism +astin +aston +astonish +astonished +astonishes +astonishing +astonishingly +astonishment +astor +astoria +astound +astounded +astounding +astoundingly +astounds +astra +astrachan +astrakhan +astral +astray +astride +astringent +astro +astrodome +astrologer +astrologers +astrological +astrology +astronaut +astronautical +astronautics +astronauts +astronomer +astronomers +astronomical +astronomically +astronomy +astrophysical +astrophysicist +astrophysicists +astrophysics +astros +astroturf +astute +astutely +astuteness +asuncion +asunder +aswell +asylum +asymmetric +asymmetrical +asymmetries +asymmetry +asymptomatic +asymptote +asymptotic +asynchronous +at +ata +atalanta +atalaya +atari +ataturk +atavistic +ataxia +atchison +ate +atef +atelier +aten +atheism +atheist +atheistic +atheists +athena +athenaeum +atheneum +athenian +athenians +athens +atherosclerosis +atherton +athlete +athletes +athletic +athletically +athleticism +athletics +athos +athwart +ation +atkin +atkins +atkinson +atlanta +atlantic +atlantico +atlantis +atlas +atlases +atmel +atmos +atmosphere +atmospheres +atmospheric +atmospherics +atocha +atoll +atom +atomic +atomics +atomized +atoms +atonal +atone +atoned +atonement +atoning +atop +atreus +atria +atrial +atrium +atrocious +atrocities +atrocity +atrophied +atrophy +att +atta +attaboy +attach +attache +attached +attaches +attaching +attachment +attachments +attack +attacked +attacker +attackers +attacking +attacks +attain +attainable +attainder +attained +attaining +attainment +attains +attanasio +attar +attempt +attempted +attempting +attempts +attend +attendance +attendances +attendant +attendants +attended +attendee +attendees +attendence +attending +attends +attention +attentions +attentive +attentively +attentiveness +attenuate +attenuated +attenuation +attest +attested +attesting +attests +attic +attica +attics +atticus +attila +attire +attired +attitude +attitudes +attitudinal +attn +attorney +attorneys +attract +attractant +attracted +attracting +attraction +attractions +attractive +attractively +attractiveness +attractor +attractors +attracts +attributable +attribute +attributed +attributes +attributing +attribution +attributions +attrition +attune +attuned +atty +atul +atwater +atwood +atypical +atypically +au +auberge +aubin +aubrey +auburn +auckland +auction +auctioned +auctioneer +auctioneers +auctioning +auctions +aud +audacious +audaciously +audacity +auden +audi +audible +audibly +audience +audiences +audio +audiology +audiophile +audiophiles +audiotape +audiotapes +audiovisual +audis +audit +auditable +audited +auditing +audition +auditioned +auditioning +auditions +auditor +auditorium +auditoriums +auditors +auditory +audits +audrey +audubon +auer +auerbach +auf +aug +auge +auger +aught +augie +augment +augmentation +augmented +augmenting +augments +augsburg +augur +augurs +august +augusta +auguste +augustin +augustine +augustinian +augusto +augustus +aul +auld +ault +aung +aunt +auntie +aunts +aura +aural +auras +aurelio +aureus +aurora +auroral +aurum +aus +auschwitz +auspex +auspices +auspicious +aussie +aussies +aust +austell +austen +auster +austere +austerely +austerity +austin +austral +australasian +australia +australian +australians +austria +austrian +austrians +auteur +auth +authentic +authentically +authenticate +authenticated +authenticating +authentication +authenticity +author +authored +authoring +authorisation +authorised +authoritarian +authoritarianism +authoritative +authoritatively +authorities +authority +authorization +authorizations +authorize +authorized +authorizes +authorizing +authors +authorship +autism +autistic +auto +autobahn +autobiographical +autobiographies +autobiography +autocracy +autocrat +autocratic +autocrats +autodesk +autograph +autographed +autographs +autoimmune +automaker +automakers +automata +automate +automated +automates +automatic +automatically +automatics +automating +automation +automaton +automatons +automobile +automobiles +automotive +autonomic +autonomous +autonomously +autonomy +autopilot +autopsied +autopsies +autopsy +autos +autosomal +autry +autumn +autumnal +aux +auxiliary +av +ava +avail +availabilities +availability +available +availed +avalanche +avalanches +avalon +avance +avant +avanti +avarice +avatar +avco +ave +avec +aven +avena +avenge +avenged +avenger +avengers +avenges +avenging +avenida +avenue +avenues +aver +average +averaged +averages +averaging +averil +averred +avers +averse +aversion +aversive +avert +averted +averting +averts +avery +aves +avesta +avg +avi +avian +aviaries +aviary +aviation +aviator +aviators +avid +avidly +avignon +avila +aviles +avinash +avionics +avis +avital +aviv +avnet +avocado +avocados +avocation +avocet +avoid +avoidable +avoidance +avoided +avoiding +avoids +avon +avondale +avowal +avowed +avowedly +avraham +avram +avril +avuncular +aw +awacs +await +awaited +awaiting +awaits +awake +awaken +awakened +awakening +awakens +awakes +awaking +award +awarded +awardees +awarding +awards +aware +awareness +awash +away +aways +awe +awed +awes +awesome +awesomely +awestruck +awful +awfully +awfulness +awhile +awkward +awkwardly +awkwardness +awning +awnings +awoke +awoken +awry +ax +axe +axed +axel +axelrod +axes +axial +axiom +axiomatic +axioms +axis +axle +axles +axon +axons +ay +ayala +ayatollah +ayatollahs +aye +ayer +ayers +ayes +aykroyd +ayn +ayres +ayrshire +ayrton +ayurveda +az +azalea +azaleas +azar +azerbaijan +azerbaijani +azerbaijanis +azeri +azevedo +azhar +aziz +azores +aztec +azteca +aztecs +azure +ba +baa +baal +baar +bab +baba +babb +babbage +babbitt +babble +babbled +babblers +babbles +babbling +babby +babcock +babe +babel +babes +babies +babington +baboon +baboons +babs +babson +babu +baby +babyhood +babyish +babylon +babylonian +babylonians +babysat +babysit +babysitter +babysitters +babysitting +bac +baca +bacall +bacardi +baccalaureate +baccarat +bacchanal +bacchanalia +bacchus +bach +bacharach +bache +bachelor +bachelors +bachman +bacillus +bacitracin +back +backache +backaches +backbenchers +backbiting +backboard +backbone +backbones +backbreaking +backcountry +backdate +backdated +backdating +backdoor +backdown +backdraft +backdrop +backdrops +backed +backer +backers +backfield +backfill +backfilling +backfire +backfired +backfires +backfiring +backgammon +background +backgrounder +backgrounds +backhand +backhanded +backhoe +backing +backlash +backless +backlighting +backlist +backlit +backlog +backlogged +backlogs +backpack +backpacker +backpackers +backpacking +backpacks +backpedal +backpedaling +backrest +backroom +backs +backseat +backside +backslapping +backslide +backsliding +backspace +backspin +backstabbing +backstage +backstop +backstreet +backstroke +backtrack +backtracked +backtracking +backup +backups +backus +backward +backwardness +backwards +backwash +backwater +backwaters +backwoods +backyard +backyards +bacon +bacteria +bacterial +bacteriologist +bacteriology +bacterium +bad +bada +badder +baddest +baddie +baddies +bade +baden +bader +badge +badged +badger +badgered +badgering +badgers +badges +badging +badia +badillo +badlands +badly +badminton +badmouthing +badness +bae +baedeker +baer +baez +baff +baffle +baffled +bafflement +baffles +baffling +bag +bagby +bagdad +bagel +bagels +bagful +baggage +bagged +bagger +baggie +baggies +bagging +baggy +baghdad +bagman +bagpipe +bagpiper +bagpipes +bags +bagwell +bah +bahadur +bahama +bahamas +bahamian +bahia +bahn +bahr +bahrain +bahraini +baht +bai +baie +bail +baile +bailed +bailey +baileys +bailie +bailiff +bailing +bailiwick +baillie +bailly +bailor +bailout +bailouts +bails +bain +bainbridge +baines +bains +bair +baird +bait +baited +baiting +baits +baize +baja +bak +baka +bake +baked +bakelite +baker +bakeries +bakers +bakersfield +bakery +bakes +bakeware +baking +bakke +bakker +bakshi +baku +bakula +bal +bala +balaclava +balance +balanced +balancer +balances +balanchine +balancing +balboa +balboni +balconies +balcony +bald +baldassare +balder +balderdash +baldi +balding +baldly +baldness +baldridge +baldwin +bale +baleen +baleful +bales +balfour +bali +balinese +baling +balk +balkan +balkanization +balkans +balked +balking +balks +balky +ball +ballad +ballade +balladeer +ballads +ballance +ballantine +ballard +ballast +ballasts +balled +ballentine +baller +ballerina +ballerinas +ballesteros +ballet +ballets +ballgame +ballgames +ballgown +balli +balling +ballinger +ballistic +ballistics +ballmer +ballo +ballon +balloon +ballooned +ballooning +balloonist +balloons +ballot +balloting +ballots +ballou +ballpark +ballparks +ballplayer +ballplayers +ballpoint +ballroom +ballrooms +balls +bally +ballyhoo +ballyhooed +balm +balmer +balmoral +balms +balmy +baloney +baloo +balsa +balsam +balsamic +baltic +baltics +baltimore +baluchi +balzer +bam +bambi +bambino +bamboo +bamboozle +bamboozled +bamford +ban +banal +banalities +banality +banana +bananas +banbury +banc +banca +banco +bancorp +bancroft +band +banda +bandage +bandaged +bandages +bandaid +bandana +bandanas +bandanna +bandar +banded +bander +banderas +bandied +banding +bandit +banditry +bandits +bandleader +bandon +bands +bandstand +bandwagon +bandwagons +bandwidth +bandwidths +bandy +bane +banes +banff +bang +bangalore +banged +banger +bangers +banging +bangkok +bangladesh +bangladeshi +bangle +bangles +bangor +bangs +bani +banish +banished +banishing +banishment +banister +banjo +banjos +bank +bankable +bankamerica +bankcard +banked +banker +bankers +banking +banknote +banknotes +bankroll +bankrolled +bankrolling +bankrolls +bankrupt +bankruptcies +bankruptcy +bankrupted +bankrupting +banks +bankside +banned +banner +bannerman +banners +banning +bannister +bannon +banque +banquet +banquets +banquettes +bans +banshee +banshees +banta +bantam +banter +bantered +bantering +bantu +banyan +banzai +banzhaf +baobab +baptism +baptismal +baptisms +baptist +baptista +baptiste +baptistery +baptists +baptize +baptized +baptizing +bar +bara +barak +barakat +baran +barb +barba +barbados +barbara +barbarian +barbarians +barbaric +barbarism +barbarity +barbarous +barbary +barbe +barbecue +barbecued +barbecues +barbecuing +barbed +barbells +barbeque +barber +barbera +barbero +barbers +barbershop +barbie +barbier +barbiturate +barbiturates +barbour +barboza +barbra +barbs +barcelona +barclay +barclays +barcode +bard +barden +bare +barebones +bared +barefoot +barely +barenboim +barents +barer +bares +barest +barf +barfield +barfly +bargain +bargained +bargaining +bargains +barge +barger +barges +barging +bari +baring +barings +baris +barish +baritone +barium +bark +barkan +barked +barker +barkers +barking +barkley +barks +barksdale +barlett +barley +barlow +barmaid +barman +barn +barnabas +barnaby +barnacle +barnacles +barnard +barnes +barnet +barnett +barney +barnhart +barnhill +barns +barnsley +barnstorm +barnstormers +barnstorming +barnum +barnwell +barnyard +barolo +barometer +barometers +barometric +baron +barone +baroness +baronet +barons +baroque +barr +barra +barrack +barracks +barracuda +barracudas +barrage +barraged +barrages +barranco +barras +barre +barred +barrel +barreled +barreling +barrell +barrelled +barrelling +barrels +barren +barrens +barrera +barret +barrett +barricade +barricaded +barricades +barricading +barrie +barrientos +barrier +barriers +barring +barringer +barrington +barrio +barrios +barris +barrister +barristers +barro +barron +barroom +barrow +barrowman +barrows +barry +barrymore +bars +bart +bartel +bartell +bartels +bartender +bartenders +barter +bartered +bartering +barth +barthel +bartholomew +bartle +bartlett +bartley +barto +bartol +bartoli +bartolo +bartolomeo +barton +bartram +barts +bartz +baru +baruch +barwick +bas +basal +basalt +basalts +base +baseball +baseballs +baseboard +baseboards +based +basel +baseless +baseline +baselines +baseman +basemen +basement +basements +baseness +bases +bash +bashaw +bashed +basher +bashers +bashes +bashford +bashful +bashing +bashir +basic +basically +basics +basie +basil +basile +basilica +basin +basing +basinger +basins +basis +bask +basked +baskerville +basket +basketball +basketballs +basketry +baskets +baskin +basking +basks +basque +basques +basra +bass +basse +basses +basset +bassett +bassin +bassinet +bassist +bassists +basso +bassoon +bast +basta +bastard +bastardized +bastards +baste +basters +bastian +bastille +basting +bastion +bastions +basu +bat +bataan +batavia +batch +batches +bate +bateau +bated +bateman +bates +batesville +bath +bathe +bathed +bather +bathers +bathes +bathhouses +bathing +bathrobe +bathrobes +bathroom +bathrooms +baths +bathsheba +bathtub +bathtubs +bathurst +bathwater +batik +batiks +batista +batiste +batman +baton +batons +bats +batt +battaglia +battalion +battalions +batted +battelle +batten +battening +batter +battered +batterer +batteries +battering +batters +battery +batting +battista +battisti +battle +battled +battlefield +battlefields +battlefront +battleground +battlegrounds +battlements +battles +battleship +battleships +battling +batts +batty +bauble +baubles +baucus +baud +baudoin +bauer +baugh +baum +baumann +baume +baumgartner +baur +bausch +bautista +bauxite +bavaria +bavarian +bawdy +bawl +bawled +bawling +baxter +bay +bayard +bayer +bayesian +bayh +baying +bayley +bayliss +baylor +bayonet +bayonets +bayonne +bayou +bayous +bayreuth +bays +baywatch +baz +bazaar +bazaars +bazar +bazooka +bazookas +bb +bbl +bbq +bbs +bcd +bch +bd +be +beach +beachcomber +beached +beaches +beachfront +beachhead +beachheads +beaching +beachside +beachy +beacon +beacons +beacuse +bead +beaded +beading +beadle +beads +beadwork +beady +beagle +beagles +beak +beaker +beaks +beal +beale +beall +beam +beamed +beamer +beaming +beamish +beams +bean +beanbag +beanie +beans +bear +bearable +bearcats +beard +bearded +bearden +beards +beardsley +bearer +bearers +bearing +bearings +bearish +bearishness +bearpaw +bears +beasley +beast +beastie +beasties +beasts +beat +beata +beatable +beaten +beater +beaters +beating +beatings +beatitudes +beatle +beatles +beatnik +beatniks +beaton +beatrice +beats +beattie +beatty +beau +beauchamp +beaucoup +beaufort +beaujolais +beaumont +beauregard +beautician +beauties +beautification +beautified +beautiful +beautifully +beautify +beauty +beauvais +beaux +beaver +beavers +beaverton +beavis +beazley +bebe +bebop +bec +becalmed +became +becasue +because +becher +bechtel +beck +becker +becket +beckett +beckham +beckie +beckman +beckmann +beckon +beckoned +beckoning +beckons +becks +beckwith +becky +become +becomes +becoming +becton +becuase +bed +beda +bedbug +bedbugs +bedchamber +bedded +bedding +bede +bedecked +bedell +bedevil +bedeviled +bedfellow +bedfellows +bedford +bedlam +bedminster +bedouin +bedouins +bedpan +bedpans +bedraggled +bedridden +bedrock +bedroom +bedrooms +beds +bedside +bedspread +bedtime +bee +beebe +beeby +beech +beecham +beecher +beechwood +beef +beefed +beefing +beefs +beefsteak +beefy +beehive +beek +beekeeper +beekeeping +beekman +beeline +beelzebub +beeman +beemer +been +beens +beep +beeper +beeping +beeps +beer +beers +beery +bees +beeson +beeswax +beet +beethoven +beetle +beetlejuice +beetles +beets +befall +befallen +befalling +befalls +befell +befit +befits +befitting +before +beforehand +befriend +befriended +befriending +befriends +befuddled +beg +began +begat +begay +beget +begets +begetting +beggar +beggars +begged +begging +begin +begining +beginner +beginners +beginning +beginnings +begins +begley +begone +begonia +begonias +begot +begotten +begrudge +begs +beguile +beguiled +beguiling +begun +behalf +behan +behave +behaved +behaves +behaving +behavior +behavioral +behaviorist +behaviorists +behaviors +behaviour +behavioural +behaviours +behead +beheaded +beheading +beheld +behemoth +behemoths +behest +behind +behinds +behn +behnke +behold +beholden +beholder +beholding +behr +behrens +behring +beige +beignets +beijing +bein +being +beings +beira +beirne +beirut +beit +bekker +bel +bela +belabor +belaboring +belair +belanger +belarus +belasco +belated +belatedly +belay +belch +belched +belcher +belching +belding +beleaguered +beleive +belen +belfast +belford +belfry +belgacom +belgian +belgians +belgium +belgrade +belie +belied +belief +beliefs +belies +believability +believable +believe +believed +believer +believers +believes +believing +belinda +belittle +belittled +belittles +belittling +belive +belize +belk +belkin +belknap +bell +bella +bellamy +bellas +belle +beller +belles +belleville +bellevue +bellflower +bellhop +bellhops +belli +bellicose +bellied +bellies +belligerence +belligerent +bellinger +bellingham +bellini +bellman +bello +bellotti +bellow +bellowed +bellowing +bellows +bells +bellsouth +bellum +bellwether +bellwethers +belly +bellyaching +bellybutton +belmont +belmonte +belo +beloit +belong +belonged +belonging +belongings +belongs +beloved +below +belson +belt +belted +belter +belting +belton +beltran +belts +beltsville +beltway +beluga +belushi +belvedere +belville +belying +bem +bemis +bemoan +bemoaned +bemoaning +bemoans +bemused +bemusement +ben +bena +benadryl +benavides +bench +benched +benches +benchmark +benchmarked +benchmarking +benchmarks +bend +bended +bender +benders +bending +bends +bene +beneath +benedetti +benedetto +benedict +benedictine +benediction +benefactor +benefactors +beneficence +beneficent +beneficial +beneficiaries +beneficiary +benefit +benefited +benefiting +benefits +benefitted +benefitting +benelux +benes +benetton +benevento +benevolence +benevolent +beng +bengal +bengali +bengals +bengtson +benham +beni +benighted +benign +benignly +benin +benita +benitez +benito +benjamin +benjamins +benjy +benn +benner +bennet +bennett +benning +bennis +benno +benny +benoit +bens +benson +bensonhurst +bent +bentley +bento +benton +bentonite +bentonville +bentsen +benvenuto +benz +benzene +beowulf +bequeath +bequeathed +bequeaths +bequest +bequests +ber +beranek +berate +berated +berates +berating +berber +bere +bereaved +bereavement +bereft +berenson +beresford +beret +berets +beretta +berg +bergamot +berge +bergen +berger +bergere +bergeron +berglund +bergman +bergs +bergstrom +bering +beringer +berk +berke +berkeley +berklee +berkley +berkman +berkowitz +berkshire +berkshires +berle +berlin +berliner +berliners +berlioz +berlusconi +berm +berman +berms +bermuda +bermudian +bern +bernadette +bernadine +bernal +bernard +bernardi +bernardin +bernardino +bernardo +bernd +berndt +berne +berner +bernhard +bernhardt +bernice +bernie +bernoulli +bernstein +bernt +berra +berri +berridge +berries +berry +berryhill +berryman +berserk +bershad +berson +bert +bertelsmann +berth +bertha +berthed +berthelot +berthing +berthold +berths +berti +bertie +bertil +bertin +bertram +bertrand +bertsch +berube +berwick +berwyn +beryl +beryllium +bes +beseech +beseeched +beseeching +beset +besetting +beside +besides +besiege +besieged +besieging +besmirch +bespeak +bespeaks +bespectacled +bespoke +bess +bessemer +bessette +bessie +besson +best +bested +bestial +bestiality +besting +bestow +bestowed +bestowing +bestows +bests +bestseller +bestsellers +bestselling +bet +beta +betancourt +betas +betcha +betel +beth +bethany +bethel +bethesda +bethke +bethlehem +bethpage +betide +betray +betrayal +betrayals +betrayed +betrayer +betraying +betrays +betrothed +bets +betsey +betsy +betta +bette +bettencourt +better +bettered +bettering +betterment +betters +bettina +betting +bettis +bettor +bettors +betts +betty +between +betweens +betwen +betwixt +betz +beulah +bev +bevan +bevel +bever +beverage +beverages +beverley +beverly +bevilacqua +bevin +bevis +bevy +bewailing +beware +bewilder +bewildered +bewildering +bewilderment +bewilders +bewitch +bewitched +bewitching +bexley +bey +beyer +beyond +bg +bhagat +bhandari +bhatt +bhattacharya +bhopal +bhp +bhutan +bhutto +bi +bianca +bianchi +bianco +biannual +bias +biased +biases +biasing +bib +bibb +bibby +bibi +bible +bibles +biblical +biblically +bibliographic +bibliographies +bibliography +bibliophiles +bibs +bic +bicarbonate +bice +bicentenary +bicentennial +biceps +bick +bicker +bickered +bickering +bickers +bickerstaff +bickford +bickle +bicknell +bicoastal +bicycle +bicycled +bicycles +bicycling +bicyclist +bicyclists +bid +bidder +bidders +bidding +biddle +biddy +bide +biden +biding +bidirectional +bids +bidwell +biel +bielecki +bielefeld +bien +biennale +biennial +biennium +bienvenue +bier +bierce +biff +bifida +bifocal +bifocals +bifurcated +bifurcating +bifurcation +big +bigamy +bigelow +bigfoot +bigger +biggerstaff +biggest +biggie +biggies +biggins +biggio +biggs +bighorn +bight +bigness +bigot +bigoted +bigotry +bigots +bigs +bigtime +bigwig +bigwigs +bihari +bijan +bijou +bike +biked +biker +bikers +bikes +biking +bikini +bikinis +bil +bilateral +bilaterally +bilbao +bilbo +bilby +bild +bile +biles +bilge +bilingual +bilingualism +bilious +bilk +bilked +bilking +bill +billable +billard +billboard +billboards +bille +billed +billerica +billet +billets +billfold +billiard +billiards +billie +billing +billingham +billings +billington +billion +billionaire +billionaires +billions +billionth +billionths +billon +billow +billowed +billowing +billows +billowy +bills +billy +biloxi +biltmore +bim +bimbo +bimbos +bimini +bimonthly +bin +bina +binaries +binary +binational +bind +binder +binders +bindery +binding +bindings +binds +bines +bing +bingaman +binge +binger +binges +bingham +binghamton +bingle +bingo +binkley +binney +binning +binocular +binoculars +binomial +bins +binyamin +bio +biochem +biochemical +biochemist +biochemistry +biochemists +biodegradable +biodiversity +bioengineered +bioengineering +bioethics +biofeedback +biogen +biographer +biographers +biographical +biographies +biography +biohazard +biologic +biological +biologically +biologicals +biologics +biologist +biologists +biology +biomass +biomaterial +biome +biomechanical +biomechanics +biomed +biomedical +biomes +biometric +biometrics +biopharm +biophysics +biopic +biopsies +biopsy +bios +bioscience +biosciences +biosphere +biostatistics +biosystems +biota +biotech +biotechnological +biotechnologies +biotechnology +biotic +biotin +bipartisan +bipartisanship +bipeds +biphenyls +bipolar +bir +birch +birches +birchwood +bird +birdcage +birdcages +birders +birdhouse +birdie +birdied +birdies +birding +birdlife +birdman +birds +birdsall +birdseed +birdseye +birdsong +birdy +birk +birks +birmingham +birnbaum +biro +birr +birt +birth +birthday +birthdays +birthing +birthmark +birthmarks +birthplace +birthrate +birthrates +birthright +births +bis +bisbee +biscayne +bischoff +biscuit +biscuits +bisected +bisecting +bisexual +bisexuality +bisexuals +bish +bishop +bishops +bismarck +bismark +bismuth +bison +bisque +bissell +bissonette +bist +bistro +bistros +bit +bitch +bitches +bitchiness +bitching +bitchy +bite +biter +bites +biting +bitmap +bits +bitsy +bitten +bitter +bitterest +bitterly +bitterness +bitterroot +bitters +bittersweet +bittman +bittner +bitty +bitumen +bituminous +bitz +bivalve +bivalves +bivouac +biweekly +bix +bixler +biz +bizarre +bizarrely +bjork +bjorn +bk +bl +blab +black +blackball +blackballed +blackberries +blackberry +blackbird +blackbirds +blackboard +blackboards +blackburn +blacked +blacken +blackened +blackening +blacker +blackest +blackface +blackfoot +blackford +blackhawk +blackhawks +blackie +blacking +blackish +blackjack +blackley +blacklist +blacklisted +blacklisting +blacklists +blackmail +blackmailed +blackmailers +blackmailing +blackman +blackmore +blackness +blackout +blackouts +blackrock +blacks +blacksburg +blackshaw +blacksmith +blackstone +blacktop +blackwater +blackwell +blackwood +bladder +bladders +blade +bladed +blades +blading +blah +blaha +blain +blaine +blair +blaisdell +blaise +blake +blakeney +blakey +blalock +blam +blame +blamed +blameless +blames +blaming +blanc +blanca +blanch +blanchard +blanche +blanched +blanches +blanco +blancs +bland +blander +blandishments +blandly +blandness +blaney +blank +blanked +blankenship +blanket +blanketed +blanketing +blankets +blanking +blankly +blanks +blanton +blare +blared +blares +blaring +blarney +blas +blase +blasphemers +blasphemous +blasphemy +blass +blast +blasted +blaster +blasting +blastoff +blasts +blat +blatant +blatantly +blather +blathering +blatter +blau +blay +blaylock +blaze +blazed +blazer +blazers +blazes +blazing +blazoned +bldg +bleach +bleached +bleacher +bleachers +bleaching +bleak +bleaker +bleakest +bleakness +bleary +bleat +bleating +blech +bled +bledsoe +bleecker +bleed +bleeding +bleeds +bleep +bleeped +bleeping +blemish +blemished +blemishes +blend +blended +blender +blenders +blending +blends +blenheim +bless +blessed +blessedly +blessedness +blesses +blessing +blessings +bleu +blew +blick +blier +bligh +blight +blighted +blights +blimp +blimps +blind +blinded +blinder +blinders +blindfold +blindfolded +blinding +blindingly +blindly +blindness +blinds +blindside +blink +blinked +blinker +blinkers +blinking +blinks +blip +blips +bliss +blissful +blissfully +blister +blistered +blistering +blisters +blithe +blithely +blitz +blitzed +blitzer +blitzes +blitzkrieg +blix +blizzard +blizzards +blk +bloat +bloated +bloating +blob +blobby +blobs +bloc +bloch +block +blockade +blockaded +blockades +blockading +blockage +blockages +blockbuster +blockbusters +blocked +blocker +blockers +blockheads +blocking +blocks +blocky +blocs +blodgett +bloedel +bloke +blokes +blom +blond +blonde +blonder +blondes +blondie +blondish +blonds +blood +bloodbath +blooded +bloodhound +bloodhounds +bloodied +bloodier +bloodiest +bloodily +bloodless +bloodletting +bloodline +bloodlines +bloodlust +bloods +bloodshed +bloodshot +bloodstained +bloodstains +bloodstream +bloodsuckers +bloodsucking +bloodthirsty +bloodworth +bloody +bloom +bloomberg +bloomed +bloomer +bloomers +bloomfield +blooming +bloomingdale +bloomington +blooms +bloop +bloopers +bloor +blossom +blossomed +blossoming +blossoms +blot +blotchy +blots +blotted +blotter +blotters +blount +blouse +blouses +blow +blowback +blower +blowers +blowfish +blowhards +blowing +blown +blowout +blowouts +blows +blowtorch +blowup +blubber +blubbering +blucher +bludgeon +bludgeoned +bludgeoning +blue +bluebell +blueberries +blueberry +bluebird +bluebirds +bluebonnet +bluebonnets +bluebook +bluefin +bluefish +bluegrass +bluejay +bluenose +blueprint +blueprints +bluer +blues +bluesman +bluest +bluestone +bluff +bluffed +bluffing +bluffs +bluish +blum +blume +blumenthal +blunder +blundered +blundering +blunders +blunt +blunted +blunting +bluntly +bluntness +blunts +blur +blurb +blurbs +blurred +blurring +blurry +blurs +blurt +blurted +blurts +blush +blushed +blusher +blushes +blushing +bluster +blustering +blustery +blvd +blyth +blythe +bmws +bn +bo +boa +boar +board +boarded +boarder +boarders +boarding +boardman +boardroom +boardrooms +boards +boardwalk +boars +boas +boast +boasted +boastful +boasting +boasts +boat +boaters +boathouse +boating +boatload +boatloads +boatman +boats +boaz +bob +bobbie +bobbin +bobbing +bobbitt +bobble +bobbled +bobby +bobcat +bobcats +bobo +bobs +bobsled +bobtail +boca +boccaccio +bocce +bock +bod +boda +bodacious +bode +bodega +bodegas +boden +bodes +bodice +bodied +bodies +bodily +bodine +boding +bodkin +bodnar +body +bodybuilding +bodyguard +bodyguards +bodywork +boe +boehm +boehringer +boeing +boer +boers +boesky +boff +boffins +boffo +bog +bogan +bogart +bogdan +bogen +bogey +bogeyman +bogeymen +bogeys +bogged +bogging +boggle +boggled +boggles +boggling +boggs +bogie +bogle +bogor +bogosian +bogota +bogs +bogue +bogus +bohemia +bohemian +bohn +bohr +boil +boileau +boiled +boiler +boilermaker +boilermakers +boilerplate +boilers +boiling +boils +boing +bois +boise +boisseau +boisterous +boivin +bok +bol +bola +bolan +boland +bolanos +bold +bolden +bolder +boldest +boldface +boldfaced +bolding +boldly +boldness +bole +bolero +boles +bolger +bolin +bolivar +bolivia +bolivian +boll +bollard +boller +bolling +bollinger +bolls +bolo +bologna +bolsa +bolshevik +bolsheviks +bolshevism +bolshoi +bolster +bolstered +bolstering +bolsters +bolt +bolted +bolten +bolting +bolton +bolts +bom +bomb +bomba +bombard +bombarded +bombardier +bombarding +bombardment +bombardments +bombast +bombastic +bombay +bombed +bomber +bombers +bombing +bombings +bombs +bombshell +bombshells +bomer +bon +bona +bonafide +bonanza +bonaparte +bonaventure +bonbon +bond +bondage +bondar +bonded +bonder +bondholder +bondholders +bondi +bonding +bonds +bondsman +bone +boned +bonefish +bonehead +boneheads +boneless +boner +bones +bonet +boney +boneyard +bonfield +bonfire +bonfires +bong +bongiovanni +bongo +bongos +bongs +bonham +boni +boniface +bonilla +bonin +boning +bonita +bonito +bonjour +bonk +bonkers +bonn +bonne +bonnell +bonner +bonnes +bonnet +bonneville +bonney +bonnie +bonny +bono +bons +bonsai +bonum +bonus +bonuses +bony +boo +boob +booboo +boobs +booby +boodle +booed +booger +boogeyman +boogie +booing +book +bookbinder +bookcase +bookcases +booked +bookend +bookends +booker +bookers +bookie +bookies +booking +bookings +bookish +bookkeeper +bookkeeping +booklet +booklets +bookmaker +bookmakers +bookmaking +bookman +bookmark +bookmarks +books +bookseller +booksellers +bookselling +bookshelf +bookshelves +bookshop +bookshops +bookstore +bookstores +bookworm +boole +boolean +boom +boombox +boomed +boomer +boomerang +boomers +booming +booms +boomtown +boon +boondocks +boondoggle +boone +boons +boor +boorish +boors +boos +boost +boosted +booster +boosters +boosting +boosts +boot +booted +booth +boothby +boothe +booths +bootie +booties +booting +bootleg +bootlegger +bootleggers +bootlegging +bootlegs +boots +bootstrap +bootstrapping +bootstraps +booty +booz +booze +boozer +boozing +boozy +bop +bopp +bopped +bops +bor +bora +borate +borax +bord +bordeaux +bordello +borden +border +bordered +bordering +borderland +borderlands +borderless +borderline +borders +bore +boreal +borealis +bored +boredom +borel +boren +borer +bores +borg +borge +borger +borges +boric +boring +borings +boris +bork +borland +born +borne +borneo +boro +borohydride +boron +borough +boroughs +borrow +borrowed +borrower +borrowers +borrowing +borrowings +borrows +bors +borscht +bort +bos +bosch +bosco +bose +bosh +boskin +bosley +bosnia +bosnian +bosnians +bosom +bosoms +boson +bosporus +bosque +boss +bossa +bossed +bosses +bossier +bossing +bossman +bossy +boston +bostonian +bostonians +bostwick +bosun +boswell +bosworth +bot +botanic +botanical +botanically +botanist +botanists +botany +botas +botch +botched +botching +botero +both +botha +bothe +bothell +bother +bothered +bothering +bothers +bothersome +bothwell +bots +botsford +botswana +bott +bottle +bottled +bottleneck +bottlenecks +bottler +bottlers +bottles +bottling +bottom +bottomed +bottoming +bottomless +bottomley +bottoms +botton +botts +botulism +bouche +boucher +boucle +boudin +boudoir +boudreau +bougainvillea +bough +boughs +bought +bougie +bouillon +boulanger +boulder +boulders +boule +boulevard +boulevards +boulos +boulter +bounce +bounced +bouncer +bouncers +bounces +bouncing +bouncy +bound +boundaries +boundary +bounded +bounder +bounding +boundless +boundries +bounds +bounteous +bounties +bountiful +bounty +bouquet +bouquets +bourbon +bourbons +bourdon +bourg +bourgeois +bourgeoisie +bourget +bourke +bourne +bourque +bourse +bourses +boustany +bout +boutique +boutiques +bouton +boutros +bouts +bouvier +bova +bove +bovine +bovines +bow +bowden +bowditch +bowdoin +bowe +bowed +bowel +bowels +bowen +bower +bowerman +bowers +bowery +bowes +bowie +bowing +bowker +bowl +bowled +bowler +bowlers +bowles +bowling +bowls +bowman +bown +bowne +bowring +bows +bowser +bowsher +bowyer +box +boxcar +boxcars +boxed +boxer +boxers +boxes +boxing +boxwood +boxy +boy +boyar +boyce +boycott +boycotted +boycotting +boycotts +boyd +boyden +boyer +boyfriend +boyfriends +boyhood +boyish +boykin +boylan +boyle +boyne +boynton +boyo +boys +boyz +bozeman +bozo +bozos +bps +br +bra +brabant +brabham +bracco +brace +braced +bracelet +bracelets +braces +bracey +brach +bracing +brack +bracken +bracket +bracketed +bracketing +brackets +brackett +brackish +bracknell +brad +bradbury +braddock +braden +bradenton +bradfield +bradford +bradlee +bradley +bradshaw +bradstreet +brady +bradycardia +brae +brag +bragg +braggadocio +braggart +bragged +bragging +brags +braham +brahma +brahmin +brahmins +brahms +braid +braided +braiding +braids +braille +brain +brainard +brainchild +brained +brainer +brainerd +brainless +brainpower +brains +brainstorm +brainstorming +brainstorms +braintree +brainwash +brainwashed +brainwashing +brainy +braise +braised +braising +braithwaite +brake +braked +brakeman +brakes +braking +bram +bramah +bramble +bramhall +brampton +bran +branca +branch +branched +branches +branching +branco +brand +branded +brandeis +brandenburg +brandes +brandi +brandies +branding +brandish +brandished +brandishes +brandishing +brandname +brando +brandon +brands +brandt +brandy +brandywine +branham +braniff +brank +brann +brannan +brannon +branscomb +branson +branstad +brant +brantley +braque +bras +brash +brashear +brasher +brashly +brashness +brasil +brasilia +brass +brassard +brasserie +brassy +brat +bratislava +brats +bratt +brattle +bratton +bratwurst +brauer +braun +braunschweig +bravado +brave +braved +bravely +braver +bravery +braves +bravest +braving +bravo +bravura +brawl +brawley +brawling +brawls +brawn +brawner +brawny +braxton +bray +brayer +brays +brazen +brazenly +brazenness +brazier +braziers +brazil +brazilian +brazilians +brazos +brazzaville +brea +breach +breached +breaches +breaching +bread +breadbasket +breaded +breadfruit +breading +breads +breadth +breadwinner +breadwinners +break +breakable +breakage +breakaway +breakdown +breakdowns +breaker +breakers +breakeven +breakfast +breakfasts +breaking +breakneck +breakout +breakouts +breakpoint +breakpoints +breaks +breakthrough +breakthroughs +breakup +breakups +breakwater +bream +brean +breast +breasted +breastfed +breastfeeding +breastplate +breasts +breath +breathable +breathe +breathed +breather +breathes +breathing +breathless +breathlessly +breaths +breathtaking +breathtakingly +breathy +breaux +brecher +brecht +breck +breckenridge +brecker +bred +breda +bree +breech +breeches +breed +breeden +breeder +breeders +breeding +breedlove +breeds +breen +brees +breeze +breezed +breezes +breezeway +breezing +breezy +breitling +bremen +bremer +bremner +bren +brenda +brendan +brendel +brennan +brenner +brent +brentwood +breslin +brest +bret +brethren +breton +brett +bretton +breuer +brevity +brew +brewed +brewer +breweries +brewers +brewery +brewing +brews +brewster +breyer +brezhnev +brian +briant +briar +bribe +bribed +bribery +bribes +bribing +brice +brick +brickbats +bricked +brickell +bricker +brickhouse +bricklayer +bricklayers +brickley +bricklin +bricks +brickyard +brid +bridal +bride +bridegroom +brides +bridesmaid +bridesmaids +bridge +bridged +bridgehead +bridgeman +bridgeport +bridger +bridges +bridgestone +bridget +bridgetown +bridgette +bridgewater +bridging +bridgman +bridle +bridled +brie +brief +briefcase +briefcases +briefed +briefer +briefers +briefest +briefing +briefings +briefly +briefs +brien +brier +brig +brigade +brigades +brigadier +brigands +briggs +brigham +bright +brighten +brightened +brightening +brightens +brighter +brightest +brightly +brightness +brighton +brigitte +brill +brilliance +brilliant +brilliantly +brim +brimelow +brimmed +brimmer +brimming +brims +brimstone +brin +brindisi +brindle +brine +brines +bring +bringing +brings +brining +brink +brinker +brinkley +brinkman +brinkmanship +brinks +brio +brioche +brion +briquette +briquettes +brisbane +brisco +briscoe +brisk +brisker +brisket +briskly +brister +bristle +bristled +bristles +bristling +bristly +bristol +bristow +brit +britain +britains +britannia +britannica +britches +brite +brith +britian +british +briton +britons +brits +britt +brittain +brittanica +brittany +britten +brittle +britton +brix +brl +bro +broach +broached +broaches +broaching +broad +broadband +broadbent +broadcast +broadcasted +broadcaster +broadcasters +broadcasting +broadcasts +broadcloth +broaden +broadened +broadening +broadens +broader +broadest +broadhead +broadhurst +broadly +broadness +broads +broadsheet +broadside +broadsides +broadsword +broadview +broadway +brobdingnagian +brocade +brocades +broccoli +brochure +brochures +brock +brocker +brockton +brod +broder +broderbund +broderick +brodeur +brodie +brodsky +brody +broe +brogan +broil +broiled +broiler +broilers +broiling +brokaw +broke +broken +brokenhearted +brokenness +broker +brokerage +brokerages +brokered +brokering +brokers +broking +brolly +bromberg +bromide +bromides +bromine +bromley +bronchial +bronchitis +bronco +broncos +bronfman +bronson +bronstein +bronte +brontosaurus +bronwen +bronx +bronze +bronzed +bronzes +brooch +brood +brooded +brooding +broody +brook +brooke +brooker +brookes +brookfield +brookhaven +brooking +brookings +brookline +brooklyn +brooks +brookside +brookstone +broom +broome +broomfield +brooms +broomstick +broomsticks +brophy +bros +brose +brosnan +broth +brothel +brothels +brother +brotherhood +brotherly +brothers +broths +brough +brougham +brought +broughton +brouhaha +broun +broussard +brow +broward +browbeat +browder +browed +brown +brownback +browne +browned +brownell +browner +brownian +brownie +brownies +browning +brownish +brownlee +brownlow +brownout +brownouts +brownrigg +browns +brownstein +brownstone +brownstones +brownsville +brows +browsable +browse +browsed +browser +browsers +browses +browsing +brrr +brubaker +brubeck +bruce +bruch +bruck +bruckner +bruder +bruges +bruin +bruins +bruise +bruised +bruiser +bruises +bruising +brum +brummer +brun +brunch +brunches +brunei +brunel +brunell +bruner +brunet +brunette +brunettes +brunetti +bruni +brunner +bruno +bruns +brunswick +brunt +brush +brushed +brushes +brushfire +brushing +brushwood +brushwork +brushy +brusque +brusquely +brussel +brussels +brut +brutal +brutalities +brutality +brutalize +brutalized +brutalizing +brutally +brute +brutish +brutus +bruxelles +bruyette +bryan +bryant +bryce +bryn +bryon +bryson +brzezinski +bs +bt +btu +bu +bub +bubba +bubble +bubbled +bubbles +bubbling +bubbly +bubby +bubonic +buccaneer +buccaneers +bucci +buch +buchan +buchanan +bucharest +bucher +buchholz +buchman +buchner +buchwald +buck +buckaroo +buckaroos +bucked +bucket +bucketful +buckets +buckey +buckeye +bucking +buckingham +buckland +buckle +buckled +buckles +buckley +buckling +buckman +buckminster +bucknell +buckner +bucks +buckshot +buckskin +buckwheat +bucky +bucolic +bucs +bud +budapest +budd +budde +buddha +buddhism +buddhist +buddhists +buddie +buddies +budding +buddy +budge +budged +budget +budgetary +budgeted +budgeting +budgets +budging +buds +budweiser +buehler +bueller +buena +buenas +bueno +buenos +buff +buffa +buffalo +buffaloes +buffalos +buffed +buffer +buffered +buffering +buffers +buffet +buffeted +buffeting +buffets +buffett +buffington +buffo +buffoon +buffs +buffy +buford +bug +bugaboo +bugged +bugger +buggers +buggies +bugging +buggy +bugle +bugler +bugles +bugs +bugsy +buhl +bui +buick +buicks +buie +build +builder +builders +building +buildings +builds +buildup +built +buis +buisness +buist +bukowski +bulb +bulbous +bulbs +bulbul +bulent +bulgaria +bulgarian +bulgarians +bulge +bulged +bulger +bulges +bulging +bulimia +bulimic +bulk +bulked +bulkeley +bulkhead +bulkheads +bulkier +bulks +bulky +bull +bulla +bullard +bulldog +bulldogs +bulldoze +bulldozed +bulldozer +bulldozers +bulldozing +buller +bullet +bulletin +bulletins +bulletproof +bullets +bullfight +bullfighter +bullfighting +bullfights +bullfrog +bullfrogs +bullhead +bullhorn +bullhorns +bullied +bullies +bullinger +bullion +bullish +bullishness +bullit +bullock +bullocks +bullpen +bullring +bulls +bullseye +bullshit +bullwhip +bullwinkle +bully +bullying +bulova +bulwark +bum +bumble +bumblebee +bumbled +bumbling +bummed +bummer +bump +bumped +bumper +bumpers +bumping +bumpkin +bumpkins +bumps +bumpy +bums +bumstead +bun +bunce +bunch +bunche +bunched +bunches +bunching +bund +bundesbank +bundestag +bundle +bundled +bundles +bundling +bunds +bundy +bung +bungalow +bungalows +bunge +bungee +bunger +bungle +bungled +bungler +bungling +bunk +bunker +bunkers +bunking +bunks +bunn +bunnell +bunnies +bunning +bunny +buns +bunt +bunting +bunton +bunyan +buoy +buoyancy +buoyant +buoyed +buoys +bur +buran +burba +burbank +burberry +burbs +burch +burchfield +burchill +burd +burden +burdened +burdening +burdens +burdensome +burdett +burdette +burdick +burdon +bure +bureacracy +bureau +bureaucracies +bureaucracy +bureaucrat +bureaucratic +bureaucratized +bureaucrats +bureaus +buren +burg +burge +burgeoned +burgeoning +burger +burgers +burgess +burgh +burgher +burglar +burglaries +burglars +burglary +burgos +burgoyne +burgundian +burgundies +burgundy +buri +burial +burials +buried +buries +burk +burka +burke +burkes +burkett +burkina +burks +burl +burlap +burleigh +burlesque +burley +burling +burlingame +burlington +burly +burma +burman +burmese +burn +burnaby +burned +burner +burners +burnet +burnett +burney +burnham +burning +burnings +burnish +burnished +burnout +burns +burnside +burnt +burp +burping +burr +burrell +burridge +burrill +burris +burrito +burritos +burro +burros +burrough +burroughs +burrow +burrowing +burrows +burry +bursa +bursitis +burson +burst +burstein +bursting +bursts +burt +burton +burts +burundi +burwell +bury +burying +bus +busboy +busboys +busby +buscemi +busch +bused +buser +buses +bush +bushed +bushel +bushels +bushes +bushman +bushmaster +bushmen +bushnell +bushy +busied +busier +busies +busiest +busily +business +businesses +businesslike +businessman +businessmen +businesspeople +businessperson +businesswoman +businesswomen +busing +busk +busker +busload +busloads +buss +bussed +busses +bussing +bust +bustamante +busted +buster +busters +bustier +bustillo +bustin +busting +bustle +bustling +busto +busts +busty +busy +busyness +but +butane +butch +butcher +butchered +butchering +butchers +butchery +butera +butkus +butler +butlers +buts +butt +buttafuoco +butte +butted +butter +butterball +buttercup +buttercups +buttered +butterfield +butterflies +butterfly +buttermilk +butternut +butters +butterscotch +butterworth +buttery +buttes +butthead +butting +buttock +buttocks +button +buttoned +buttonhole +buttonholed +buttons +buttress +buttressed +buttresses +buttressing +butts +butyl +butz +buxom +buy +buyable +buyback +buybacks +buyer +buyers +buying +buyout +buyouts +buys +buz +buzz +buzzard +buzzards +buzzed +buzzer +buzzes +buzzing +buzzword +buzzwords +buzzy +bx +by +byard +bye +byers +byes +bygone +bygones +bylaw +bylaws +byline +bylines +bypass +bypassed +bypasses +bypassing +byproduct +byproducts +byrd +byrne +byrnes +byron +bystander +bystanders +byte +bytes +bythe +byu +byway +byways +byword +byzantine +byzantium +ca +caan +cab +caba +cabal +caballero +cabana +cabanas +cabaret +cabbage +cabbages +cabbie +cabbies +cabby +cabdriver +cabell +cabello +cabernet +cabernets +cabezas +cabin +cabinet +cabinetry +cabinets +cabins +cable +cabled +cables +cabletron +cablevision +cabling +cabo +cabochon +caboodle +caboose +cabot +cabotage +cabral +cabrera +cabriolet +cabs +caca +cacao +cacciatore +cache +cached +caches +cachet +caching +cackle +cackling +cacophony +cacti +cactus +cad +cada +cadaver +cadavers +cadbury +caddell +caddie +caddies +caddy +caddyshack +cade +cadell +cadena +cadence +cadences +cadet +cadets +cadillac +cadillacs +cadiz +cadmium +cadogan +cadre +cadres +cads +cady +caen +caesar +caesarean +caesars +caetano +caf +cafe +cafes +cafeteria +cafeterias +caffeinated +caffeine +cage +caged +cages +cagey +cagney +cahill +cahn +cahoots +cai +cain +caine +caird +cairn +cairns +cairo +caisse +caitlin +cajole +cajoled +cajoling +cajun +cajuns +cake +caked +cakes +cakewalk +cal +cala +calabash +calabrese +calais +calamari +calamities +calamitous +calamity +calaveras +calc +calcification +calcified +calcite +calcium +calculate +calculated +calculates +calculating +calculation +calculations +calculator +calculators +calculus +calcutta +calder +caldera +calderon +caldwell +cale +caleb +caledonia +caledonian +calendar +calendars +calender +cales +caley +calf +calgary +calgon +calhoun +cali +caliber +calibrate +calibrated +calibrates +calibration +calibrations +calibre +calico +calif +califano +california +californian +californians +caliper +calipers +caliph +caliphate +calisthenics +calix +calkin +call +calla +callable +callaghan +callahan +callan +callanan +callas +callaway +callback +calle +called +callender +caller +callers +calles +calley +callie +calligrapher +calligraphic +calligraphy +calling +calliope +callisto +callous +calloused +callously +callousness +callow +calloway +calls +callum +callus +callused +calluses +cally +calm +calmed +calmer +calming +calmly +calmness +calms +caloric +calorie +calories +calpers +caltech +caltrans +calumny +calvary +calvert +calves +calvin +calving +calvinist +calvinists +calvino +calvo +calypso +calyx +calzone +cam +camacho +camara +camaraderie +camargo +camarillo +camaro +camas +cambell +camber +cambio +cambodia +cambodian +cambodians +cambria +cambrian +cambridge +camby +camco +camcorder +camcorders +camden +came +camel +camellia +camelot +camels +camembert +cameo +cameos +camera +cameraman +cameramen +cameras +camerawork +cameron +cameroon +camilla +camille +camillo +caminiti +camino +camisole +camorra +camouflage +camouflaged +camp +campa +campaign +campaigned +campaigner +campaigners +campaigning +campaigns +campanella +campanile +campbell +campeche +camped +camper +campers +campesinos +campfire +campfires +campground +campgrounds +campi +camping +campion +campo +campos +camps +campsite +campsites +campton +campus +campuses +campy +camry +camrys +cams +camshaft +camshafts +camus +can +cana +canaan +canaanite +canaanites +canada +canadian +canadians +canal +canales +canals +canandaigua +canapes +canard +canaries +canary +canasta +canavan +canaveral +canberra +canby +cancan +cancel +canceled +canceling +cancellation +cancellations +cancelled +cancelling +cancels +cancer +cancerous +cancers +cancun +candace +candela +candelabra +candelaria +candice +candid +candida +candidacies +candidacy +candidate +candidates +candidature +candide +candidiasis +candidly +candidness +candied +candies +candle +candlelight +candlelit +candler +candles +candlestick +candlesticks +candor +candy +cane +caned +canes +canfield +canine +canines +caning +canion +canister +canisters +canker +cann +cannabis +canned +canner +canneries +cannery +cannes +cannibal +cannibalism +cannibalistic +cannibalize +cannibalized +cannibalizing +cannibals +canning +cannister +cannon +cannonball +cannons +cannot +canny +cano +canoe +canoed +canoeing +canoes +canoga +canon +canonical +canonized +canons +canopied +canopies +canopy +cans +canseco +canst +cant +cantaloupe +cantankerous +cantata +canteen +canter +canterbury +canticle +cantilevered +cantina +canto +canton +cantonal +cantonese +cantons +cantor +cantrell +cantwell +canty +canucks +canvas +canvases +canvass +canvassed +canvassers +canvasses +canvassing +canyon +canyons +cap +capa +capabilities +capability +capable +capacious +capacitance +capacities +capacitive +capacitor +capacitors +capacity +cape +caped +capel +capella +capelli +caper +capers +caperton +capes +capetown +capillaries +capillary +capistrano +capita +capital +capitalism +capitalist +capitalistic +capitalists +capitalization +capitalizations +capitalize +capitalized +capitalizes +capitalizing +capitals +capitan +capitol +capitoline +capitols +capitulate +capitulated +capitulation +caplan +caplin +capo +capon +capone +capote +capp +capped +cappella +cappelletti +capper +cappiello +capping +capps +cappuccino +cappy +capra +capri +capriati +caprice +caprices +capricious +capriciously +capricorn +capris +capron +caps +capsaicin +capsize +capsized +capstan +capstone +capsule +capsules +capt +captain +captained +captains +caption +captioned +captioning +captions +captivate +captivated +captivating +captive +captives +captivity +captor +captors +capture +captured +captures +capturing +capuchin +caputo +car +cara +caracas +carafe +caramel +caramelized +caramels +carapace +carat +carats +caravan +caravans +caravel +caravelle +caraway +carb +carberry +carbide +carbine +carbines +carbo +carbohydrate +carbohydrates +carbon +carbonate +carbonated +carbonates +carbondale +carbone +carboniferous +carbonized +carbons +carboy +carbs +carburetor +carburetors +carcass +carcasses +carcinogen +carcinogenic +carcinogens +carcinoma +card +cardamom +cardboard +carded +cardenas +cardholder +cardholders +cardia +cardiac +cardiff +cardigan +cardigans +cardin +cardinal +cardinals +cardio +cardiologist +cardiologists +cardiology +cardiomyopathy +cardiopulmonary +cardiovascular +cardona +cardoon +cardoso +cardozo +cards +carducci +care +cared +careen +careened +careening +careens +career +careerism +careerist +careers +carefree +careful +carefully +caregivers +carel +careless +carelessly +carelessness +cares +caress +caresses +caressing +caret +caretaker +caretakers +caretaking +carew +carey +cargill +cargo +cargoes +cargos +caribbean +caribe +caribou +caricature +caricatured +caricatures +carillo +carillon +carina +caring +carjacking +carl +carla +carle +carleen +carles +carleton +carley +carli +carlin +carling +carlisle +carlo +carload +carloads +carlos +carlsbad +carlsen +carlson +carlsson +carlton +carly +carlyle +carmack +carmaker +carmakers +carman +carmel +carmelite +carmen +carmichael +carmine +carmody +carmona +carnage +carnahan +carnal +carnation +carnations +carne +carnegie +carneiro +carnelian +carney +carnival +carnivals +carnivore +carnivores +carnivorous +carny +caro +carob +carol +carole +carolina +carolinas +caroline +carolinian +carolinians +carols +carolus +carolyn +caron +carotene +carotid +carouse +carousel +carousing +carp +carpal +carpe +carpenter +carpenters +carpentry +carper +carpet +carpetbagger +carpetbaggers +carpeted +carpeting +carpets +carping +carpool +carport +carps +carr +carrasco +carrefour +carrel +carrell +carrels +carrer +carrera +carretta +carriage +carriages +carribean +carrick +carrie +carried +carrier +carriers +carries +carrillo +carrington +carrion +carrol +carroll +carrollton +carrot +carrots +carrousel +carruth +carry +carrying +carryout +carryover +cars +carson +carsten +carswell +cart +carta +cartagena +carte +carted +cartel +cartels +carter +carters +carthage +cartier +cartilage +carting +cartographic +cartography +carton +cartons +cartoon +cartooning +cartoonist +cartoonists +cartoons +cartridge +cartridges +carts +cartwheel +cartwheels +cartwright +carty +carucci +caruso +carvajal +carvalho +carve +carved +carvel +carvell +carver +carvers +carves +carvey +carville +carving +carvings +cary +caryl +caryn +cas +casa +casablanca +casal +casanova +casares +casas +casbah +cascade +cascaded +cascades +cascading +casco +case +casebook +casein +caseload +caseloads +casement +cases +casework +caseworker +caseworkers +casey +cash +cashed +cashes +cashew +cashews +cashflow +cashier +cashiered +cashiers +cashin +cashing +cashless +cashman +cashmere +casimir +casing +casings +casino +casinos +casio +cask +casket +caskets +casks +caso +caspar +casper +caspian +cass +cassady +cassandra +cassandras +cassata +cassatt +casse +cassel +cassell +cassells +cassels +casserly +casserole +casseroles +cassette +cassettes +cassia +cassidy +cassie +cassini +cassino +cassiopeia +cassis +cassius +casson +cassone +cast +castaneda +castano +castaway +castaways +caste +castell +castellana +castellano +castellanos +castelli +castello +castellon +caster +casters +castes +castigate +castigated +castigating +castiglione +castile +castilla +castille +castillo +casting +castings +castle +castleman +castles +castoffs +castor +castrate +castrated +castrating +castration +castro +castrol +casts +casual +casuality +casually +casualness +casualties +casualty +caswell +cat +cataclysm +cataclysmic +cataclysms +catacomb +catacombs +catagories +catalan +catalano +cataldo +catalina +catalog +cataloged +catalogers +cataloging +catalogs +catalogue +catalogued +catalogues +cataloguing +catalonia +catalyst +catalysts +catalytic +catalyze +catalyzed +catalyzes +catamaran +catamarans +catania +catapult +catapulted +catapults +cataract +cataracts +catastrophe +catastrophes +catastrophic +catastrophically +catatonia +catatonic +catawba +catbird +catcalls +catch +catchall +catcher +catchers +catches +catching +catchphrase +catchup +catchword +catchy +cate +catechism +categorical +categorically +categories +categorised +categorization +categorize +categorized +categorizes +categorizing +category +cater +catered +caterer +caterers +catering +caterpillar +caterpillars +caters +cates +catfight +catfish +cath +catharine +catharsis +cathartic +cathay +cathedra +cathedral +cathedrals +catherine +catheter +catheterization +catheters +cathey +cathleen +cathode +cathodes +catholic +catholicism +catholics +cathy +cation +cations +catlett +catlike +catlin +catnip +cato +cats +catskill +catsup +catt +cattle +cattleman +cattlemen +catto +catty +catwalk +catwoman +caucasian +caucasians +caucasus +caucus +caucuses +caudal +caudillo +caudle +caufield +caught +caul +cauldron +caulfield +cauliflower +caulk +caulking +causa +causal +causality +causation +causative +cause +caused +causes +causeway +causeways +causey +causing +caustic +cauterize +caution +cautionary +cautioned +cautioning +cautions +cautious +cautiously +cautiousness +cava +cavalcade +cavalier +cavaliere +cavalieri +cavalierly +cavaliers +cavalli +cavallo +cavalry +cavan +cavanagh +cave +caveat +caveats +caved +cavell +caveman +cavemen +cavendish +caver +cavern +cavernous +caverns +cavers +caves +caviar +cavil +caving +cavities +cavity +cavort +cavorting +caw +caxton +cay +cayce +cayenne +cayman +caymans +cc +ccm +ccs +cd +cdma +cdr +cdrom +ce +cea +ceasar +cease +ceased +ceasefire +ceaseless +ceaselessly +ceases +ceasing +cecelia +ceci +cecil +cecilia +cedar +cedars +cedarwood +cede +ceded +cedes +ceding +cedric +cee +cees +ceiba +ceil +ceiling +ceilings +cel +celadon +cele +celeb +celebrant +celebrants +celebrate +celebrated +celebrates +celebrating +celebration +celebrations +celebratory +celebrities +celebrity +celebs +celery +celesta +celeste +celestial +celestine +celia +celibacy +celibate +celica +celinda +celine +celio +cell +cella +cellar +cellars +cellblock +cellist +cellists +cellmate +cellnet +cello +cellophane +cellphone +cells +cellular +cellulitis +celluloid +cellulose +celsius +celt +celtic +celtics +celts +cement +cemented +cementing +cements +cemetaries +cemetary +cemeteries +cemetery +cen +censor +censored +censoring +censors +censorship +censure +censured +censures +census +censuses +cent +centaur +centauri +centaurs +centenarian +centenarians +centenary +centennial +center +centered +centerfielder +centerfold +centering +centerline +centerpiece +centers +centerville +centigrade +centigram +centime +centimes +centimeter +centimeters +centipede +centipedes +centra +central +centrale +centralia +centralised +centrality +centralization +centralize +centralized +centralizes +centralizing +centrally +centre +centred +centrepiece +centres +centrex +centric +centrifugal +centrifuge +centrist +centrists +centro +centrum +cents +centuries +centurion +century +cepa +cepeda +cephalon +cephalopod +cephalosporin +cept +cera +ceramic +ceramics +cereal +cereals +cerebellum +cerebral +cerebrum +ceremonial +ceremonially +ceremonies +ceremony +ceres +cerf +ceridian +cerise +cern +cerrito +cerritos +cerro +cert +certain +certainly +certainties +certainty +certifiable +certificate +certificates +certification +certifications +certified +certifies +certify +certifying +certiorari +certitude +cerulean +cerulli +cervantes +cerveza +cervical +cervix +cesar +cesare +cesarean +cesium +cess +cessation +cessna +cesspool +cetacean +cetera +cetus +ceylon +cezanne +cf +cfs +cg +ch +cha +chablis +chabot +chace +chacon +chad +chadwick +chaebol +chafe +chafed +chafee +chafes +chaff +chaffee +chafing +chagall +chagrin +chagrined +chai +chaim +chain +chained +chaining +chains +chainsaw +chainsaws +chair +chaired +chairing +chairman +chairmanship +chairmanships +chairmen +chairperson +chairpersons +chairs +chairwoman +chaise +chait +chalet +chalets +chalice +chalk +chalkboard +chalked +chalker +chalking +chalks +challenge +challenged +challenger +challengers +challenges +challenging +challis +chalmers +cham +chamber +chambered +chambering +chamberlain +chamberlin +chambermaid +chambers +chambliss +chameleon +chamois +chamonix +champ +champagne +champagnes +champaign +champion +championed +championing +champions +championship +championships +champs +chan +chana +chance +chancellor +chancellors +chancery +chances +chancey +chancy +chanda +chandelier +chandeliers +chandler +chandra +chanel +chaney +chang +change +changeable +changed +changeover +changeovers +changer +changers +changes +changing +channel +channeled +channeling +channels +channing +chant +chantal +chanted +chanterelle +chanteuse +chanticleer +chantilly +chanting +chants +chanukah +chao +chaos +chaotic +chap +chaparral +chapeau +chapel +chapelle +chapels +chaperone +chaperoning +chapin +chaplain +chaplains +chaplin +chapman +chappel +chappell +chaps +chapstick +chapter +chapters +char +chara +character +characterise +characterised +characteristic +characteristically +characteristics +characterization +characterizations +characterize +characterized +characterizes +characterizing +characters +charade +charades +charbonneau +charcoal +charcoals +chard +chardonnay +chare +charette +charge +chargeable +charged +charger +chargers +charges +charging +charing +chariot +charioteer +chariots +charisma +charismatic +charitable +charitably +charities +charity +charlatan +charlatans +charlemagne +charlene +charles +charleston +charlestown +charlesworth +charley +charlie +charlotte +charlottesville +charlottetown +charlton +charm +charmaine +charmed +charmer +charmers +charming +charmingly +charms +charnel +charon +charpentier +charred +charron +charry +chars +chart +charted +charter +chartered +charterhouse +chartering +charters +chartier +charting +chartist +chartists +chartrand +chartres +chartreuse +charts +chary +chas +chase +chased +chaser +chasers +chases +chasing +chasm +chasse +chassis +chaste +chasten +chastened +chastening +chastise +chastised +chastisement +chastises +chastising +chastity +chat +chateau +chateaux +chatfield +chatham +chats +chatsworth +chattahoochee +chattanooga +chatted +chattel +chattels +chatter +chatterbox +chattering +chatterjee +chatters +chatting +chatty +chatwin +chau +chaucer +chauffer +chauffeur +chauffeured +chauffeurs +chauncey +chautauqua +chauvinism +chauvinist +chauvinistic +chaves +chavez +chavis +chaw +chaz +che +cheadle +cheap +cheapen +cheapened +cheapening +cheaper +cheapest +cheapie +cheaply +cheapness +cheapo +cheapskate +cheat +cheated +cheater +cheaters +cheating +cheats +checchi +chechen +chechens +check +checkbook +checked +checker +checkerboard +checkered +checkers +checkfree +checking +checklist +checklists +checkmate +checkoff +checkout +checkouts +checkpoint +checkpoints +checks +checkup +checkups +cheddar +chee +cheech +cheek +cheekbone +cheekbones +cheeks +cheeky +cheep +cheer +cheered +cheerful +cheerfully +cheerier +cheerily +cheering +cheerio +cheerios +cheerleader +cheerleaders +cheerleading +cheers +cheery +cheese +cheeseburger +cheeseburgers +cheesecake +cheesed +cheeses +cheesiest +cheesy +cheetah +cheetahs +cheever +chef +chefs +chek +chekhov +chelating +chelation +chelmsford +chelsea +chem +chemical +chemically +chemicals +chemie +chemin +chemise +chemist +chemistry +chemists +chemo +chemotherapy +chen +cheney +cheng +chengdu +chenier +chenille +cheong +cheque +cheques +cher +cherie +cherish +cherished +cherishes +chernin +chernobyl +chernomyrdin +chernow +cherokee +cherokees +cherries +cherry +chert +cherub +cherubic +cherubs +chery +cheryl +chesapeake +cheshire +chesler +chesney +chess +chessboard +chessman +chessmaster +chessmen +chest +chested +chester +chesterfield +chesterton +chestnut +chestnuts +chests +chet +cheung +cheval +chevalier +cheviot +chevrolet +chevrolets +chevron +chevy +chevys +chew +chewable +chewed +chewer +chewing +chews +chewy +cheyenne +cheyennes +cheyney +chez +chi +chia +chiang +chianti +chiao +chiara +chiaro +chiat +chiba +chic +chica +chicago +chicagoan +chicagoans +chicanery +chicano +chicanos +chichester +chichi +chick +chicken +chickened +chickens +chicks +chico +chicory +chide +chided +chides +chiding +chief +chiefly +chiefs +chieftain +chieftains +chien +chiffon +chigger +chiggers +chihuahua +chihuahuas +child +childbearing +childbirth +childcare +childe +childhood +childhoods +childish +childless +childlike +childproof +children +childrens +childress +childs +chile +chilean +chileans +chiles +chili +chilies +chilis +chill +chilled +chiller +chilli +chillier +chillies +chilliness +chilling +chillingly +chills +chilly +chilton +chim +chime +chimed +chimera +chimes +chiming +chimney +chimneys +chimp +chimpanzee +chimpanzees +chimps +chin +china +chinatown +chinchilla +chine +chinese +ching +chink +chinks +chino +chinook +chinooks +chinos +chintzy +chip +chipmaker +chipmunk +chipmunks +chipped +chippendale +chipper +chippewa +chipping +chippy +chips +chipset +chiquita +chirac +chirico +chiron +chiropractic +chiropractor +chiropractors +chirp +chirping +chirps +chirpy +chis +chisel +chisels +chisholm +chit +chitchat +chitra +chits +chitty +chiu +chivalry +chive +chives +chiyoda +chloe +chlorate +chloride +chlorides +chlorinated +chlorinating +chlorination +chlorine +chlorofluorocarbons +chloroform +chlorophyll +chloroplast +chloroplasts +chloroquine +cho +choate +chock +chocks +chocolat +chocolate +chocolates +chocolatey +choctaw +choe +choi +choice +choices +choicest +choir +choirboy +choirs +chok +choke +choked +choker +chokes +choking +chola +cholera +choleric +cholesterol +cholla +chomp +chomping +chomsky +chon +chong +chongqing +choo +choose +chooser +choosers +chooses +choosing +choosy +chop +chopin +chopped +chopper +choppers +chopping +choppy +chopra +chops +chopstick +chopsticks +choral +chord +chords +chore +choreograph +choreographed +choreographer +choreographers +choreographic +choreographing +choreography +chores +chorion +chorizo +chortle +chortled +chortles +chortling +chorus +choruses +chose +chosen +chosing +chou +chow +chowder +chows +choy +chretien +chris +chriss +chrissy +christ +christa +christchurch +christen +christendom +christened +christening +christens +christensen +christenson +christi +christian +christiane +christiania +christianity +christianization +christianize +christianized +christians +christiansen +christianson +christie +christina +christine +christmas +christmases +christmastime +christo +christoph +christophe +christopher +christos +christs +christy +chromatograph +chromatography +chrome +chromed +chromium +chromosome +chromosomes +chron +chronic +chronically +chronicle +chronicled +chronicler +chroniclers +chronicles +chronicling +chronological +chronologically +chronologies +chronology +chrysalis +chrysanthemum +chrysanthemums +chrysler +chryslers +chrystal +chs +chu +chua +chuan +chuang +chubais +chubb +chubby +chuck +chucked +chuckie +chucking +chuckle +chuckled +chuckles +chuckling +chucks +chucky +chug +chugged +chugging +chui +chul +chum +chummy +chump +chumps +chums +chun +chung +chunk +chunks +chunky +chunnel +chur +church +churches +churchgoers +churchgoing +churchill +churchman +churchmen +churchyard +churlish +churn +churned +churning +churns +chute +chutes +chutney +chutzpah +cia +cially +ciampi +ciao +ciaran +ciba +cicada +cicadas +ciccone +cicely +cicero +cicerone +cid +cider +cie +ciel +cienega +cig +cigar +cigarette +cigarettes +cigars +cigna +cilantro +cilia +ciliary +cilicia +ciller +cima +cimarron +cimino +cinch +cinched +cincinatti +cincinnati +cinco +cinder +cinderella +cinders +cindy +cine +cinema +cinemas +cinematic +cinematographer +cinematographic +cinematography +cinemax +cineplex +cinerama +cini +cinnabar +cinnamon +cinque +cipher +ciphers +cipriani +cipriano +cir +circ +circa +circadian +circe +circle +circled +circles +circling +circuit +circuited +circuitous +circuitry +circuits +circular +circularity +circulars +circulate +circulated +circulates +circulating +circulation +circulations +circulatory +circumcise +circumcised +circumcision +circumference +circumferential +circumlocutions +circumnavigating +circumscribe +circumscribed +circumspect +circumspection +circumstance +circumstances +circumstantial +circumstantially +circumvent +circumvented +circumventing +circumvention +circumvents +circus +circuses +cirillo +cirque +cirrhosis +cirrus +cis +cisco +cisneros +cistercian +cistern +cisterns +cit +citadel +citadels +citation +citations +cite +cited +cites +citi +citibank +citicorp +cities +citing +citizen +citizenry +citizens +citizenship +citrate +citric +citroen +citron +citronella +citrus +city +cityscape +citywide +ciudad +civ +civet +civic +civics +civil +civilian +civilians +civilisation +civilised +civility +civilization +civilizations +civilize +civilized +civilly +ck +cl +clack +clad +claes +claeys +claiborne +claim +claimant +claimants +claimed +claiming +claims +clair +claire +clairol +clairvoyance +clairvoyant +clam +clambake +clamber +clambered +clammy +clamor +clamoring +clamour +clamp +clampdown +clamped +clamping +clamps +clams +clamshell +clan +clancy +clandestine +clandestinely +clang +clanging +clank +clanking +clannish +clans +clansman +clanton +clap +clapboard +clapp +clapped +clapper +clapping +claps +clapton +claptrap +clara +clare +claremont +clarence +clarendon +claret +clarice +claridge +clarification +clarifications +clarified +clarifies +clarify +clarifying +clarinet +clarinetist +clarion +claris +clarissa +clarity +clark +clarke +clarks +clarkson +clarkston +clarksville +claro +clash +clashed +clashes +clashing +clasp +clasped +class +classed +classen +classes +classic +classical +classically +classicism +classicist +classico +classics +classier +classiest +classifiable +classification +classifications +classified +classifieds +classifies +classify +classifying +classing +classless +classmate +classmates +classroom +classrooms +classy +clatter +claud +claude +claudette +claudia +claudine +claudio +claudius +claus +clause +clauses +claussen +claustrophobia +claustrophobic +clavichord +clavicle +clavier +claw +clawed +clawing +claws +claxton +clay +claymore +clays +clayson +clayton +clean +cleaned +cleaner +cleaners +cleanest +cleaning +cleanings +cleanliness +cleanly +cleans +cleanse +cleansed +cleanser +cleansers +cleanses +cleansing +cleanup +cleanups +clear +clearance +clearances +cleared +clearer +clearest +clearing +clearinghouse +clearinghouses +clearings +clearly +clearness +clears +clearwater +cleary +cleat +cleats +cleavage +cleavages +cleave +cleaver +cleaves +cleaving +cleese +clef +cleft +cleland +clem +clematis +clemence +clemency +clemens +clement +clemente +clementine +clements +clemmons +clemson +clench +clenched +cleo +cleopatra +clergy +clergyman +clergymen +cleric +clerical +clerics +clerk +clerking +clerks +clerkship +clerkships +clermont +cleve +cleveland +clever +cleverest +cleverly +cleverness +cli +cliche +cliched +cliches +click +clicked +clicker +clicking +clicks +client +clientele +clients +cliff +cliffhanger +cliffhangers +clifford +cliffs +cliffside +clift +clifton +climactic +climate +climates +climatic +climatological +climatologist +climax +climaxed +climaxes +climb +climbed +climber +climbers +climbing +climbs +clime +climes +clinch +clinched +clincher +clinches +clinching +cline +clines +cling +clinger +clinging +clings +clinic +clinical +clinically +clinician +clinicians +clinics +clink +clinker +clint +clinton +clintons +clio +clip +clipboard +clipped +clipper +clippers +clipping +clippings +clips +clique +cliques +clitoral +clitoris +clive +clk +clo +cloak +cloaked +cloaking +cloakroom +cloaks +clobber +clobbered +clobbering +clock +clocked +clocking +clocks +clockwise +clockwork +clod +clog +clogged +clogging +clogs +cloister +cloistered +cloisters +clone +cloned +clones +cloning +clooney +clorox +clos +close +closed +closely +closeness +closer +closers +closes +closest +closet +closeted +closets +closeup +closeups +closing +closings +closure +closures +clot +cloth +clothe +clothed +clothes +clothesline +clothier +clothiers +clothing +cloths +clots +clotted +clotting +cloture +cloud +clouded +cloudiness +clouding +cloudless +clouds +cloudy +clough +clout +clove +cloven +clover +cloves +clovis +clow +clower +clowes +clown +clowning +clowns +cloying +clr +club +clubbed +clubbers +clubbing +clubby +clubhouse +clubhouses +clubs +cluck +clucking +clucks +clue +clued +clueless +clues +cluff +clum +clump +clumping +clumps +clumpy +clumsily +clumsiness +clumsy +clung +clunk +clunker +clunkers +clunky +cluster +clustered +clustering +clusters +clutch +clutched +clutches +clutching +clutter +cluttered +cluttering +clyde +clydesdale +clymer +cm +cmd +cmdr +cmos +cnn +co +coach +coached +coaches +coaching +coachmen +coagulation +coakley +coal +coale +coalesce +coalesced +coalescing +coalition +coalitions +coals +coarse +coarsely +coarseness +coarsening +coarser +coast +coastal +coasted +coaster +coasters +coasting +coastline +coastlines +coasts +coat +coated +coates +coating +coatings +coats +coattails +coauthor +coauthored +coauthors +coax +coaxed +coaxial +coaxing +cob +cobain +cobalt +cobb +cobble +cobbled +cobbler +cobblers +cobblestone +cobblestones +cobbling +cobbs +cobe +coble +cobol +cobra +cobras +cobre +cobs +coburg +coburn +cobweb +cobwebs +coca +cocaine +cocco +cochin +cochlea +cochlear +cochran +cochrane +cock +cockatoo +cockatoos +cockburn +cocked +cocker +cockeyed +cockiness +cocking +cockles +cockney +cockpit +cockpits +cockrell +cockroach +cockroaches +cocks +cocktail +cocktails +cocky +coco +cocoa +cocom +coconut +coconuts +cocoon +cocooned +cocooning +cocoons +cocos +cod +coda +codding +coddle +coddled +coddling +code +codebreaker +codec +codecs +coded +codeine +coder +coders +codes +codex +codicils +codification +codified +codifies +codify +codifying +coding +codings +cody +coe +coed +coefficient +coefficients +coelho +coen +coequal +coerce +coerced +coercing +coercion +coercive +coeur +coexist +coexisted +coexistence +coexisting +coexists +cofer +coffee +coffeehouse +coffees +coffer +coffers +coffey +coffin +coffins +coffman +cofounder +cog +cogency +cogeneration +cogent +cogently +coghlan +cogitate +cogitation +cogito +cognac +cognate +cognisance +cognition +cognitive +cognizable +cognizance +cognizant +cognoscenti +cogs +cohabit +cohabitation +cohabiting +cohan +cohen +coherence +coherent +coherently +cohesion +cohesive +cohesively +cohesiveness +cohn +coho +cohort +cohorts +cohost +coif +coiffed +coiffure +coiffures +coil +coiled +coils +coin +coinage +coincide +coincided +coincidence +coincidences +coincident +coincidental +coincidentally +coincidently +coincides +coinciding +coined +coining +coins +cointreau +coit +coitus +cojones +coke +coker +cokes +cokie +coking +col +cola +colander +colas +colbert +colborne +colburn +colby +cold +colder +coldest +coldly +coldness +colds +coldwell +cole +coleen +coleman +colen +coleridge +coles +coleslaw +colette +coley +colgate +coli +colic +colin +coliseum +colitis +coll +collaborate +collaborated +collaborating +collaboration +collaborations +collaborative +collaboratively +collaborator +collaborators +collage +collagen +collages +collapse +collapsed +collapses +collapsible +collapsing +collar +collarbone +collard +collards +collared +collars +collate +collated +collateral +collateralized +collating +collation +colle +colleague +colleagues +collect +collectable +collected +collectibility +collectible +collectibles +collecting +collection +collections +collective +collectively +collectives +collectivism +collectivist +collectivization +collectivized +collector +collectors +collects +colleen +college +colleges +collegial +collegiality +collegians +collegiate +collegues +collen +collet +collette +colley +colli +collide +collided +collider +collides +colliding +collie +collier +colliers +colliery +collies +collin +colling +collingwood +collins +collis +collision +collisions +collison +collocation +colloid +colloidal +colloquial +colloquialisms +colloquium +colloquy +collude +colluded +colluding +collusion +collusive +collyer +colman +colo +cologne +colognes +colomb +colombe +colombia +colombian +colombians +colombo +colon +colonel +colonels +colonia +colonial +colonialism +colonialist +colonialists +colonials +colonic +colonies +colonist +colonists +colonization +colonize +colonized +colonizer +colonizers +colonizing +colonnade +colons +colony +color +colorado +coloration +coloratura +colored +colorfast +colorful +colorfully +coloring +colorized +colorless +colors +colossal +colossally +colosseum +colossi +colossus +colour +coloured +colours +cols +colson +colt +colter +colton +coltrane +colts +columba +columbia +columbian +columbine +columbo +columbus +column +columnar +columned +columnist +columnists +columns +colvin +colwell +com +coma +comanche +comandante +comas +comatose +comb +combat +combatant +combatants +combated +combating +combative +combativeness +combats +combatting +combe +combed +comber +combes +combination +combinations +combine +combined +combines +combing +combining +combo +combos +combs +combust +combustible +combustion +comcast +comdex +come +comeback +comebacks +comedian +comedians +comedic +comedienne +comedies +comedown +comedy +comely +comer +comerica +comers +comes +comet +cometary +cometh +comets +comeuppance +comfort +comfortable +comfortably +comforted +comforter +comforters +comforting +comforts +comfy +comic +comical +comically +comics +coming +comings +comiskey +comitatus +comite +comittee +comity +comm +comma +command +commandant +commanded +commandeer +commandeered +commander +commanders +commanding +commandment +commandments +commando +commandos +commands +commas +comme +commemorate +commemorated +commemorates +commemorating +commemoration +commemorations +commemorative +commence +commenced +commencement +commences +commencing +commend +commendable +commendably +commendation +commendations +commended +commending +commends +commensurate +commensurately +comment +commentaries +commentary +commentating +commentator +commentators +commented +commenting +comments +commerce +commercial +commercialism +commercialization +commercialize +commercialized +commercially +commercials +commerical +commerzbank +commie +commies +comming +commingle +commingled +commingling +commiserate +commish +commision +commisioner +commissar +commissaries +commissars +commissary +commission +commissioned +commissioner +commissioners +commissioning +commissions +commit +commited +commiting +commitment +commitments +commits +committal +committed +committee +committeeman +committees +committing +committment +commode +commodious +commodities +commodity +commodore +common +commonalities +commonality +commoner +commoners +commonly +commonplace +commons +commonsense +commonsensical +commonweal +commonwealth +commonwealths +commotion +communal +communalism +commune +communes +communi +communicable +communicate +communicated +communicates +communicating +communication +communications +communicative +communicator +communicators +communion +communique +communiques +communism +communist +communists +communities +community +communitywide +commutation +commute +commuted +commuter +commuters +commutes +commuting +como +comp +compact +compacted +compactness +compactor +compacts +compagnie +companhia +compania +companies +companion +companions +companionship +company +companys +compaq +comparability +comparable +comparably +comparative +comparatively +comparator +compare +compared +compares +comparing +comparision +comparison +comparisons +compartment +compartmentalize +compartmentalized +compartmentalizing +compartmented +compartments +compass +compassion +compassionate +compassionately +compatibility +compatible +compatibles +compatriot +compatriots +compel +compelled +compelling +compellingly +compels +compendium +compensable +compensate +compensated +compensates +compensating +compensation +compensations +compensatory +compete +competed +competence +competencies +competency +competent +competently +competes +competing +competition +competitions +competitive +competitively +competitiveness +competitor +competitors +compilation +compilations +compile +compiled +compiler +compilers +compiles +compiling +complacency +complacent +complacently +complain +complainant +complainants +complained +complainer +complainers +complaining +complains +complaint +complaints +complaisant +complement +complementarity +complementary +complemented +complementing +complements +complete +completed +completely +completeness +completes +completing +completion +completions +completly +complex +complexes +complexion +complexions +complexities +complexity +compliance +compliant +complicate +complicated +complicates +complicating +complication +complications +complicit +complicity +complied +complies +compliment +complimentary +complimented +complimenting +compliments +comply +complying +compo +component +components +comport +comportment +comports +compose +composed +composer +composers +composes +composing +composite +composites +composition +compositional +compositions +compost +composting +composure +compote +compound +compounded +compounding +compounds +comprehend +comprehended +comprehending +comprehends +comprehensibility +comprehensible +comprehension +comprehensive +comprehensively +comprehensiveness +compress +compressed +compresses +compressibility +compressing +compression +compressions +compressive +compressor +compressors +comprise +comprised +comprises +comprising +compromise +compromised +compromises +compromising +comps +compton +comptroller +compulsion +compulsions +compulsive +compulsively +compulsory +compunction +compunctions +compusa +compuserve +computation +computational +computationally +computations +compute +computed +computer +computerised +computerization +computerize +computerized +computerizing +computerland +computers +computerworld +computes +computing +compuware +comrade +comrades +coms +comsat +comstock +comus +con +conan +conant +conaty +concatenation +concave +conceal +concealed +concealing +concealment +conceals +concede +conceded +concedes +conceding +conceit +conceited +conceivable +conceivably +conceive +conceived +conceiving +concensus +concentrate +concentrated +concentrates +concentrating +concentration +concentrations +concentrator +concentrators +concentric +concepcion +concept +conception +conceptions +concepts +conceptual +conceptualization +conceptualize +conceptualized +conceptually +concern +concerned +concerning +concerns +concert +concerted +concertina +concertmaster +concerto +concertos +concerts +concession +concessionaire +concessionaires +concessional +concessions +conch +concha +concierge +conciliation +conciliator +conciliatory +conciousness +concise +concisely +conclave +conclaves +conclude +concluded +concludes +concluding +conclusion +conclusions +conclusive +conclusively +concoct +concocted +concocting +concoction +concoctions +concocts +concomitant +concomitantly +concord +concordance +concorde +concordia +concourse +concourses +concrete +concretely +concreteness +concubine +concubines +concur +concurred +concurrence +concurrent +concurrently +concurring +concurs +concussion +concussions +cond +conde +condemn +condemnation +condemnations +condemned +condemning +condemns +condensate +condensates +condensation +condense +condensed +condenser +condensers +condenses +condensing +condescend +condescending +condescendingly +condescension +condiment +condiments +condit +condition +conditional +conditionality +conditionally +conditioned +conditioner +conditioners +conditioning +conditions +condo +condolence +condolences +condom +condominium +condominiums +condoms +condon +condone +condoned +condones +condoning +condor +condors +condos +conducive +conduct +conductance +conducted +conducting +conduction +conductive +conductivity +conductor +conductors +conducts +conduit +conduits +cone +coneflower +cones +conestoga +coney +conf +confab +confection +confectioner +confectioners +confectionery +confections +confederacy +confederate +confederated +confederates +confederation +confer +conferees +conference +conferences +conferencing +conferred +conferring +confers +confess +confessed +confesses +confessing +confession +confessional +confessionals +confessions +confessor +confetti +confidant +confidante +confidantes +confidants +confide +confided +confidence +confidences +confident +confidential +confidentiality +confidentially +confidently +confides +confiding +configurable +configuration +configurations +configure +configured +configuring +confine +confined +confinement +confines +confining +confirm +confirmation +confirmations +confirmatory +confirmed +confirming +confirms +confiscate +confiscated +confiscating +confiscation +confiscations +confiscatory +confit +conflagration +conflated +conflates +conflating +conflict +conflicted +conflicting +conflicts +confluence +conform +conformance +conformation +conformed +conforming +conformist +conformists +conformity +conforms +confound +confounded +confounding +confounds +confront +confrontation +confrontational +confrontations +confronted +confronting +confronts +confucian +confucianism +confucius +confuse +confused +confuses +confusing +confusingly +confusion +confusions +cong +conga +congealed +congenial +congeniality +congenital +congenitally +conger +congested +congestion +congestive +conglomerate +conglomerates +conglomeration +congo +congolese +congrats +congratulate +congratulated +congratulates +congratulating +congratulation +congratulations +congratulatory +congregants +congregate +congregated +congregating +congregation +congregational +congregations +congress +congresses +congressional +congressionally +congressman +congressmen +congressperson +congresswoman +congruence +congruent +conical +conifer +coniferous +conifers +conjecture +conjectured +conjectures +conjoin +conjoined +conjoining +conjugal +conjugate +conjugated +conjugates +conjugation +conjugations +conjunction +conjunctions +conjure +conjured +conjures +conjuring +conk +conklin +conley +conlon +conn +connally +connaught +connect +connected +connectedness +connecticut +connecting +connection +connections +connective +connectivity +connector +connectors +connects +conned +connell +connelly +conner +conners +connery +connexion +connick +connie +conning +connivance +connive +conniving +connoisseur +connoisseurs +connolly +connor +connors +connotation +connotations +connote +connoted +connotes +conny +conoco +conor +conquer +conquered +conquering +conqueror +conquerors +conquers +conquest +conquests +conquistador +conquistadors +conrad +conrail +conroy +cons +conscience +consciences +conscientious +conscientiously +conscious +consciously +consciousness +conscript +conscripted +conscription +conscripts +consecrate +consecrated +consecrating +consecration +consecrations +consecutive +consecutively +consensual +consensus +consent +consented +consenting +consents +consequence +consequences +consequent +consequential +consequently +conservancy +conservation +conservationist +conservationists +conservations +conservatism +conservative +conservatively +conservatives +conservativism +conservator +conservatories +conservators +conservatorship +conservatory +conserve +conserved +conserves +conserving +consider +considerable +considerably +considerate +consideration +considerations +considered +considering +considers +considine +consiglio +consign +consigned +consigning +consignment +consignments +consist +consistant +consisted +consistencies +consistency +consistent +consistently +consisting +consists +consol +consolation +consolations +console +consoled +consoles +consolidate +consolidated +consolidates +consolidating +consolidation +consolidations +consolidator +consolidators +consoling +consonance +consonant +consonants +consort +consortia +consorting +consortium +consortiums +conspicuous +conspicuously +conspiracies +conspiracy +conspirator +conspiratorial +conspirators +conspire +conspired +conspiring +const +constable +constables +constabulary +constance +constancy +constant +constantin +constantine +constantinople +constantly +constants +constellation +constellations +consternation +constipated +constipation +constituencies +constituency +constituent +constituents +constitute +constituted +constitutes +constituting +constitution +constitutional +constitutionality +constitutionally +constitutions +constrain +constrained +constraining +constrains +constraint +constraints +constrict +constricted +constricting +constriction +constrictions +constrictor +constricts +construct +constructed +constructing +construction +constructionist +constructions +constructive +constructively +constructor +constructors +constructs +construe +construed +consuela +consul +consular +consulate +consulates +consuls +consult +consultancy +consultant +consultants +consultation +consultations +consultative +consulted +consulting +consults +consumable +consumables +consumate +consume +consumed +consumer +consumerism +consumerist +consumers +consumes +consuming +consummate +consummated +consummating +consummation +consumption +cont +contact +contacted +contacting +contacts +contagion +contagious +contain +contained +container +containerized +containers +containing +containment +contains +contaminant +contaminants +contaminate +contaminated +contaminates +contaminating +contamination +contaminations +contant +conte +contemplate +contemplated +contemplates +contemplating +contemplation +contemplative +contemporaneous +contemporaneously +contemporaries +contemporary +contempt +contemptible +contemptuous +contemptuously +contend +contended +contender +contenders +contending +contends +content +contented +contentedly +contention +contentions +contentious +contentiousness +contentment +contents +contessa +contest +contestable +contestant +contestants +contested +contesting +contests +context +contexts +contextual +contextually +conti +contiguity +contiguous +continent +continental +continentals +continents +contingencies +contingency +contingent +contingents +continua +continual +continually +continuance +continuation +continue +continued +continues +continuing +continuity +continuous +continuously +continuum +contort +contorted +contorting +contortion +contortionist +contortions +contorts +contour +contoured +contouring +contours +contra +contraband +contraception +contraceptive +contraceptives +contract +contracted +contracting +contraction +contractions +contractor +contractors +contracts +contractual +contractually +contradict +contradicted +contradicting +contradiction +contradictions +contradictory +contradicts +contradistinction +contrails +contraire +contraption +contraptions +contrarian +contrarians +contrary +contras +contrast +contrasted +contrasting +contrasts +contravene +contravenes +contravention +contreras +contretemps +contribute +contributed +contributes +contributing +contribution +contributions +contributor +contributors +contributory +contrite +contrition +contrivance +contrivances +contrive +contrived +contriving +control +controling +controllable +controlled +controller +controllers +controlling +controls +controversial +controversially +controversies +controversy +contruction +contusion +contusions +conundrum +conundrums +conus +conv +convalescence +convalescent +convalescing +convection +convene +convened +convenes +convenience +conveniences +convenient +conveniently +convening +convent +convention +conventional +conventionally +conventioneers +conventions +converge +converged +convergence +convergent +converges +converging +conversant +conversation +conversational +conversationalist +conversations +converse +conversed +conversely +converses +conversing +conversion +conversions +convert +converted +converter +converters +convertibility +convertible +convertibles +converting +converts +convex +convexity +convey +conveyance +conveyances +conveyed +conveyer +conveying +conveyor +conveyors +conveys +convict +convicted +convicting +conviction +convictions +convicts +convince +convinced +convinces +convincing +convincingly +convivial +convocation +convoluted +convolution +convolutions +convoy +convoys +convulsing +convulsion +convulsions +convulsive +conway +conwell +conyers +coo +cooing +cook +cookbook +cookbooks +cooke +cooked +cooker +cookers +cookery +cookie +cookies +cooking +cookout +cookouts +cooks +cookson +cookware +cool +coolant +coolants +cooled +cooler +coolers +coolest +cooley +coolidge +cooling +coolly +coolness +cools +coombe +coombs +coon +cooney +coons +coop +cooped +cooper +cooperage +cooperate +cooperated +cooperates +cooperating +cooperation +cooperations +cooperative +cooperatively +cooperatives +coopers +coops +coordinate +coordinated +coordinates +coordinating +coordination +coordinator +coordinators +coors +coos +coot +coots +coover +cop +copa +copacabana +copayment +copayments +copco +cope +coped +copeland +copenhagen +coper +copernican +copernicus +copes +copied +copier +copiers +copies +copilot +coping +copious +copland +copley +copp +coppa +copped +copper +copperfield +copperhead +copperheads +coppers +coppersmith +coppery +coppin +copping +copple +coppola +copps +copra +coproduction +cops +copse +copses +copter +coptic +copulate +copulation +copy +copycat +copycats +copying +copyright +copyrighted +copyrights +copywriter +copywriters +coquettish +cor +cora +coral +corals +coram +corazon +corbet +corbett +corbin +corby +corcoran +cord +corded +cordell +corder +cordero +cordes +cordial +cordiality +cordially +cordials +cording +cordis +cordless +cordoba +cordon +cordoned +cordova +cords +cordura +corduroy +cordy +core +corea +cored +corel +corelli +cores +coretta +corey +corgi +coriander +corina +corinna +corinne +corinth +corinthian +corinthians +cork +corkage +corker +corks +corkscrew +corkscrews +corky +corley +cormac +cormack +corman +cormier +cormorant +cormorants +corn +cornbread +corncob +corne +cornea +corneal +corneas +corned +cornel +cornelia +cornelio +cornelius +cornell +corner +cornerback +cornered +cornering +corners +cornerstone +cornerstones +cornet +cornett +cornfield +cornfields +cornflakes +cornflowers +cornice +corniche +corning +cornish +cornmeal +corns +cornstalk +cornstalks +cornstarch +cornucopia +cornwall +cornwallis +cornwell +corny +corolla +corollaries +corollary +corollas +corona +coronado +coronal +coronary +coronas +coronation +coroner +coroners +coronet +coronets +corp +corpora +corporal +corporate +corporately +corporation +corporations +corporatism +corporatist +corporeal +corps +corpse +corpses +corpsman +corpulent +corpus +corr +corral +corrales +corralled +corralling +corrals +correa +correct +correctable +corrected +correcting +correction +correctional +corrections +corrective +correctly +correctness +corrects +correlate +correlated +correlates +correlating +correlation +correlations +correll +corrente +correspond +corresponded +correspondence +correspondences +correspondent +correspondents +corresponding +correspondingly +corresponds +corridor +corridors +corrie +corrigan +corroborate +corroborated +corroborates +corroborating +corroboration +corroborative +corrode +corroded +corrodes +corroding +corrosion +corrosive +corrugated +corrupt +corrupted +corrupter +corrupting +corruption +corruptions +corruptly +corrupts +corry +corsage +corsages +corsair +corse +corset +corsica +corso +cort +corte +cortes +cortese +cortex +cortez +corti +cortical +corticosteroid +corticosteroids +cortina +cortisone +corum +coruscating +corvallis +corvette +corvettes +corwin +cory +cos +cosa +cosby +cose +cosey +cosi +cosic +cosine +cosmetic +cosmetically +cosmetics +cosmetologist +cosmetology +cosmic +cosmo +cosmological +cosmologist +cosmologists +cosmology +cosmonaut +cosmonauts +cosmopolitan +cosmopolitans +cosmos +cosponsor +cosponsored +cossack +cossacks +cost +costa +costal +costantino +costanza +costar +costas +costco +costed +costello +coster +costigan +costing +costless +costlier +costliest +costly +coston +costs +costume +costumed +costumer +costumers +costumes +costuming +cosy +cot +cote +coterie +cotes +coto +cots +cott +cotta +cottage +cottages +cotter +cottle +cotton +cottoned +cottons +cottonseed +cottonwood +cottonwoods +cottony +cottrell +couch +couched +couches +coud +cougar +cougars +cough +coughed +coughing +coughlin +coughs +could +coulda +couldn +couldnt +coulee +couleur +coulis +coulon +coulson +coulter +council +councillor +councillors +councilman +councilmen +councilor +councilors +councils +councilwoman +counsel +counseling +counselling +counsellor +counsellors +counselor +counselors +counsels +count +countable +countdown +counted +countenance +countenanced +countenances +counter +counteract +counteracted +counteracting +counteracts +counterargument +counterattack +counterattacks +counterbalance +counterbalanced +counterbalancing +counterclaim +counterclaims +counterclockwise +counterculture +countered +counterexamples +counterfeit +counterfeited +counterfeiter +counterfeiters +counterfeiting +countering +counterinsurgency +counterintelligence +counterintuitive +countermand +countermeasure +countermeasures +counteroffensive +counteroffer +counterpart +counterparts +counterpoint +counterproductive +counterproposal +counterproposals +counterpunch +counterrevolution +counterrevolutionary +counters +countersigned +countersuit +countertenor +counterterrorism +countertop +countervailing +counterweight +countess +counties +counting +countless +countries +country +countryman +countrymen +countryside +countrywide +counts +county +countywide +coup +coupe +coupes +couple +coupled +coupler +couplers +couples +couplet +couplets +coupling +couplings +coupon +coupons +coups +courage +courageous +courageously +courant +courier +couriers +course +courses +coursework +coursing +courson +court +courted +courtenay +courteous +courter +courtesies +courtesy +courthouse +courthouses +courtier +courtiers +courting +courtly +courtney +courtois +courtroom +courtrooms +courts +courtship +courtside +courtyard +courtyards +couse +cousin +cousins +cousteau +couto +couture +couturier +covalent +cove +coven +covenant +covenants +covent +coventry +cover +coverage +coverages +coverall +coveralls +coverdell +covered +covering +coverings +covers +covert +covertly +coverup +coverups +coves +covet +coveted +coveting +covetous +covets +covey +covina +covington +cow +cowan +coward +cowardice +cowardly +cowards +cowart +cowbell +cowbells +cowboy +cowboys +cowden +cowed +cowell +cowen +cower +cowered +cowering +cowers +cowgirl +cowgirls +cowherd +cowl +cowley +cowling +cowman +coworker +coworkers +cowper +cows +cox +coxe +coy +coyle +coyly +coyne +coyness +coyote +coyotes +coz +cozier +coziness +cozy +cozying +cp +cps +cpt +cpu +cpus +cr +crab +crabbe +crabbed +crabby +crabmeat +crabs +crabtree +crack +crackdown +crackdowns +cracked +cracker +crackerjack +crackers +cracking +crackle +crackled +crackles +crackling +crackpot +crackpots +cracks +cradle +cradled +cradles +cradling +craft +crafted +crafter +craftiness +crafting +craftmanship +crafts +craftsman +craftsmanship +craftsmen +craftspeople +crafty +crag +cragg +craggy +craig +crain +cram +cramer +crammed +cramming +cramp +cramped +cramping +cramps +cranberries +cranberry +crandall +crane +cranes +cranford +cranial +cranium +crank +crankcase +cranked +cranking +cranks +crankshaft +cranky +crannies +cranny +cranston +crap +crape +crapo +crapped +crapper +crappy +craps +crash +crashed +crashes +crashing +crass +crate +crater +cratered +craters +crates +crave +craved +craven +craver +craves +craving +cravings +craw +crawfish +crawford +crawl +crawled +crawler +crawley +crawling +crawls +crawly +cray +crayfish +crayola +crayon +crayons +craze +crazed +crazier +crazies +craziest +crazily +craziness +crazy +crea +creak +creaked +creaking +creaks +creaky +cream +creamed +creamer +creamery +creaming +creams +creamy +crease +creases +creasy +creat +create +created +creates +creatine +creating +creation +creationism +creationist +creations +creative +creatively +creativeness +creativity +creator +creators +creature +creatures +credence +credential +credentialed +credentials +credibility +credible +credibly +credit +creditable +credited +crediting +creditor +creditors +credits +creditworthiness +credo +credulity +credulous +cree +creech +creed +creeds +creek +creeks +creel +creep +creepiness +creeping +creeps +creepy +cregan +creighton +cremate +cremated +cremation +crematorium +creme +cremona +crenshaw +creole +creoles +creosote +crepe +crepes +crept +crepuscular +cres +crescendo +crescent +crescents +crespi +cress +cressey +cresson +cresswell +crest +crested +crestfallen +cresting +crests +cretaceous +crete +cretins +crevasses +crevice +crevices +crew +crewed +crewman +crewmen +crews +crib +cribbage +cribbed +cribbs +cribs +crichton +crick +cricket +crickets +cried +crier +cries +crim +crime +crimea +crimean +crimes +criminal +criminality +criminalization +criminalize +criminalized +criminally +criminals +criminologist +criminologists +criminology +crimp +crimped +crimping +crimson +cringe +cringed +cringes +cringing +crinkled +crinkles +crippen +cripple +crippled +cripples +crippling +crips +cris +crisco +crises +crisis +crisp +crisper +crispin +crisply +crispness +crispy +criss +crisscross +crisscrossed +crisscrossing +crist +cristal +cristiano +cristina +cristo +cristobal +cristy +criswell +crit +criteria +criterion +criterium +critic +critical +criticality +critically +criticise +criticised +criticising +criticism +criticisms +criticize +criticized +criticizes +criticizing +critics +critique +critiqued +critiques +critiquing +crittenden +critter +critters +cro +croak +croaker +croaking +croat +croatia +croatian +croatians +croats +croc +croce +crochet +crocheted +crocheting +crock +crocker +crockery +crockett +crocodile +crocodiles +crocus +crocuses +croft +crofton +crofts +croissant +croissants +croix +croke +croker +cromwell +cron +crone +cronies +cronin +cronk +cronkite +cronus +crony +cronyism +crook +crooked +crookedness +crooks +croon +crooner +crooning +croons +crop +cropland +cropped +cropper +cropping +crops +croquet +crosbie +crosby +cross +crossbones +crossbow +crossbows +crosscurrents +crosscutting +crosse +crossed +crossen +crosser +crosses +crossfire +crosshairs +crossing +crossings +crossley +crosslink +crossover +crossovers +crossroad +crossroads +crosstalk +crosstown +crosswalk +crosswise +crossword +crosswords +crotch +crotchety +croton +crotty +crouch +crouched +croucher +crouching +croup +croupier +crouse +croutons +crow +crowbar +crowbars +crowd +crowded +crowder +crowding +crowds +crowe +crowed +crowell +crowing +crowley +crown +crowne +crowned +crowning +crowns +crows +crowther +croy +croydon +crozier +crs +cruces +crucial +crucially +cruciate +crucible +crucibles +crucified +crucifix +crucifixes +crucifixion +crucify +crucis +crud +cruddy +crude +crudely +crudeness +crudes +crudest +crudup +cruel +cruelest +cruelly +cruelties +cruelty +cruise +cruised +cruiser +cruisers +cruises +cruising +crum +crumb +crumble +crumbled +crumbles +crumbling +crumbs +crumley +crummy +crump +crumple +crumpled +crumpler +crumpling +crunch +crunched +cruncher +crunchers +crunches +crunching +crunchy +crus +crusade +crusader +crusaders +crusades +crusading +cruse +crush +crushable +crushed +crusher +crushers +crushes +crushing +crusoe +crust +crustacean +crustaceans +crustal +crusted +crusts +crusty +crutch +crutcher +crutches +crux +cruz +cruzado +cry +crybaby +crying +cryogenic +cryogenics +crypt +cryptic +cryptically +crypto +cryptogram +cryptographic +cryptography +cryptosporidium +crypts +crystal +crystalline +crystallization +crystallize +crystallized +crystallizes +crystallizing +crystallography +crystals +ct +cto +ctr +ctrl +cu +cuarto +cub +cuba +cuban +cubans +cubbies +cubbyhole +cube +cubed +cubes +cubic +cubical +cubicle +cubicles +cubism +cubist +cubs +cuckoo +cuckoos +cucumber +cucumbers +cud +cuddle +cuddled +cuddles +cuddling +cuddly +cuddy +cudgel +cudgels +cue +cued +cuellar +cuero +cuervo +cues +cuesta +cuevas +cuff +cuffed +cufflinks +cuffs +cuisine +cuisines +cul +culebra +culinary +cull +culled +cullen +culley +culligan +culling +culls +cullum +cully +culminate +culminated +culminates +culminating +culmination +culp +culpa +culpability +culpable +culpepper +culprit +culprits +cult +cultic +cultist +cultists +cultivar +cultivate +cultivated +cultivates +cultivating +cultivation +cults +cultural +culturally +culture +cultured +cultures +culturing +culver +culverhouse +culvert +culverts +cum +cumberland +cumbersome +cumin +cummerbund +cumming +cummings +cummins +cumulative +cumulatively +cumulus +cunard +cuneiform +cuneo +cunha +cunning +cunningham +cuny +cuomo +cup +cupboard +cupboards +cupcake +cupcakes +cupertino +cupid +cupids +cuppa +cupping +cups +cur +curable +curacao +curare +curate +curative +curator +curatorial +curators +curb +curbed +curbing +curbs +curbside +curcio +curd +curdle +curdled +curds +cure +cured +cures +curfew +curfews +curia +curial +curie +curing +curios +curiosities +curiosity +curious +curiouser +curiousity +curiously +curl +curled +curler +curlers +curley +curling +curls +curly +curmudgeon +curragh +curran +currant +currants +currencies +currency +current +currently +currents +currenty +curricula +curricular +curriculum +curriculums +currie +curried +curries +curry +currying +curs +curse +cursed +curses +cursing +cursive +cursor +cursors +cursory +curt +curtail +curtailed +curtailing +curtailment +curtailments +curtails +curtain +curtains +curtin +curtis +curtiss +curtly +curtsy +curvaceous +curvature +curve +curveball +curved +curves +curving +curvy +curzon +cus +cusack +cush +cushing +cushion +cushioned +cushioning +cushions +cushman +cushy +cusp +cuss +cussed +cussing +custard +custer +custis +custodial +custodian +custodians +custody +custom +customarily +customary +customer +customers +customizable +customization +customize +customized +customizing +customs +cut +cutaneous +cutaway +cutback +cutbacks +cute +cuteness +cuter +cutest +cutesy +cuthbert +cuticle +cuticles +cutie +cuties +cutlass +cutler +cutlery +cutoff +cutoffs +cutout +cutouts +cutover +cuts +cutter +cutters +cutthroat +cutting +cuttings +cutty +cuvee +cuyahoga +cv +cy +cyan +cyanamid +cyanide +cybele +cyber +cybernetics +cyberspace +cyborg +cyborgs +cycads +cyclades +cycle +cycled +cycles +cyclic +cyclical +cyclicality +cyclically +cycling +cyclist +cyclists +cyclone +cyclones +cyclonic +cyclopean +cyclops +cyclotron +cygnus +cylinder +cylinders +cylindrical +cymbal +cymbals +cyndi +cynic +cynical +cynically +cynicism +cynics +cynosure +cynthia +cypher +cyphers +cypress +cyprian +cypriot +cypriots +cyprus +cyril +cyrillic +cyrix +cyrus +cyst +cystic +cysts +cytology +cytomegalovirus +cytoplasm +cytotoxic +czar +czarist +czars +czech +czechoslovak +czechoslovakia +czechoslovakian +czechs +da +dab +dabbing +dabble +dabbled +dabblers +dabbles +dabbling +dabney +dac +dace +dacha +dachshund +dachshunds +dacron +dactyl +dad +dada +daddies +daddy +dade +dado +dads +dae +daedalus +daemon +daewoo +daffodil +daffodils +daffy +dafoe +daft +dag +dagestan +dagger +daggers +daggett +dagmar +dagon +dagwood +dah +dahl +dahlem +dahlgren +dahlia +dahlias +dahlquist +dahmer +dail +dailey +dailies +daily +daimler +dain +dainty +daiquiri +dairies +dairy +dairyman +dais +daisies +daisy +daiwa +dak +dakota +dakotans +dakotas +dal +dalai +dale +daler +dales +daley +dali +dalia +dalian +dall +dalla +dallas +dalliance +dallied +dally +dalmatian +dalmatians +dalrymple +dalton +daltons +daly +dam +dama +damage +damaged +damages +damaging +daman +damas +damascus +damask +damato +dame +dames +damian +damiano +damico +damien +damm +damme +dammed +damming +dammit +damn +damnable +damnably +damnation +damndest +damned +damnedest +damning +damns +damocles +damon +damp +damped +dampen +dampened +dampener +dampening +dampens +damper +dampers +damping +dampness +dams +damsel +damsels +dan +dana +danbury +dance +danceable +danced +dancer +dancers +dances +dancing +dancy +dandelion +dandelions +dander +dandies +dando +dandruff +dandy +dane +danes +danforth +dang +danger +dangerfield +dangerous +dangerously +dangers +dangle +dangled +dangles +dangling +dani +dania +daniel +daniela +daniele +daniell +danielle +daniels +danielson +danis +danish +dank +danke +danker +danko +dann +danna +danner +danny +dans +danser +dansk +dante +danton +danube +danvers +danza +danzig +dao +daoud +dap +daphne +dapper +dapple +dar +dara +darby +darcy +darden +dare +dared +daredevil +daredevils +daren +dares +daresay +dari +daria +darien +darin +daring +daringly +dario +darius +darjeeling +dark +darken +darkened +darkening +darkens +darker +darkest +darkly +darkness +darkroom +darlene +darley +darling +darlings +darlington +darman +darmstadt +darn +darndest +darned +darnedest +darnell +darpa +darrah +darrel +darrell +darren +darrick +darrin +darrow +darryl +darshan +dart +dartboard +darted +darter +darth +darting +dartmouth +darton +darts +darwin +darwinian +darwinism +darwish +darya +daryl +das +dasa +daschle +dash +dashboard +dashboards +dashed +dasher +dashes +dashiell +dashing +dasilva +dassault +dastardly +dat +data +database +databases +datafile +dataquest +datas +dataset +date +datebook +dated +dateline +dater +dates +dating +dato +datsun +datuk +datum +daub +daube +daubed +dauber +daugherty +daughter +daughters +daum +daun +daunt +daunted +daunting +dauphin +dauphine +dave +davenport +daves +davey +davi +david +davide +davidian +davidians +davids +davidson +davie +davies +davila +davin +davis +davison +davisson +davos +davy +daw +dawdle +dawdling +dawe +dawes +dawkins +dawley +dawn +dawned +dawning +dawns +daws +dawson +day +daya +dayan +daybook +daybreak +daycare +daydream +daydreamed +daydreaming +daydreams +daylight +daylights +daylong +days +daytime +dayton +daytona +daze +dazed +dazzle +dazzled +dazzling +db +dbase +dc +dd +de +dea +deacon +deaconess +deacons +deactivate +deactivated +deactivates +deactivation +dead +deadbeat +deadbeats +deaden +deadening +deader +deadhead +deadheads +deadlier +deadliest +deadline +deadlines +deadliness +deadlock +deadlocked +deadlocks +deadly +deadman +deadpan +deadweight +deadwood +deaf +deafening +deafness +deal +dealer +dealers +dealership +dealerships +dealing +dealings +dealmaker +dealmaking +deals +dealt +dealy +dean +deana +deane +deaner +deangelis +deanna +deans +dear +dearborn +dearest +dearie +dearly +dears +dearth +deary +deas +death +deathbed +deathless +deathly +deaths +deathwatch +deb +debacle +debacles +debarred +debase +debased +debasement +debasing +debatable +debate +debated +debater +debaters +debates +debating +debauched +debauchery +debbie +debby +debenture +debentures +debevoise +debi +debilitate +debilitated +debilitating +debility +debit +debits +debonair +debora +deborah +debord +debra +debrief +debriefed +debriefing +debris +debs +debt +debtor +debtors +debts +debug +debugged +debugger +debugging +debunk +debunked +debunking +debunks +debussy +debut +debutante +debutantes +debuted +debuting +debuts +dec +decade +decadence +decadent +decades +decaf +decaffeinated +decal +decalogue +decals +decamp +decamped +decant +decanter +decanters +decanting +decapitate +decapitated +decapitation +decathlon +decatur +decay +decayed +decaying +decays +decease +deceased +decedent +deceit +deceitful +deceits +deceive +deceived +deceiver +deceives +deceiving +decelerate +decelerated +decelerating +deceleration +decem +december +decency +decennial +decent +decently +decentralization +decentralize +decentralized +decentralizing +deception +deceptions +deceptive +deceptively +decibel +decibels +decide +decided +decidedly +decides +deciding +deciduous +decile +decimal +decimals +decimate +decimated +decimating +decimation +decipher +decipherable +deciphered +deciphering +decipherment +decision +decisionmaking +decisions +decisive +decisively +decisiveness +deck +decked +decker +deckers +deckhand +deckhands +decking +decks +declaim +declaimed +declarant +declaration +declarations +declarative +declaratory +declare +declared +declares +declaring +declassified +declassify +declination +decline +declined +declines +declining +decnet +deco +decode +decoded +decoder +decoders +decodes +decoding +decommission +decommissioned +decommissioning +decompose +decomposed +decomposes +decomposing +decomposition +decompress +decompressed +decompression +deconcini +decongestant +decongestants +deconstruct +deconstruction +decontaminate +decontaminated +decontamination +decontrol +decor +decorate +decorated +decorating +decoration +decorations +decorative +decorator +decorators +decorous +decorum +decouple +decoupled +decoupling +decoy +decoys +decrease +decreased +decreases +decreasing +decree +decreed +decrees +decrement +decrepit +decrepitude +decried +decries +decriminalization +decriminalize +decriminalized +decriminalizing +decry +decrying +decrypt +decrypted +decryption +dedham +dedicate +dedicated +dedicates +dedicating +dedication +dedications +dedman +deduce +deduced +deduct +deducted +deductibility +deductible +deductibles +deducting +deduction +deductions +deductive +deducts +dee +deed +deeded +deedee +deeds +deejay +deem +deemed +deems +deen +deep +deepak +deepen +deepened +deepening +deepens +deeper +deepest +deeply +deepwater +deer +deere +deerfield +deering +dees +def +deface +defaced +defacing +defacto +defamation +defamatory +defame +defamed +defaming +default +defaulted +defaulter +defaulters +defaulting +defaults +defazio +defcon +defeat +defeated +defeating +defeatism +defeatist +defeats +defecate +defecation +defect +defected +defecting +defection +defections +defective +defector +defectors +defects +defence +defences +defend +defendant +defendants +defended +defender +defenders +defending +defends +defense +defenseless +defenseman +defenses +defensible +defensive +defensively +defensiveness +defensor +defer +deference +deferens +deferential +deferment +deferments +deferral +deferrals +deferred +deferring +defers +defiance +defiant +defiantly +defibrillator +defibrillators +deficiencies +deficiency +deficient +deficit +deficits +defied +defies +defile +defiled +defilement +defiles +defiling +definable +definately +define +defined +defines +defining +definite +definitely +definition +definitions +definitive +definitively +deflate +deflated +deflates +deflating +deflation +deflationary +deflator +deflect +deflected +deflecting +deflection +deflector +deflects +defoliant +deforest +deforestation +deforested +deform +deformation +deformed +deformities +deformity +defraud +defrauded +defrauding +defray +defraying +defrost +defrosting +deft +deftly +deftness +defunct +defuse +defused +defuses +defusing +defy +defying +deg +degas +degeneracy +degenerate +degenerated +degenerates +degenerating +degeneration +degenerative +degnan +degradable +degradation +degradations +degrade +degraded +degrades +degrading +degreasing +degree +degrees +degress +deguerin +dehaven +dehumanization +dehumanize +dehumanized +dehumanizing +dehydrate +dehydrated +dehydration +dehydrator +dei +deidre +deification +deified +deify +deign +deinstitutionalization +deion +deirdre +deism +deist +deitch +deities +deity +deja +dejected +dekalb +del +delahunt +delaine +deland +delaney +delano +delany +delaware +delay +delayed +delaying +delays +delbert +delco +delectable +delegate +delegated +delegates +delegating +delegation +delegations +deleon +delete +deleted +deleterious +deletes +deleting +deletion +deletions +delft +delgado +delhi +deli +delia +deliberate +deliberated +deliberately +deliberateness +deliberates +deliberating +deliberation +deliberations +deliberative +delicacies +delicacy +delicate +delicately +delicatessen +delicatessens +delicious +deliciously +delight +delighted +delightful +delightfully +delighting +delights +delilah +delima +delimited +deline +delineate +delineated +delineates +delineating +delineation +delinquencies +delinquency +delinquent +delinquents +delirious +delirium +delist +delisted +delisting +deliver +deliverable +deliverables +deliverance +delivered +deliverer +deliverers +deliveries +delivering +delivers +delivery +dell +della +delle +dellinger +delmar +delmarva +delmas +deloitte +delong +delorean +delores +delorme +delors +delph +delphi +delphic +delphine +delray +dels +delta +deltas +deluca +delude +deluded +deluding +deluge +deluged +delusion +delusional +delusions +deluxe +delve +delvecchio +delved +delves +delving +dem +demagogic +demagogue +demagoguery +demagogues +demagogy +demain +demand +demanded +demanding +demands +demarcating +demarcation +demarcations +demarche +demarco +demark +deme +demean +demeaned +demeaning +demeanor +demeans +dement +demented +dementia +dementias +demerits +demerol +demeter +demetrius +demi +demilitarization +demilitarize +demilitarized +demille +deming +demirel +demise +demjanjuk +demo +demobilization +demobilize +demobilized +democracies +democracy +democrat +democratic +democratically +democratization +democratize +democratized +democratizing +democrats +demographer +demographers +demographic +demographically +demographics +demography +demoiselles +demolish +demolished +demolishes +demolishing +demolition +demolitions +demon +demonic +demonization +demonize +demonized +demonizing +demons +demonstrable +demonstrably +demonstrate +demonstrated +demonstrates +demonstrating +demonstration +demonstrations +demonstrative +demonstrator +demonstrators +demoralization +demoralize +demoralized +demoralizing +demos +demoss +demote +demoted +demotic +demotion +demotions +dempsey +dempster +dems +demur +demure +demurely +demurred +demurs +demuth +demystify +den +dena +denali +denard +denarius +denationalization +denatured +denby +dendrite +dendrites +dendritic +dene +denes +deng +dengue +deniability +denial +denials +denied +denier +denies +denigrate +denigrated +denigrating +denigration +denim +denims +deniro +denis +denise +denison +deniz +denizen +denizens +denman +denmark +dennie +denning +dennis +dennison +denny +denominate +denominated +denomination +denominational +denominations +denominator +denominators +denote +denoted +denotes +denouement +denounce +denounced +denounces +denouncing +dens +dense +densely +denser +densest +densities +density +dent +dental +dented +denting +dentist +dentistry +dentists +dentition +denton +dents +denture +dentures +denuded +denunciation +denunciations +denver +deny +denying +denys +denzel +deodorant +deodorants +deon +deoxyribonucleic +dep +depardieu +depart +departed +departing +department +departmental +departments +departs +departure +departures +depaul +depeche +depend +dependability +dependable +dependably +dependant +depended +dependence +dependencies +dependency +dependent +dependents +depending +depends +depersonalize +depict +depicted +depicting +depiction +depictions +depicts +depilatory +deplete +depleted +depletes +depleting +depletion +depletions +deplorable +deplore +deplored +deplores +deploring +deploy +deployable +deployed +deploying +deployment +deployments +deploys +depo +depopulated +depopulation +deport +deportation +deportations +deported +deportee +deportees +deporting +deportment +depose +deposed +deposing +deposit +depositary +deposited +depositing +deposition +depositions +depositor +depositories +depositors +depository +deposits +depot +depots +depp +depraved +depravity +deprecate +deprecated +deprecating +depreciable +depreciate +depreciated +depreciates +depreciating +depreciation +depredation +depredations +depress +depressant +depressants +depressed +depresses +depressing +depressingly +depression +depressions +depressive +deprivation +deprivations +deprive +deprived +deprives +depriving +dept +depth +depths +deputation +deputed +deputies +deputize +deputized +deputy +der +derail +derailed +derailing +derailment +derails +deranged +derangement +derbies +derby +derbyshire +dere +deregister +deregulate +deregulated +deregulating +deregulation +deregulatory +derek +derelict +dereliction +derelicts +deride +derided +derides +deriding +derision +derisive +derisively +derisory +derivable +derivation +derivations +derivative +derivatives +derive +derived +derives +deriving +dermal +dermatitis +dermatological +dermatologist +dermatologists +dermatology +dermis +dern +dernier +derogation +derogatory +derr +derrick +derricks +derriere +derry +dershowitz +dervish +des +desai +desalination +desc +descartes +descend +descendant +descendants +descended +descendent +descendents +descending +descends +descent +descents +deschamps +describable +describe +described +describes +describing +description +descriptions +descriptive +descriptor +descriptors +desecrate +desecrated +desecration +desegregate +desegregated +desegregation +deselect +desensitization +desensitize +desensitized +desensitizing +deseret +desert +deserted +deserter +deserters +deserting +desertion +desertions +deserts +deserve +deserved +deservedly +deserves +deserving +desi +desiccation +desiderata +design +designate +designated +designates +designating +designation +designations +designed +designee +designees +designer +designers +designing +designs +desirability +desirable +desire +desireable +desired +desiree +desires +desiring +desirous +desist +desjardins +desk +deskjet +deskpro +desks +desktop +desktops +desmond +desolate +desolation +desoto +despair +despaired +despairing +despairs +desparate +desparately +despatch +desperado +desperados +desperate +desperately +desperation +despicable +despise +despised +despises +despising +despite +despondency +despondent +despot +despotic +despotism +despots +desrosiers +dessert +desserts +dest +destabilization +destabilize +destabilized +destabilizing +destin +destination +destinations +destined +destinies +destiny +destitute +destitution +destroy +destroyed +destroyer +destroyers +destroying +destroys +destruct +destructed +destruction +destructions +destructive +destructiveness +desultory +det +detach +detachable +detached +detaching +detachment +detachments +detail +detailed +detailing +details +detain +detained +detainee +detainees +detaining +detains +detect +detectable +detected +detecting +detection +detections +detective +detectives +detector +detectors +detects +detente +detention +deter +detergent +detergents +deteriorate +deteriorated +deteriorates +deteriorating +deterioration +determinable +determinant +determinants +determinate +determination +determinations +determinative +determine +determined +determinedly +determines +determining +determinism +determinist +deterministic +deterred +deterrence +deterrent +deterrents +deterring +deters +detest +detestable +detested +dethrone +dethroned +detonate +detonated +detonating +detonation +detonations +detonator +detonators +detour +detoured +detours +detox +detoxification +detoxify +detract +detracted +detracting +detractor +detractors +detracts +detriment +detrimental +detriments +detritus +detroit +deuce +deuces +deukmejian +deus +deutch +deuterium +deuteronomy +deutsch +deutsche +deutscher +deutschland +deux +dev +deva +deval +devaluation +devaluations +devalue +devalued +devalues +devaluing +devaney +devastate +devastated +devastates +devastating +devastatingly +devastation +develop +developable +develope +developed +developement +developer +developers +developing +development +developmental +developmentally +developments +develops +devereaux +devereux +devi +deviance +deviancy +deviant +deviants +deviate +deviated +deviates +deviating +deviation +deviations +device +devices +devil +devilbiss +devilish +devilishly +deville +devils +devin +devine +devious +deviously +deviousness +devise +devised +devises +devising +devito +devlin +devoe +devoid +devolution +devolve +devolved +devolving +devon +devonian +devonshire +devore +devos +devote +devoted +devotee +devotees +devotes +devoting +devotion +devotional +devotions +devoto +devour +devoured +devouring +devours +devout +devoutly +devries +dew +dewan +dewar +dewatering +dewey +dewing +dewitt +dewy +dex +dexter +dexterity +dexterous +dextrose +dextrous +dey +deyoung +dfw +dhabi +dhahran +dhaka +dharma +di +dia +diab +diabetes +diabetic +diabetics +diablo +diabolical +diaconate +diacritical +diadem +diagnose +diagnosed +diagnoses +diagnosing +diagnosis +diagnostic +diagnostics +diagonal +diagonally +diagonals +diagram +diagrammatic +diagrammed +diagramming +diagrams +dial +dialect +dialectic +dialectical +dialects +dialed +dialing +dialling +dialog +dialogue +dialogues +dials +dialtone +dialup +dialysis +diamant +diamante +diameter +diameters +diametrically +diamond +diamonds +dian +diana +diane +dianetics +dianne +diaper +diapers +diaphragm +diaphragms +diaries +diario +diarrhea +diarrhoea +diary +dias +diaspora +diastolic +diatomaceous +diatoms +diatonic +diatribe +diatribes +diaz +dibble +dibs +dic +dice +diced +dicey +dich +dichotomy +dichter +dick +dickens +dickensian +dickenson +dicker +dickerson +dickey +dickie +dickinson +dicks +dickson +dicta +dictaphone +dictate +dictated +dictates +dictating +dictation +dictator +dictatorial +dictators +dictatorship +dictatorships +diction +dictionaries +dictionary +dictum +did +didactic +diddley +diddy +didi +didier +didion +didnt +dido +die +died +diederich +diedrich +diego +diehard +diehards +diehl +diem +diener +dier +dies +diesel +diesels +diet +dietary +dieted +dieter +dieters +dietetic +dietician +dieting +dietitian +dietitians +dietmar +dietrich +diets +diety +dietz +diez +dif +diff +differ +differed +difference +differences +different +differential +differentially +differentials +differentiate +differentiated +differentiates +differentiating +differentiation +differentiator +differently +differing +differs +difficile +difficult +difficulties +difficultly +difficulty +diffidence +diffraction +diffuse +diffused +diffuses +diffusing +diffusion +dig +digby +digest +digested +digester +digestible +digesting +digestion +digestive +digests +digger +diggers +digging +diggins +diggs +digit +digital +digitalis +digitally +digitals +digitised +digitization +digitize +digitized +digitizing +digits +dignified +dignify +dignitaries +dignitary +dignity +digress +digressed +digression +digressions +digs +dijon +dike +dikes +dil +dilapidated +dilatation +dilate +dilated +dilating +dilation +dilatory +dildo +dildos +dilemma +dilemmas +dilettante +diligence +diligent +diligently +dill +dillard +diller +dillon +dills +dilly +dilute +diluted +dilutes +diluting +dilution +dim +dimaggio +dimas +dime +dimension +dimensional +dimensionality +dimensioned +dimensioning +dimensions +dimes +dimethyl +diminish +diminished +diminishes +diminishing +diminishment +diminution +diminutive +dimitri +dimitris +dimly +dimmed +dimmer +dimmers +dimming +dimness +dimorphism +dimple +dimpled +dimples +dims +dimwits +din +dina +dinah +dinar +dinars +dine +dined +dineen +diner +dinero +diners +dines +dinesh +dinette +ding +dinged +dingell +dinger +dinghy +dingle +dingman +dingo +dings +dingwall +dingy +dinh +dining +dink +dinkins +dinks +dinky +dinner +dinners +dinnertime +dinnerware +dinning +dino +dinos +dinosaur +dinosaurs +dinsmore +dint +diocesan +diocese +dioceses +diode +diodes +dion +dionisio +dionne +dior +dios +dioxide +dioxides +dioxin +dioxins +dip +diphenhydramine +diphtheria +diploma +diplomacy +diplomas +diplomat +diplomatic +diplomatically +diplomats +dipole +dipped +dipper +dippers +dipping +dippy +dips +dipstick +dipsy +dir +dirac +dire +direct +directed +directeur +directing +direction +directional +directionality +directionally +directionless +directions +directive +directives +directly +directness +director +directorate +directorates +directorial +directories +directors +directorship +directorships +directory +directs +directv +direly +direst +dirge +dirk +dirks +dirksen +dirt +dirtier +dirtiest +dirty +dis +disabilities +disability +disable +disabled +disables +disabling +disabuse +disabused +disadvantage +disadvantaged +disadvantageous +disadvantages +disadvantaging +disaffected +disaffection +disaggregate +disaggregated +disaggregation +disagree +disagreeable +disagreed +disagreeing +disagreement +disagreements +disagrees +disallow +disallowance +disallowed +disallowing +disallows +disappear +disappearance +disappearances +disappeared +disappearing +disappears +disappoint +disappointed +disappointing +disappointingly +disappointment +disappointments +disappoints +disapproval +disapprove +disapproved +disapproves +disapproving +disarm +disarmament +disarmed +disarming +disarmingly +disarray +disassemble +disassembled +disassembling +disassembly +disassociate +disassociated +disassociating +disaster +disasterous +disasters +disastrous +disastrously +disavow +disavowal +disavowed +disavowing +disavows +disband +disbanded +disbanding +disbandment +disbarment +disbarred +disbelief +disbelieve +disbelieving +disburse +disbursed +disbursement +disbursements +disbursing +disc +discard +discarded +discarding +discards +discern +discernable +discerned +discernible +discerning +discernment +disch +discharge +discharged +discharger +discharges +discharging +disciple +disciples +discipleship +disciplinarian +disciplinarians +disciplinary +discipline +disciplined +disciplines +discipling +disciplining +disclaim +disclaimed +disclaimer +disclaimers +disclaiming +disclaims +disclose +disclosed +discloses +disclosing +disclosure +disclosures +disco +discography +discoloration +discolorations +discomfiture +discomfort +discomforts +disconcert +disconcerting +disconnect +disconnected +disconnecting +disconnection +disconnections +disconnects +discontent +discontented +discontents +discontinuance +discontinuation +discontinue +discontinued +discontinues +discontinuing +discontinuities +discontinuity +discord +discordant +discos +discotheque +discount +discounted +discounter +discounters +discounting +discounts +discourage +discouraged +discouragement +discourages +discouraging +discourse +discourses +discover +discoverable +discovered +discoverer +discoverers +discoveries +discovering +discovers +discovery +discredit +discredited +discrediting +discredits +discreet +discreetly +discrepancies +discrepancy +discrete +discretely +discretion +discretionary +discriminate +discriminated +discriminates +discriminating +discrimination +discriminations +discriminative +discriminatory +discs +discursive +discus +discuss +discussants +discussed +discusses +discussing +discussion +discussions +disdain +disdained +disdainful +disdaining +disdains +disease +diseased +diseases +disembark +disembodied +disenchanted +disenchantment +disenfranchise +disenfranchised +disenfranchisement +disenfranchising +disengage +disengaged +disengagement +disengaging +disentangle +disentangled +disentanglement +disentangling +disequilibrium +disfavor +disfigure +disfigured +disfigurement +disfiguring +disgorge +disgorged +disgorgement +disgorging +disgrace +disgraced +disgraceful +disgruntled +disgruntlement +disguise +disguised +disguises +disguising +disgust +disgusted +disgusting +disgustingly +disgusts +dish +disharmony +dishearten +disheartened +disheartening +dished +dishes +dishing +dishonest +dishonestly +dishonesty +dishonor +dishonorable +dishwasher +dishwashers +dishwashing +dishwater +disillusion +disillusioned +disillusioning +disillusionment +disincentive +disincentives +disinclination +disinclined +disinfect +disinfectant +disinfectants +disinfecting +disinfection +disinflation +disinformation +disingenuous +disingenuously +disingenuousness +disintegrate +disintegrated +disintegrates +disintegrating +disintegration +disinterest +disinterested +disintermediation +disinvestment +disjointed +disjunction +disk +diskette +diskettes +diskin +diskless +disks +dislike +disliked +dislikes +disliking +dislocate +dislocated +dislocating +dislocation +dislocations +dislodge +dislodged +dislodging +disloyal +disloyalty +dismal +dismally +dismantle +dismantled +dismantlement +dismantles +dismantling +dismay +dismayed +dismaying +dismays +dismember +dismembered +dismembering +dismemberment +dismiss +dismissal +dismissals +dismissed +dismisses +dismissing +dismissive +dismount +dismutase +disney +disneyland +disneyworld +disobedience +disobedient +disobey +disobeyed +disobeying +disobeys +disorder +disordered +disorderly +disorders +disorganization +disorganized +disorient +disorientation +disoriented +disorienting +disown +disowned +disowning +disparage +disparaged +disparagement +disparages +disparaging +disparagingly +disparate +disparities +disparity +dispassionate +dispassionately +dispatch +dispatched +dispatcher +dispatchers +dispatches +dispatching +dispel +dispell +dispelled +dispelling +dispels +dispensable +dispensary +dispensation +dispense +dispensed +dispenser +dispensers +dispenses +dispensing +dispersal +disperse +dispersed +disperses +dispersing +dispersion +dispersions +dispersive +dispirited +dispiriting +displace +displaced +displacement +displacements +displaces +displacing +display +displayed +displaying +displays +displease +displeased +displeasing +displeasure +disposable +disposables +disposal +disposals +dispose +disposed +disposer +disposes +disposing +disposition +dispositions +dispositive +dispossessed +dispossession +disproof +disproportionate +disproportionately +disprove +disproved +disproven +disproves +disproving +disputation +dispute +disputed +disputes +disputing +disqualification +disqualified +disqualifies +disqualify +disqualifying +disquiet +disquieting +disquisition +disquisitions +disraeli +disregard +disregarded +disregarding +disregards +disrepair +disreputable +disrepute +disrespect +disrespected +disrespectful +disrespectfully +disrupt +disrupted +disrupting +disruption +disruptions +disruptive +disrupts +diss +dissatisfaction +dissatisfied +dissect +dissected +dissecting +dissection +dissections +dissects +dissemble +dissembling +disseminate +disseminated +disseminating +dissemination +dissension +dissent +dissented +dissenter +dissenters +dissenting +dissents +dissertation +disservice +dissidence +dissident +dissidents +dissimilar +dissimilarity +dissing +dissipate +dissipated +dissipates +dissipating +dissipation +dissociate +dissociation +dissolution +dissolve +dissolved +dissolves +dissolving +dissonance +dissonant +dissuade +dissuaded +dissuading +dist +distal +distance +distanced +distances +distancing +distant +distantly +distaste +distasteful +distemper +distend +distended +distill +distillate +distillates +distillation +distilled +distiller +distilleries +distillers +distillery +distilling +distills +distinct +distinction +distinctions +distinctive +distinctively +distinctiveness +distinctly +distinguish +distinguishable +distinguished +distinguishes +distinguishing +distort +distorted +distorting +distortion +distortions +distorts +distract +distracted +distracting +distraction +distractions +distracts +distraught +distress +distressed +distresses +distressing +distressingly +distributable +distribute +distributed +distributes +distributing +distribution +distributional +distributions +distributive +distributor +distributors +distributorship +district +districts +distrust +distrusted +distrustful +distrusting +distrusts +disturb +disturbance +disturbances +disturbed +disturbing +disturbingly +disturbs +disunion +disunity +disuse +dit +ditch +ditched +ditches +ditching +dither +dithered +dithering +dithers +ditka +ditties +dittman +ditto +ditty +diuretic +diuretics +diurnal +div +diva +divan +divas +dive +dived +diver +diverge +diverged +divergence +divergences +divergent +diverges +diverging +divers +diverse +diversification +diversified +diversifies +diversify +diversifying +diversion +diversionary +diversions +diversities +diversity +divert +diverted +diverting +diverts +dives +divest +divested +divesting +divestiture +divestment +divestments +divests +divi +divide +divided +dividend +dividends +divider +dividers +divides +dividing +divination +divine +divined +divinely +divines +diving +divining +divinities +divinity +divisible +division +divisional +divisions +divisive +divisiveness +divisor +divorce +divorced +divorcee +divorces +divorcing +divot +divulge +divulged +divulging +divvied +divvy +diwan +dix +dixie +dixieland +dixit +dixon +dizziness +dizzy +dizzying +dizzyingly +djibouti +djinn +dk +dmitri +do +doa +doable +doan +dob +dobbin +dobbins +dobbs +doberman +dobermans +dobie +dobkin +dobler +dobra +dobson +doby +doc +docent +docile +docility +dock +docked +docker +dockers +dockery +docket +dockets +docking +docklands +docks +dockside +dockworkers +dockyard +docs +doctor +doctoral +doctorate +doctorates +doctored +doctoring +doctors +doctrinaire +doctrinal +doctrine +doctrines +docudrama +document +documenta +documentaries +documentary +documentation +documented +documenting +documents +dod +dodd +doddering +dodds +dodge +dodged +dodger +dodgers +dodges +dodging +dodgy +dodo +dodson +doe +doer +doers +does +doesn +doesnt +doff +doffed +doffing +dog +dogan +dogberry +doge +dogfight +dogged +doggedly +doggedness +doggerel +doggett +doggie +doggies +dogging +doggone +doggy +doghouse +dogma +dogmas +dogmatic +dogmatically +dogmatism +dogs +dogwood +dogwoods +doh +dohc +doherty +doi +doilies +doily +doing +doings +doke +dol +dolan +dolby +dolce +doldrums +dole +doled +doleful +doles +dolezal +doling +dolittle +doll +dollar +dollars +dolled +dolley +dollop +dollops +dolls +dolly +dolman +dolomite +dolomites +dolores +dolph +dolphin +dolphins +dolt +dom +domain +domaine +domains +dome +domecq +domed +domes +domestic +domestically +domesticate +domesticated +domesticating +domestication +domesticity +domestics +domi +domicile +domiciles +domina +dominance +dominant +dominantly +dominate +dominated +dominates +dominating +domination +dominator +domine +domineering +domingo +domingos +dominguez +domini +dominic +dominica +dominican +dominicans +dominick +dominik +dominion +dominions +dominique +domino +dominoes +dominos +dominus +domke +don +dona +donahue +donald +donaldson +donat +donate +donated +donates +donating +donation +donations +donato +donde +done +doner +doney +dong +dongen +donis +donkey +donkeys +donlan +donna +donnas +donne +donned +donnell +donnelley +donnelly +donner +donnie +donning +donny +donnybrook +donoghue +donohoe +donohue +donor +donors +donovan +dons +dont +donut +donuts +doo +doodad +doodads +doodle +doodles +doody +doogie +dooley +doolittle +doom +doomed +dooming +dooms +doomsayers +doomsday +doonesbury +door +doorbell +doorframe +doorknob +doorknobs +doorman +doormat +doormats +doors +doorstep +doorsteps +doorstop +doorway +doorways +doozy +dopa +dopamine +dope +doped +dopes +dopey +doping +doppelganger +doppler +dopson +dor +dora +dorado +doral +doran +dorchester +dore +doreen +dorey +dorf +dorfman +dorgan +dori +doria +dorian +doric +dorin +doris +doritos +dork +dorland +dorm +dorman +dormancy +dormant +dormer +dormitories +dormitory +dorms +dorn +dornan +dornbusch +doron +dorothea +dorothy +dorr +dorsal +dorset +dorsett +dorsey +dort +dortmund +dory +dos +dosage +dosages +dose +doses +dosh +dosing +doss +dossier +dossiers +dost +dostoevsky +dot +dote +dotes +doth +doting +dots +dotted +dottie +dotting +dotty +doty +double +doubled +doubleday +doubleheader +doubles +doublespeak +doubletree +doubling +doubly +doubt +doubted +doubter +doubters +doubtful +doubting +doubtless +doubts +doucet +doucette +douche +doug +dough +doughboy +dougherty +doughnut +doughnuts +doughty +doughy +douglas +douglass +doulton +dour +douse +doused +douses +dousing +dov +dove +dover +doves +dovetail +dovetailed +dovetailing +dovetails +dovey +dovish +dow +dowager +dowd +dowden +dowdy +dowel +dowell +dowels +dower +dowling +down +downbeat +downcast +downdraft +downed +downer +downers +downes +downey +downfall +downgrade +downgraded +downgrades +downgrading +downhearted +downhill +downhills +downie +downing +download +downloadable +downloaded +downloading +downloads +downpayment +downplay +downplayed +downplaying +downplays +downpour +downpours +downright +downriver +downs +downscale +downside +downsides +downsize +downsized +downsizing +downstairs +downstate +downstream +downswing +downtime +downtown +downtowns +downtrend +downtrodden +downturn +downturns +downward +downwardly +downwards +downwind +downy +dowry +dows +dowse +doxycycline +doyen +doyenne +doyle +doze +dozed +dozen +dozens +dozes +dozier +dozing +dp +dr +drab +drabs +drachma +draco +draconian +dracula +draeger +draft +drafted +draftees +drafter +drafters +drafting +drafts +draftsman +draftsmanship +draftsmen +drafty +drag +dragged +dragging +draggy +dragnet +drago +dragon +dragonfly +dragons +drags +drain +drainage +drained +draining +drains +drake +drakes +dram +drama +dramas +dramatic +dramatically +dramatics +dramatist +dramatization +dramatizations +dramatize +dramatized +dramatizes +dramatizing +drams +drang +drank +drape +drapeau +draped +draper +draperies +drapery +drapes +draping +drastic +drastically +drat +draught +draughts +draw +drawback +drawbacks +drawbridge +drawdown +drawdowns +drawer +drawers +drawing +drawings +drawl +drawled +drawls +drawn +draws +drawstring +dray +drayton +dread +dreaded +dreadful +dreadfully +dreading +dreads +dream +dreamed +dreamer +dreamers +dreaming +dreamland +dreamlike +dreams +dreamt +dreamtime +dreamworld +dreamy +dreariness +dreary +dreck +dredge +dredged +dredges +dredging +dregs +dreier +drench +drenched +drenching +drennan +drescher +dresden +dresdner +dress +dressage +dressed +dresser +dressers +dresses +dressier +dressing +dressings +dressler +dressmaker +dressmakers +dressy +drew +drews +drexel +drexler +dreyer +dreyfus +dreyfuss +dribble +dribbled +dribbler +dribbles +dribbling +dribs +dried +drier +dries +driest +drift +drifted +drifter +drifters +drifting +drifts +driftwood +drill +drilled +driller +drillers +drilling +drills +drink +drinkable +drinker +drinkers +drinking +drinks +drinkwater +drip +dripped +dripping +drippy +drips +driscoll +drivable +drive +drivel +driveline +driven +driver +drivers +drives +drivetrain +driveway +driveways +driving +drizzle +drizzling +drizzly +drogue +droit +droll +dromedary +drone +droned +drones +droning +drool +drooled +drooling +drools +droop +drooped +drooping +droopy +drop +droplet +droplets +dropout +dropouts +dropped +dropper +droppers +dropping +droppings +drops +dropsy +dross +drought +droughts +drove +drover +droves +drown +drowned +drowning +drownings +drowns +drowsiness +drowsy +dru +drub +drubbed +drubbing +drucker +drudge +drudgery +drug +drugged +drugging +druggists +drugs +drugstore +drugstores +druid +druids +drum +drumbeat +drummed +drummer +drummers +drumming +drummond +drumroll +drums +drumstick +drunk +drunkard +drunkards +drunken +drunkenness +drunks +drury +druse +druze +dry +dryden +dryer +dryers +drying +dryly +dryness +drysdale +drywall +dss +dt +du +dual +dualism +dualistic +dualities +duality +dually +duan +duane +duarte +dub +dubai +dubbed +dubbing +dube +dubey +dubin +dubious +dublin +dubois +dubrovnik +dubs +duc +ducal +duce +ducharme +duchenne +duchess +duchy +duck +ducked +ducker +duckett +ducking +duckling +ducklings +ducks +duckworth +ducky +duct +ductile +ductility +ducting +ducts +dud +duddy +dude +dudek +dudes +dudgeon +dudley +duds +due +duel +dueled +dueling +duels +dues +duesseldorf +duet +duets +duff +duffel +duffer +duffers +duffey +duffield +duffle +duffy +dufour +dug +duggan +dugout +dugouts +duh +dui +duiker +duisburg +dukakis +duke +dukes +dulce +dulcet +dulcimer +dull +dulled +duller +dulles +dullest +dulling +dullness +dulls +duluth +duly +dum +duma +dumaine +dumais +dumas +dumb +dumbbell +dumbbells +dumber +dumbest +dumbfounded +dumbing +dumbo +dumbstruck +dummies +dummy +dumont +dump +dumped +dumper +dumpers +dumping +dumpling +dumplings +dumps +dumpster +dumpsters +dumpty +dumpy +dun +dunaway +dunbar +duncan +dunce +dundas +dundee +dune +dunedin +dunes +dunford +dung +dungeon +dungeons +dungy +dunham +dunk +dunked +dunkel +dunker +dunkin +dunking +dunkirk +dunkle +dunks +dunlap +dunleavy +dunlop +dunn +dunne +dunnett +dunning +dunno +duns +dunst +dunston +dunwoody +duo +duodenal +duodenum +duomo +duopoly +duos +dup +dupe +duped +duper +dupes +duping +duplex +duplexes +duplicate +duplicated +duplicates +duplicating +duplication +duplications +duplicitous +duplicity +dupont +dupree +dupuis +dupuy +duque +duquesne +dur +dura +durability +durable +durables +duracell +duran +durango +durant +durante +duration +durations +durban +durbin +dure +duress +durham +durian +durie +during +durkin +durn +durning +duro +durr +durst +durum +dusk +dusky +dusseldorf +dust +dustbin +dusted +duster +dusters +dustin +dusting +dustman +dusts +dustup +dusty +dutch +dutchess +dutchman +duties +dutiful +dutifully +dutko +dutra +dutt +dutton +duty +duva +duval +duvalier +duvall +duvet +dux +dvorak +dwarf +dwarfed +dwarfing +dwarfs +dwarves +dwayne +dwell +dwelled +dweller +dwellers +dwelling +dwellings +dwells +dwelt +dwight +dwindle +dwindled +dwindles +dwindling +dworkin +dwyer +dy +dyck +dye +dyed +dyeing +dyer +dyers +dyes +dying +dyke +dykes +dylan +dynamic +dynamical +dynamically +dynamics +dynamism +dynamite +dynamo +dynastic +dynasties +dynasty +dysentery +dysfunction +dysfunctional +dysfunctions +dyslexia +dyslexic +dyslexics +dyson +dyspepsia +dyspeptic +dysphoria +dysplasia +dysplastic +dystrophy +ea +each +eachother +eads +eagan +eager +eagerly +eagerness +eagle +eagleburger +eagles +eagleson +eaker +eakin +eakins +eales +ealing +ealy +eames +eamon +ear +earache +eardrum +eardrums +eared +earful +earhart +earl +earle +earley +earlier +earliest +earlobe +earls +early +earmark +earmarked +earmarking +earmarks +earmuffs +earn +earned +earner +earners +earnest +earnestly +earnestness +earning +earnings +earns +earp +earphone +earphones +earpiece +earpieces +earplugs +earring +earrings +ears +earshot +earth +eartha +earthbound +earthen +earthenware +earthling +earthlings +earthly +earthquake +earthquakes +earths +earthwork +earthworm +earthworms +earthy +earwax +ease +eased +easel +easement +easements +eases +easier +easiest +easily +easing +easley +eason +east +eastbound +easter +easterbrook +easterling +easterly +eastern +easterners +eastham +eastland +eastman +easton +eastward +eastwards +eastwick +eastwood +easy +easygoing +eat +eaten +eater +eateries +eaters +eatery +eating +eaton +eats +eau +eaux +eave +eaves +eavesdrop +eavesdroppers +eavesdropping +eb +ebb +ebbed +ebbers +ebbing +ebbs +ebel +eben +ebenezer +eber +eberhard +eberhardt +eberle +eberstadt +ebert +ebner +ebony +ebullience +ebullient +eccentric +eccentricities +eccentricity +eccentrics +eccles +ecclesia +ecclesiastes +ecclesiastic +ecclesiastical +eccleston +ecevit +echelon +echelons +echo +echoed +echoes +echoing +echos +eck +eckard +ecker +eckersley +eckert +eckhard +eckhart +eckman +eckstein +eclectic +eclecticism +eclipse +eclipsed +eclipses +eclipsing +eco +ecole +ecologic +ecological +ecologically +ecologist +ecologists +ecology +econ +econometric +econometrics +economic +economical +economically +economics +economies +economist +economists +economize +economizing +economy +ecosystem +ecosystems +ecru +ecstasy +ecstatic +ecstatically +ecuador +ecuadorean +ecuadorian +ecumenical +eczema +ed +edam +eddie +eddies +eddington +eddy +edel +edelman +edelstein +edelweiss +edema +eden +edens +eder +edgar +edge +edged +edger +edgerton +edges +edgewise +edgeworth +edginess +edging +edgy +edi +edibility +edible +edibles +edict +edicts +edie +edification +edifice +edifices +edify +edifying +edina +edinboro +edinburgh +edison +edit +editable +edited +edith +editing +edition +editions +editor +editorial +editorialist +editorialists +editorialize +editorialized +editorializing +editorially +editorials +editors +editorship +edits +edlund +edman +edmond +edmonds +edmondson +edmonson +edmonton +edmund +edmunds +edna +edo +edouard +eds +edsel +edson +eduard +eduardo +educ +educate +educated +educates +educating +education +educational +educationally +educations +educator +educators +eduction +edutainment +edward +edwardian +edwards +edwin +edwina +ee +eeg +eel +eelam +eels +een +eerie +eerily +ef +eff +efface +effacing +effect +effected +effecting +effective +effectively +effectiveness +effects +effectuate +effectuating +effeminate +effervescent +effete +efficacious +efficacy +efficiencies +efficiency +efficient +efficiently +effigy +effluent +effluents +effort +effortless +effortlessly +efforts +effrontery +effusions +effusive +effusively +efron +eg +egad +egal +egalitarian +egalitarianism +egan +eger +egerton +egg +egged +eggers +eggert +egghead +eggleston +eggplant +eggplants +eggs +eggshell +eggshells +ego +egocentric +egoism +egoistic +egomaniac +egomaniacal +egon +egos +egotism +egotist +egotistical +egregious +egregiously +egress +egret +egypt +egyptian +egyptians +egyptology +eh +ehlers +ehrenfeld +ehrenreich +ehrlich +ehrlichman +ehrman +ehud +eichel +eichmann +eid +eide +eiffel +eigen +eight +eighteen +eighteenth +eightfold +eighth +eighths +eighties +eightieth +eights +eighty +eileen +ein +einar +eindhoven +einem +einhorn +einstein +eire +eis +eisa +eisen +eisenach +eisenberg +eisenhower +eisenman +eisenstein +eisler +eisner +eitan +either +ejaculate +ejaculation +eject +ejected +ejecting +ejection +ejections +ejects +ek +ekaterina +eke +eked +ekes +eking +eklund +ekman +el +elaborate +elaborated +elaborately +elaborates +elaborating +elaboration +elaborations +elaine +elam +elan +eland +elapse +elapsed +elapses +elastic +elasticity +elastin +elastomer +elastomers +elated +elation +elayne +elbow +elbowed +elbowing +elbows +eldar +elder +elderly +elders +eldest +eldin +eldon +eldorado +eldred +eldredge +eldridge +eleanor +elec +elect +electability +electable +elected +electic +electing +election +electioneering +elections +elective +electives +elector +electoral +electorate +electorates +electors +electra +electric +electrical +electrically +electricals +electrician +electricians +electricity +electrics +electrification +electrified +electrify +electrifying +electro +electrocardiogram +electrocardiograms +electrochemical +electrocute +electrocuted +electrocution +electrode +electrodes +electrodynamics +electrolux +electrolysis +electrolyte +electrolytes +electrolytic +electromagnet +electromagnetic +electromagnetism +electromechanical +electron +electronic +electronically +electronics +electrons +electrophoresis +electroplating +electroshock +electrostatic +elects +elegance +elegant +elegantly +elegiac +elegies +elegy +elektra +elektro +element +elemental +elementary +elements +elena +elephant +elephantine +elephants +elevate +elevated +elevates +elevating +elevation +elevations +elevator +elevators +eleven +elevens +eleventh +elf +elfin +elfman +elgar +elgin +eli +elia +elias +elicit +elicited +eliciting +elicits +elie +eliezer +eligibility +eligible +elijah +eliminate +eliminated +eliminates +eliminating +elimination +eliminations +eliminator +elin +elinor +eliot +elisa +elisabeth +elise +elisha +elite +elites +elitism +elitist +elitists +elixir +eliza +elizabeth +elizabethan +elizondo +elk +elkhorn +elkington +elkins +elko +elks +ell +ella +elle +elledge +ellen +eller +ellerman +ellery +ellie +elliman +elling +ellingson +ellington +elliot +elliott +ellipse +ellipses +ellipsis +elliptical +ellis +ellison +ellman +ellmann +ells +ellsberg +ellsworth +ellwood +elly +elm +elman +elmer +elmhurst +elmira +elmo +elmore +elms +elmwood +eloise +elongate +elongated +elongation +elope +eloping +eloquence +eloquent +eloquently +elroy +els +elsa +else +elses +elsevier +elsewhere +elsie +elsinore +elson +elton +elucidate +elucidated +elude +eluded +eludes +eluding +elusive +elusiveness +elvers +elves +elvin +elvira +elvis +elway +elwell +elwood +ely +elysee +elysees +elysian +elysium +em +ema +emaciated +email +emailed +emanate +emanated +emanates +emanating +emanation +emanations +emancipate +emancipated +emancipation +emanuel +emasculated +emasculating +embalm +embalmed +embalming +embankment +embankments +embarass +embarassed +embarassing +embarassment +embarcadero +embargo +embargoed +embargoes +embark +embarkation +embarked +embarking +embarks +embarrased +embarrass +embarrassed +embarrasses +embarrassing +embarrassingly +embarrassment +embarrassments +embassies +embassy +embattled +embed +embedded +embedding +embeds +embellish +embellished +embellishing +embellishment +embellishments +ember +embers +embezzle +embezzled +embezzlement +embezzler +embezzling +embitter +embittered +emblazoned +emblem +emblematic +emblems +embodied +embodies +embodiment +embody +embodying +embolden +emboldened +emboli +embolism +emboss +embossed +embrace +embraceable +embraced +embraces +embracing +embroider +embroidered +embroideries +embroidering +embroidery +embroiled +embry +embryo +embryology +embryonic +embryos +emcee +emerald +emeralds +emeraude +emerge +emerged +emergence +emergencies +emergency +emergent +emerges +emerging +emeritus +emerson +emery +emeryville +emetic +emig +emigrant +emigrants +emigrate +emigrated +emigrating +emigration +emigre +emigres +emil +emile +emilia +emilie +emilio +emily +eminence +eminences +eminent +eminently +emir +emirate +emirates +emissaries +emissary +emission +emissions +emit +emits +emitted +emitter +emitters +emitting +emma +emmanuel +emmaus +emmer +emmerich +emmet +emmett +emmitt +emmons +emmy +emory +emotion +emotional +emotionalism +emotionally +emotions +emotive +emp +empathetic +empathic +empathize +empathizes +empathizing +empathy +emperor +emperors +empey +emphases +emphasis +emphasised +emphasize +emphasized +emphasizes +emphasizing +emphatic +emphatically +emphysema +empire +empires +empiric +empirical +empirically +empiricism +empiricist +emplacements +employ +employable +employed +employee +employees +employer +employers +employing +employment +employs +emporia +emporium +empower +empowered +empowering +empowerment +empowers +empresa +empress +emptied +emptier +empties +emptiness +emptive +emptor +empty +emptying +emrick +ems +emu +emulate +emulated +emulates +emulating +emulation +emulator +emulators +emulex +emulsion +en +ena +enable +enabled +enabler +enablers +enables +enabling +enact +enacted +enacting +enactment +enactments +enacts +enamel +enameled +enamels +enamored +enamoured +enberg +encamped +encampment +encampments +encapsulate +encapsulated +encapsulates +encapsulating +encapsulation +encase +encased +encases +encasing +encaustic +encephalitis +encephalopathy +enchant +enchanted +enchanting +enchantment +enchantress +enchilada +enchiladas +encino +encircle +encircled +encirclement +encircling +enclave +enclaves +enclose +enclosed +encloses +enclosing +enclosure +enclosures +encode +encoded +encoder +encodes +encoding +encompass +encompassed +encompasses +encompassing +encore +encores +encounter +encountered +encountering +encounters +encourage +encouraged +encouragement +encourages +encouraging +encouragingly +encroach +encroached +encroaches +encroaching +encroachment +encroachments +encrusted +encrypt +encrypted +encrypting +encryption +encrypts +encumber +encumbered +encumbrance +encyclical +encyclopaedia +encyclopedia +encyclopedias +encyclopedic +end +endanger +endangered +endangering +endangerment +endangers +ende +endear +endeared +endearing +endearment +endeavor +endeavored +endeavors +endeavour +ended +endemic +ender +enders +endgame +endicott +ending +endings +endive +endless +endlessly +endnotes +endo +endocrine +endocrinologist +endocrinologists +endocrinology +endogenous +endometrial +endometriosis +endorphin +endorse +endorsed +endorsement +endorsements +endorser +endorsers +endorses +endorsing +endoscopic +endoscopy +endow +endowed +endowing +endowment +endowments +endows +endpoint +endpoints +ends +endurance +endure +endured +endures +enduring +endymion +enema +enemas +enemies +enemy +energetic +energetically +energies +energize +energized +energizer +energizes +energizing +energy +enfant +enfeebled +enfield +enforce +enforceability +enforceable +enforced +enforcement +enforcer +enforcers +enforces +enforcing +enfranchised +eng +engage +engaged +engagement +engagements +engages +engaging +engdahl +engel +engelberg +engelbert +engelhardt +engelmann +engels +engen +engender +engendered +engendering +engenders +engine +engineer +engineered +engineering +engineers +engines +engl +england +englander +englanders +engle +engler +englewood +english +englishman +englishmen +englishwoman +engrave +engraved +engraver +engraving +engravings +engrossed +engrossing +engulf +engulfed +engulfing +enhance +enhanced +enhancement +enhancements +enhancer +enhancers +enhances +enhancing +eniac +enid +enigma +enigmas +enigmatic +enjoin +enjoined +enjoining +enjoy +enjoyable +enjoyed +enjoying +enjoyment +enjoys +enlarge +enlarged +enlargement +enlargements +enlarger +enlarges +enlarging +enlighten +enlightened +enlightening +enlightenment +enlist +enlisted +enlistees +enlisting +enlistment +enlists +enliven +enlivened +enlivens +enloe +enmeshed +enmities +enmity +ennis +ennoble +ennobling +ennui +eno +enoch +enormity +enormous +enormously +enough +enought +enquire +enquirer +enquiries +enquiring +enquiry +enrage +enraged +enrages +enraging +enrapture +enraptured +enrich +enriched +enriches +enriching +enrichment +enrichments +enrico +enright +enrique +enroll +enrolled +enrollee +enrollees +enrolling +enrollment +enrollments +enrolls +enrolment +enron +ens +ensconced +ensemble +ensembles +enshrine +enshrined +enshrinement +ensign +enslave +enslaved +enslavement +ensnare +ensnared +ensor +ensue +ensued +ensues +ensuing +ensure +ensured +ensures +ensuring +ent +entail +entailed +entailing +entails +entangle +entangled +entanglement +entanglements +entangling +ente +entendre +enter +entered +enteric +entering +enteritis +enterprise +enterprises +enterprising +enters +entertain +entertained +entertainer +entertainers +entertaining +entertainingly +entertainment +entertainments +entertains +enthralled +enthuse +enthused +enthusiasm +enthusiasms +enthusiast +enthusiastic +enthusiastically +enthusiasts +entice +enticed +enticement +enticements +entices +enticing +entire +entirely +entirety +entities +entitle +entitled +entitlement +entitlements +entitles +entitling +entity +entombed +entombment +entomologist +entomologists +entomology +entourage +entrails +entrance +entranced +entrances +entranceway +entrancing +entrant +entrants +entrap +entrapment +entrapped +entre +entreaties +entreaty +entree +entrees +entrench +entrenched +entrenching +entrenchment +entrepreneur +entrepreneurial +entrepreneurs +entrepreneurship +entries +entropic +entropy +entrust +entrusted +entrusting +entrusts +entry +entryway +entwine +entwined +enuff +enumerate +enumerated +enumerates +enumerating +enumeration +enunciate +enunciated +enunciating +env +envelop +envelope +enveloped +envelopes +enveloping +envelops +enviable +envied +envious +enviously +enviroment +enviromental +environ +environment +environmental +environmentalism +environmentalist +environmentalists +environmentally +environments +environs +envisage +envisaged +envisages +envisaging +envision +envisioned +envisioning +envisions +envoy +envoys +envy +enzo +enzymatic +enzyme +enzymes +eo +eocene +eon +eons +eos +ep +epaulets +epcot +ephedrine +ephemera +ephemeral +ephesians +ephesus +ephraim +epi +epic +epicenter +epics +epicurean +epidemic +epidemics +epidemiological +epidemiologist +epidemiologists +epidemiology +epidermal +epidermis +epidural +epigram +epigraph +epilepsy +epileptic +epilogue +epiphanies +epiphany +episcopal +episcopalian +episcopalians +episiotomy +episode +episodes +episodic +epistemological +epistemology +epistle +epistles +epistolary +epitaph +epitaphs +epithelial +epithet +epithets +epitome +epitomize +epitomized +epitomizes +epoch +epochal +epochs +eponym +eponymous +epoxy +epp +epps +epsilon +epsom +epson +epstein +eq +equal +equaling +equality +equalization +equalize +equalized +equalizer +equalizing +equally +equals +equanimity +equate +equated +equates +equating +equation +equations +equator +equatorial +equestrian +equestrians +equidistant +equifax +equilibria +equilibrium +equine +equinox +equip +equipment +equipments +equipped +equipping +equips +equitable +equitably +equities +equity +equivalence +equivalency +equivalent +equivalently +equivalents +equivocal +equivocate +equivocating +equivocation +er +era +eradicate +eradicated +eradicates +eradicating +eradication +eral +eras +erase +erased +eraser +erasers +erases +erasing +erasmus +erasure +erb +erd +ere +erect +erected +erecting +erection +erections +erector +erects +erg +ergo +ergonomic +ergonomically +ergonomics +ergot +eric +erica +erich +erick +erickson +ericson +ericsson +erie +erik +erika +erikson +eriksson +erin +eris +eritrea +erk +erl +erland +erlangen +erlanger +erlich +erlichman +erma +erman +ermine +ernest +ernestine +ernesto +ernie +ernst +ero +erode +eroded +erodes +eroding +erogenous +eroica +eros +erosion +erosive +erotic +erotica +eroticism +err +errand +errands +errant +errata +erratic +erratically +erred +erring +errol +erroneous +erroneously +error +errors +errs +ers +ersatz +erskine +erstwhile +erudite +erudition +erupt +erupted +erupting +eruption +eruptions +eruptive +erupts +ervin +erving +erwin +erythromycin +erythropoietin +es +esau +esc +esca +escalade +escalante +escalate +escalated +escalates +escalating +escalation +escalations +escalator +escalators +escapade +escapades +escape +escaped +escapee +escapees +escapes +escaping +escapism +escapist +escarpment +escarpments +eschatological +eschenbach +escher +eschew +eschewed +eschewing +eschews +escobar +escondido +escort +escorted +escorting +escorts +escrow +escrowed +escudo +escudos +ese +esh +eskimo +eskimos +esmeralda +esmerelda +esophagus +esoteric +esp +espada +espana +espanol +espcially +espe +especial +especially +esperanto +espinal +espinosa +espinoza +espionage +esplanade +esposito +espouse +espoused +espouses +espousing +espresso +esprit +espy +esq +esquire +esquivel +ess +essa +essay +essayist +essays +esse +essen +essence +essences +essene +essense +essential +essentiality +essentially +essentials +esser +esses +essex +esso +est +esta +establish +established +establishes +establishing +establishment +establishments +estado +estate +estates +este +esteban +estee +esteem +esteemed +estefan +estella +estelle +ester +esters +estes +esther +estimable +estimate +estimated +estimates +estimating +estimation +estimations +estimator +estimators +estonia +estonian +estonians +estrada +estradiol +estranged +estrangement +estrella +estrich +estrin +estrogen +estrogens +estuaries +estuary +eszterhas +et +eta +etc +etcetera +etch +etched +etching +etchings +eternal +eternally +eternity +eth +ethan +ethane +ethanol +ethel +ether +ethereal +etheridge +ethernet +ethers +ethic +ethical +ethically +ethicist +ethicists +ethics +ethiopia +ethiopian +ethiopians +ethnic +ethnical +ethnically +ethnicity +ethnics +ethnocentric +ethnocentrism +ethnographic +ethnology +ethnomusicologist +ethnos +ethology +ethos +ethridge +ethyl +ethylene +etienne +etiology +etiquette +etna +eton +etre +etruscan +etruscans +etta +etter +ettinger +etude +etudes +etymological +etymologically +etymology +etzel +etzioni +eu +euan +eubanks +eucalyptus +eucharist +eucharistic +euchre +euclid +euclidean +eudora +eugene +eugenia +eugenic +eugenics +eugenie +eugenio +euler +eulogies +eulogize +eulogized +eulogy +eunice +eunuch +euphemism +euphemisms +euphemistic +euphemistically +euphoria +euphoric +euphrates +eurasia +eurasian +eure +eureka +euripides +euro +eurobond +eurobonds +eurocentric +eurodisney +eurodollar +eurodollars +europa +europe +european +europeans +euros +eurostat +eurotunnel +eustis +euthanasia +ev +eva +evacuate +evacuated +evacuating +evacuation +evacuations +evacuee +evacuees +evade +evaded +evader +evaders +evades +evading +eval +evaluate +evaluated +evaluates +evaluating +evaluation +evaluations +evaluative +evaluator +evaluators +evan +evanescence +evanescent +evangel +evangelical +evangelicals +evangeline +evangelism +evangelist +evangelistic +evangelists +evangelize +evangelized +evangelizing +evans +evanston +evansville +evaporate +evaporated +evaporates +evaporating +evaporation +evaporator +evasion +evasions +evasive +eve +evel +evelina +evelyn +even +evened +evenhanded +evenhandedly +evening +evenings +evenly +evenness +evens +evenson +event +eventful +events +eventual +eventuality +eventually +ever +everage +everest +everett +everglades +evergreen +evergreens +everitt +everlasting +everly +evermore +evers +everson +evert +everthing +everton +every +everybody +everyday +everyman +everyone +everything +everytime +everywhere +eves +evian +evict +evicted +evicting +eviction +evictions +evidence +evidenced +evidences +evident +evidentiary +evidently +evil +evildoer +evildoers +evils +evince +evinced +evinces +eviscerate +eviscerated +evisceration +evita +evocation +evocative +evoke +evoked +evokes +evoking +evolution +evolutionary +evolutionist +evolutions +evolve +evolved +evolves +evolving +evry +ew +ewald +ewan +ewart +ewe +ewell +ewer +ewers +ewes +ewing +ex +exabyte +exacerbate +exacerbated +exacerbates +exacerbating +exacerbation +exacerbations +exact +exacta +exacted +exacting +exactions +exactitude +exactly +exacts +exaggerate +exaggerated +exaggerates +exaggerating +exaggeration +exaggerations +exalt +exaltation +exalted +exalting +exalts +exam +examen +examination +examinations +examine +examined +examinee +examiner +examiners +examines +examining +example +examples +exams +exasperate +exasperated +exasperating +exasperation +excalibur +excavate +excavated +excavating +excavation +excavations +excavator +excavators +exceed +exceeded +exceeding +exceedingly +exceeds +excel +excelled +excellence +excellency +excellent +excellently +excelling +excels +excelsior +except +excepted +excepting +exception +exceptional +exceptionally +exceptions +excepts +excercise +excerpt +excerpted +excerpting +excerpts +excess +excesses +excessive +excessively +exchange +exchangeable +exchanged +exchanger +exchangers +exchanges +exchanging +exchequer +excise +excised +excises +excising +excision +excitable +excitation +excite +excited +excitedly +excitement +excitements +exciter +excites +exciting +excitingly +exclaim +exclaimed +exclaiming +exclaims +exclamation +exclamations +excludable +exclude +excluded +excludes +excluding +exclusion +exclusionary +exclusions +exclusive +exclusively +exclusives +exclusivity +excommunicated +excommunication +excoriate +excoriated +excoriating +excoriation +excrement +excrete +excretion +excretory +excruciating +excruciatingly +exculpate +exculpatory +excursion +excursions +excusable +excuse +excused +excuses +excusing +exec +execrable +execs +executable +execute +executed +executes +executing +execution +executioner +executioners +executions +executive +executives +executor +executors +executory +exegesis +exel +exemplar +exemplars +exemplary +exemplified +exemplifies +exemplify +exemplifying +exempt +exempted +exempting +exemption +exemptions +exempts +exercisable +exercise +exercised +exerciser +exercises +exercising +exert +exerted +exerting +exertion +exertions +exerts +exes +exeter +exhalation +exhale +exhaled +exhaust +exhausted +exhausting +exhaustion +exhaustive +exhaustively +exhausts +exhibit +exhibited +exhibiting +exhibition +exhibitionist +exhibitionists +exhibitions +exhibitor +exhibitors +exhibits +exhilarated +exhilarating +exhilaration +exhort +exhortation +exhortations +exhorted +exhorting +exhorts +exhumation +exhume +exhumed +exigencies +exigency +exigent +exile +exiled +exiles +exist +existance +existed +existence +existences +existent +existential +existentialist +existentialists +existing +exists +exit +exited +exiting +exits +exley +exner +exodus +exogenous +exon +exonerate +exonerated +exonerates +exonerating +exoneration +exor +exorbitant +exorbitantly +exorcise +exorcising +exorcisms +exorcist +exotic +exotica +exotics +expand +expandability +expandable +expanded +expander +expanding +expands +expanse +expanses +expansion +expansionary +expansionism +expansionist +expansions +expansive +expatiate +expatriate +expatriates +expatriation +expect +expectancies +expectancy +expectant +expectation +expectations +expected +expecting +expectorant +expects +expedience +expediency +expedient +expedite +expedited +expediting +expedition +expeditionary +expeditions +expeditious +expeditiously +expel +expelled +expelling +expels +expend +expendable +expended +expending +expenditure +expenditures +expends +expense +expensed +expenses +expensing +expensive +expensively +experience +experienced +experiences +experiencing +experiential +experiment +experimental +experimentalist +experimentally +experimentation +experimented +experimenter +experimenters +experimenting +experiments +expert +expertise +expertly +experts +expiration +expirations +expire +expired +expires +expiring +expiry +explain +explainable +explaination +explained +explaining +explains +explanation +explanations +explanatory +expletive +expletives +explicable +explication +explicit +explicitly +explicity +explode +exploded +explodes +exploding +exploit +exploitable +exploitation +exploitative +exploited +exploiters +exploiting +exploitive +exploits +exploration +explorations +exploratory +explore +explored +explorer +explorers +explores +exploring +explosion +explosions +explosive +explosively +explosiveness +explosives +expo +exponent +exponential +exponentially +exponents +export +exportable +exportation +exported +exporter +exporters +exporting +exports +expos +expose +exposed +exposes +exposing +exposition +expositions +exposure +exposures +expound +expounded +expounding +expounds +express +expressed +expresses +expressing +expression +expressionism +expressionist +expressionistic +expressionists +expressionless +expressions +expressive +expressivity +expressly +expresso +expressway +expressways +expropriate +expropriated +expropriating +expropriation +expropriations +expulsion +expulsions +expunge +expunged +expunging +exquisite +exquisitely +ext +extant +extemporaneous +extemporaneously +extend +extendable +extended +extender +extenders +extending +extends +extensible +extension +extensions +extensive +extensively +extent +extents +extenuating +exterior +exteriors +exterminate +exterminated +exterminating +extermination +exterminator +external +externalities +externality +externally +externals +extinct +extinction +extinctions +extinguish +extinguished +extinguisher +extinguishers +extinguishing +extinguishment +extol +extolled +extolling +extols +extort +extorted +extorting +extortion +extortionate +extortionist +extra +extract +extracted +extracting +extraction +extractions +extractor +extracts +extracurricular +extradite +extradited +extraditing +extradition +extragalactic +extrajudicial +extralegal +extramarital +extraneous +extraordinaire +extraordinarily +extraordinary +extrapolate +extrapolated +extrapolating +extrapolation +extrapolations +extras +extrasensory +extraterrestrial +extraterrestrials +extravagance +extravagances +extravagant +extravagantly +extravaganza +extravaganzas +extreme +extremely +extremes +extremism +extremist +extremists +extremities +extremity +extricate +extricated +extricating +extrinsic +extrovert +extroverted +extrude +extruded +extrusion +extrusions +exuberance +exuberant +exude +exuded +exudes +exuding +exult +exultant +exultation +exulted +exults +exxon +ey +eye +eyeball +eyeballed +eyeballing +eyeballs +eyebrow +eyebrows +eyed +eyedrops +eyeful +eyeglass +eyeglasses +eyeing +eyelash +eyelashes +eyeless +eyelet +eyelets +eyelid +eyelids +eyeliner +eyepiece +eyer +eyes +eyeshade +eyesight +eyesore +eyesores +eyestrain +eyewash +eyewear +eyewitness +eyewitnesses +eyler +eyre +eyrie +ezekiel +ezer +ezra +fa +fab +faber +faberge +fabian +fabiano +fabio +fable +fabled +fables +fabre +fabric +fabricant +fabricate +fabricated +fabricates +fabricating +fabrication +fabrications +fabricator +fabricators +fabrics +fabulous +fabulously +fac +facade +facades +face +faced +faceless +facelift +faceoff +faceplate +faces +facet +faceted +facetious +facetiously +facets +facial +facially +facials +facie +facile +facilitate +facilitated +facilitates +facilitating +facilitation +facilitator +facilitators +facilities +facility +facing +facsimile +facsimiles +fact +faction +factional +factionalism +factions +factitious +facto +factoid +factoids +factor +factored +factories +factoring +factors +factory +facts +factual +factually +faculties +faculty +fad +faddish +fade +faded +fadel +fadeout +fader +fades +fading +fado +fads +fae +faerie +fag +fagan +faggot +faggots +fagin +fags +fahd +faherty +fahey +fahrenheit +faience +fail +failed +failing +failings +fails +failsafe +failure +failures +fain +faint +fainted +fainter +faintest +fainthearted +fainting +faintly +faintness +faints +fair +fairbanks +fairchild +faircloth +faire +faired +fairer +fairest +fairfax +fairfield +fairground +fairgrounds +fairies +fairing +fairley +fairly +fairmont +fairness +fairs +fairview +fairway +fairways +fairy +fairyland +fairytale +faisal +fait +faith +faithful +faithfully +faithfulness +faithfuls +faithless +faiths +faits +fajardo +fajitas +fake +faked +faker +fakers +fakes +faking +falafel +falciparum +falcon +falconer +falconry +falcons +falk +falke +falkland +falklands +falkner +fall +falla +fallacies +fallacious +fallacy +fallback +fallen +faller +fallibility +fallible +falling +falloff +fallon +fallopian +fallout +fallow +fallows +falls +falmouth +false +falsehood +falsehoods +falsely +falseness +falsetto +falsification +falsified +falsifies +falsify +falsifying +falsity +falstaff +falter +faltered +faltering +falters +falun +falwell +fama +fame +famed +famer +famers +familia +familial +familiar +familiarity +familiarization +familiarize +familiarized +familiarizing +familiarly +families +family +famine +famines +famished +famous +famously +fan +fanatic +fanatical +fanatically +fanaticism +fanatics +fancied +fancier +fanciers +fancies +fanciest +fanciful +fancifully +fancy +fandango +fandom +fane +fanfare +fanfares +fang +fanged +fangled +fangs +fanned +fannie +fanning +fanny +fans +fant +fanta +fantasia +fantasies +fantasize +fantasized +fantasizes +fantasizing +fantastic +fantastical +fantastically +fantastico +fantasy +fantasyland +fanzine +far +faraday +faragher +farah +faraway +farber +farce +farcical +fare +fared +fares +farewell +farewells +farfetched +fargo +farias +farid +faries +farina +faring +faris +farish +farkas +farley +farm +farmed +farmer +farmers +farmhand +farmhands +farmhouse +farmhouses +farming +farmington +farmland +farmlands +farms +farmstead +farmworkers +farmyard +farnham +farnsworth +faro +faroe +farouk +farquhar +farr +farrago +farragut +farrah +farrakhan +farrar +farrel +farrell +farrier +farrington +farris +farrow +farrowing +farry +farsighted +farsightedness +fart +farther +farthest +farthing +farting +farts +farwell +fas +fasano +fascia +fascinate +fascinated +fascinates +fascinating +fascination +fascism +fascist +fascistic +fascists +fashion +fashionable +fashionably +fashioned +fashioning +fashions +faso +fass +fast +fastback +fastball +fastballs +fasted +fasten +fastened +fastener +fasteners +fastening +fastenings +faster +fastest +fastidious +fasting +fastness +fasts +fat +fatah +fatal +fatale +fatalism +fatalist +fatalistic +fatalities +fatality +fatally +fate +fated +fateful +fates +fath +fathead +father +fathered +fatherhood +fathering +fatherland +fatherless +fatherly +fathers +fathom +fathomed +fathoms +fatigue +fatigued +fatigues +fatiguing +fatima +fats +fatten +fattened +fattening +fatter +fattest +fatty +fatuous +fatwa +faucet +faucets +fauci +faulk +faulkner +faulks +fault +faulted +faulting +faults +faulty +fauna +fauntleroy +faure +faust +faustian +fausto +faux +fava +fave +favela +favor +favorability +favorable +favorably +favored +favoring +favorite +favorites +favoritism +favors +favour +favourable +favourably +favoured +favourite +favours +favre +fawcett +fawkes +fawlty +fawn +fawning +fawns +fax +faxed +faxes +faxing +faxon +fay +faye +fayed +fayette +fayetteville +fayez +faze +fazed +fazio +fb +fbi +fc +fcp +fe +fealty +fear +feared +fearful +fearing +fearless +fearn +fears +fearsome +feasability +feasibility +feasible +feasibly +feast +feasted +feaster +feasting +feasts +feat +feather +featherbed +feathered +feathering +featherless +feathers +featherweight +feathery +feats +feature +featured +featureless +features +featuring +feb +february +fecal +feces +feckless +fed +feder +federal +federalism +federalist +federalists +federalization +federalize +federalized +federalizing +federally +federated +federation +federations +federico +federman +fedex +fedora +feds +fee +feeble +feebly +feed +feedback +feedbacks +feeder +feeders +feeding +feedings +feedlot +feedlots +feeds +feedstock +feel +feeler +feelers +feeley +feeling +feelings +feels +feely +feeney +feeny +feer +fees +feet +fehr +fei +feigenbaum +feign +feigned +feigning +fein +feinberg +feiner +feingold +feinstein +feint +feist +feisty +fel +feld +felder +feldman +feldstein +feldt +felice +felicia +feliciano +felicitations +felicitous +felicity +feline +felines +felipe +felis +felix +fell +fella +fellas +felled +feller +fellers +felling +fellini +fellow +fellows +fellowship +fellowships +fells +felon +felonies +felonious +felons +felony +fels +felt +felton +felts +felty +fem +female +females +feminine +femininity +feminism +feminist +feminists +femme +femmes +femoral +femur +fen +fence +fenced +fences +fencing +fend +fended +fender +fenders +fending +fends +fenelon +feng +fenian +fenn +fennel +fennell +fenner +fennimore +fens +fenster +fenton +fenway +fer +feral +ferber +ferd +ferdinand +ferenc +fergie +fergus +ferguson +feria +ferman +ferment +fermentation +fermented +fermenting +fermi +fermin +fern +fernand +fernanda +fernandes +fernandez +fernandina +fernando +ferns +ferocious +ferociously +ferocity +ferranti +ferrara +ferrari +ferraro +ferreira +ferreiro +ferrel +ferrell +ferrer +ferrera +ferret +ferreted +ferreting +ferrets +ferretti +ferri +ferried +ferrier +ferries +ferrigno +ferris +ferrite +ferro +ferrous +ferry +ferryboat +ferrying +fers +fertile +fertility +fertilization +fertilize +fertilized +fertilizer +fertilizers +fertilizes +fertilizing +fervent +fervently +fervid +fervor +fervour +fescue +fess +fessed +fest +festa +fester +festered +festering +festival +festivals +festive +festively +festivities +festivity +festoon +festooned +festus +feta +fetal +fetch +fetched +fetches +fetching +fete +feted +fetid +fetish +fetishes +fetishism +fetishist +fetishists +fetter +fetters +fettuccine +fetus +fetuses +feu +feud +feudal +feudalism +feuded +feuding +feuds +feuer +fever +fevered +feverfew +feverish +feverishly +fevers +few +fewer +fewest +fey +feynman +fez +ff +fg +fi +fiala +fiance +fiancee +fiasco +fiascos +fiat +fiber +fiberglass +fibers +fibre +fibreboard +fibreglass +fibres +fibrillation +fibrin +fibroids +fibrosis +fibrous +fibs +fibula +fickle +fickleness +fiction +fictional +fictionalized +fictions +fictitious +ficus +fiddle +fiddled +fiddler +fiddlers +fiddles +fiddling +fide +fidel +fidelity +fides +fidget +fidgeting +fidgety +fido +fiduciaries +fiduciary +fied +fiedler +fief +fiefdom +fiefdoms +field +fielded +fielden +fielder +fielders +fieldhouse +fielding +fields +fieldwork +fiend +fiendish +fiendishly +fiends +fiennes +fierce +fiercely +fiercer +fiercest +fierro +fiery +fiesta +fiestas +fife +fifi +fifo +fifteen +fifteenth +fifth +fifties +fiftieth +fifty +fig +figaro +fight +fighter +fighters +fighting +fights +figment +figments +figs +figueroa +figuration +figurative +figuratively +figure +figured +figurehead +figures +figurine +figurines +figuring +fiji +fijian +fike +fil +fila +filament +filaments +filbert +filch +filched +file +filed +filemaker +filename +filer +filers +files +filet +filets +filial +filibuster +filibustered +filibustering +filibusters +filigree +filing +filings +filip +filipino +filipinos +filippo +fill +filled +filler +fillers +fillet +filleted +fillets +fillies +filling +fillings +fillip +fillmore +fills +filly +film +filmed +filmgoers +filmic +filming +filmmaker +filmmakers +filmmaking +filmore +films +filo +filofax +filomena +fils +filter +filtered +filtering +filters +filth +filthy +filtration +fin +fina +finagle +finagling +final +finale +finalist +finalists +finality +finalization +finalize +finalized +finalizes +finalizing +finally +finals +finance +financed +finances +financial +financially +financials +financier +financiere +financiers +financing +financings +finca +finch +fincher +finches +find +finder +finders +finding +findings +findlay +finds +fine +fined +finegold +finely +fineman +fineness +finer +finery +fines +finesse +finessed +finest +finger +fingered +fingering +fingerless +fingernail +fingernails +fingerprint +fingerprinted +fingerprinting +fingerprints +fingers +fingertip +fingertips +fini +finial +finian +finicky +fining +finis +finish +finished +finisher +finishers +finishes +finishing +finite +fink +finke +finkel +finkelstein +finkle +finks +finland +finlay +finley +finn +finnan +finnegan +finnerty +finney +finnigan +finnish +finns +fino +fins +fiona +fiore +fiorello +fiori +fir +fire +firearm +firearms +fireball +fireballs +firebird +firebirds +firebomb +firebombed +firebombing +firebombs +firebrand +firebrands +firecracker +firecrackers +fired +firefight +firefighter +firefighters +firefighting +fireflies +firefly +firehouse +firehouses +fireman +firemen +fireplace +fireplaces +firepower +fireproof +fireproofing +fires +fireside +firestone +firestorm +firewall +firewater +fireweed +firewood +firework +fireworks +firey +firing +firings +firm +firma +firmament +firman +firmed +firmer +firmest +firming +firmly +firmness +firms +firs +first +firstborn +firsthand +firstly +firsts +firth +fisc +fiscal +fiscally +fischer +fischler +fish +fishbone +fishbowl +fishburn +fishburne +fished +fisher +fisheries +fisherman +fishermen +fishers +fishery +fishes +fishing +fishkill +fishkin +fishman +fishmonger +fishnets +fishtail +fishy +fisk +fiske +fissile +fission +fissionable +fissure +fissured +fissures +fist +fistful +fisticuffs +fists +fit +fitch +fitchburg +fite +fitful +fitfully +fitness +fits +fitted +fitter +fitters +fittest +fitting +fittingly +fittings +fitz +fitzgerald +fitzgibbon +fitzhugh +fitzpatrick +fitzsimmons +fitzwater +five +fivefold +fiver +fives +fix +fixable +fixate +fixated +fixating +fixation +fixative +fixatives +fixe +fixed +fixer +fixes +fixing +fixings +fixture +fixtures +fizz +fizzle +fizzled +fizzles +fizzling +fizzy +fjord +fjords +fl +flab +flabbergasted +flabby +flaccid +flack +flag +flagella +flagellate +flagellation +flagg +flagged +flagging +flagpole +flagpoles +flagrant +flagrantly +flags +flagship +flagships +flagstaff +flagstone +flagstones +flaherty +flail +flailing +flair +flak +flake +flaked +flakes +flakey +flaking +flaky +flamboyance +flamboyant +flamboyantly +flame +flamed +flamenco +flameout +flamers +flames +flamethrower +flamethrowers +flaming +flamingo +flamingos +flamm +flammability +flammable +flan +flanagan +flanders +flange +flanges +flanigan +flank +flanked +flanker +flanking +flanks +flannel +flannels +flannery +flannigan +flap +flapjack +flapped +flapper +flappers +flapping +flaps +flare +flared +flares +flaring +flash +flashback +flashbacks +flashbulb +flashed +flasher +flashers +flashes +flashier +flashiest +flashing +flashlight +flashlights +flashpoint +flashpoints +flashy +flask +flasks +flat +flatbed +flathead +flatheads +flatiron +flatland +flatlands +flatley +flatly +flatmate +flatness +flats +flatt +flatten +flattened +flattening +flattens +flatter +flattered +flattering +flattery +flattish +flatulence +flatulent +flatware +flatworm +flaubert +flaum +flaunt +flaunted +flaunting +flaunts +flavia +flavin +flavor +flavored +flavoring +flavorings +flavors +flavour +flaw +flawed +flawless +flawlessly +flaws +flax +flaxman +flay +flayed +flaying +flea +fleas +fleck +flecks +fled +fledged +fledging +fledgling +flee +fleece +fleeced +fleecing +fleecy +fleeing +flees +fleet +fleeting +fleetingly +fleets +fleetwood +fleischer +fleischmann +fleishman +fleming +flemings +flemish +flemming +flesch +flesh +fleshed +fleshing +fleshly +fleshy +fletch +fletcher +fleur +fleury +flew +flex +flexed +flexes +flexi +flexibility +flexible +flexibly +flexing +flexion +flexor +flick +flicked +flicker +flickered +flickering +flickers +flicking +flicks +flier +fliers +flies +flight +flightless +flights +flighty +flimsiest +flimsy +flinch +flinched +flinching +flinders +fling +flinging +flings +flinn +flint +flintlock +flints +flintstone +flintstones +flinty +flip +flippancy +flippant +flipped +flipper +flippers +flipping +flips +flirt +flirtation +flirtations +flirtatious +flirted +flirting +flirts +flirty +flit +flitting +flix +flo +float +floated +floater +floaters +floating +floats +floaty +flock +flocked +flockhart +flocking +flocks +floe +flog +flogged +flogging +floggings +flom +flood +flooded +floodgate +floodgates +flooding +floodlight +floodlights +floodplain +floods +floodwater +floodwaters +floor +floorboard +floorboards +floored +flooring +floors +flop +flopped +floppies +flopping +floppy +flops +flor +flora +floral +florence +florentine +florentino +flores +florescent +florey +florez +florian +florid +florida +floridian +floridians +florin +florio +floris +florist +florists +flory +floss +flossie +flotation +flotilla +flotsam +flounce +flounces +flounder +floundered +floundering +flounders +flour +flourescent +flourish +flourished +flourishes +flourishing +flours +floury +flout +flouted +flouting +flouts +flow +flowchart +flowcharts +flowed +flower +flowered +flowering +flowerpot +flowers +flowery +flowing +flown +flows +floyd +flu +flubs +fluctuate +fluctuated +fluctuates +fluctuating +fluctuation +fluctuations +flue +fluency +fluent +fluently +fluff +fluffed +fluffing +fluffy +fluid +fluidity +fluidized +fluids +fluke +flukes +flume +flummoxed +flung +flunk +flunked +flunkies +flunking +flunks +flunky +fluor +fluoresce +fluorescence +fluorescent +fluorescents +fluoridation +fluoride +fluorine +fluoroscopy +flurries +flurry +flus +flush +flushed +flushes +flushing +fluster +flustered +flute +fluted +flutes +fluting +flutist +flutter +fluttered +fluttering +flutters +fluvial +flux +fly +flyaway +flyby +flycatcher +flyer +flyers +flying +flynn +flyover +flyovers +flypaper +flytrap +flyway +flyweight +flywheel +fm +fo +foal +foam +foaming +foams +foamy +fob +focal +foci +focus +focused +focuses +focusing +focussed +focussing +fodder +fodor +foe +foerster +foes +fog +fogarty +fogel +fogerty +fogg +fogged +foggiest +fogging +foggy +foghorn +fogle +foia +foible +foibles +foie +foil +foiled +foiling +foils +foist +foisted +foisting +fokker +folate +fold +foldable +folded +folder +folders +folding +foldout +folds +foley +foliage +foliate +folic +folie +folies +folio +folios +folk +folkish +folklore +folkloric +folkman +folks +folksongs +folksy +folktale +folkways +follette +follicle +follicles +follies +follow +followed +follower +followers +following +followings +follows +followup +follwing +folly +folsom +foment +fomented +fomenting +fomento +fon +fond +fonda +fonder +fondest +fondle +fondled +fondling +fondly +fondness +fonds +fondue +fone +fong +fonseca +font +fontaine +fontana +fontenot +fontes +fonts +foo +food +foods +foodstuff +foodstuffs +fool +fooled +foolery +foolhardy +fooling +foolish +foolishly +foolishness +foolproof +fools +foor +foot +footage +football +footballer +footballs +footbridge +foote +footed +footer +footers +footfall +foothill +foothills +foothold +footholds +footing +footings +footlights +footloose +footman +footnote +footnoted +footnotes +footnoting +footpath +footpaths +footprint +footprints +foots +footsie +footstep +footsteps +footstool +footwear +footwork +fop +for +fora +forage +foraging +foray +forays +forbade +forbear +forbearance +forbes +forbid +forbidden +forbidding +forbids +force +forced +forceful +forcefully +forcefulness +forceps +forces +forcible +forcibly +forcing +ford +fordham +fords +fordyce +fore +forearm +forearms +forebear +forebears +foreboding +forebrain +forecast +forecasted +forecaster +forecasters +forecasting +forecasts +foreclose +foreclosed +forecloses +foreclosing +foreclosure +foreclosures +forefather +forefathers +forefinger +forefingers +forefront +forego +foregoes +foregoing +foregone +foreground +forehand +forehands +forehead +foreheads +foreign +foreigner +foreigners +foreknowledge +foreman +foremen +foremost +forensic +forensically +forensics +foreplay +forero +forerunner +forerunners +foresaw +foresee +foreseeable +foreseeing +foreseen +foresees +foreshadow +foreshadowed +foreshadowing +foreshadows +foresight +foreskin +forest +forestall +forestalled +forestalling +forested +forester +foresters +forestry +forests +foret +foretaste +foretell +foretelling +foretells +forethought +foretold +forever +forevermore +foreward +forewarned +forewarning +foreword +forex +forfeit +forfeited +forfeiting +forfeits +forfeiture +forfeitures +forgave +forge +forged +forger +forgeries +forgers +forgery +forges +forget +forgetful +forgetfulness +forgets +forgettable +forgetting +forging +forgings +forgivable +forgive +forgiven +forgiveness +forgives +forgiving +forgo +forgoes +forgoing +forgone +forgot +forgotten +forint +forints +fork +forked +forker +forking +forklift +forklifts +forks +forlorn +form +forma +formal +formaldehyde +formalised +formalism +formalist +formalities +formality +formalization +formalize +formalized +formalizes +formalizing +formally +forman +format +formation +formations +formative +formats +formatted +formatting +forme +formed +former +formerly +formic +formica +formidable +formidably +forming +formless +formosa +forms +formula +formulae +formulaic +formulary +formulas +formulate +formulated +formulates +formulating +formulation +formulations +forney +fornicating +fornication +forrest +forrestal +forrester +forsake +forsaken +forsaking +forsee +forseeable +forsman +forsook +forst +forster +forswear +forsworn +forsyth +forsythe +forsythia +fort +forte +forth +forthcoming +forthright +forthrightly +forthrightness +forthwith +fortier +forties +fortieth +fortification +fortifications +fortified +fortify +fortifying +fortin +fortis +fortitude +fortnight +fortnightly +fortran +fortress +fortresses +forts +fortuitous +fortuna +fortunate +fortunately +fortunato +fortune +fortunes +forty +forum +forums +forward +forwarded +forwarder +forwarders +forwarding +forwards +fosdick +foss +fosse +fossil +fossilized +fossils +foster +fostered +fostering +fosters +fotheringham +fotis +fought +foul +fouled +fouling +foulkes +foulness +fouls +found +foundation +foundational +foundations +founded +founder +foundered +foundering +founders +founding +foundling +foundries +foundry +founds +fountain +fountainhead +fountains +four +fourfold +fourier +fournier +fours +foursome +foursquare +fourteen +fourteenth +fourth +fourthly +fourths +foust +fouts +foward +fowl +fowler +fox +foxboro +foxes +foxfire +foxgloves +foxhole +foxholes +foxman +foxtail +foxtrot +foxwoods +foxx +foxy +foy +foyer +foyle +foyt +fp +fps +fr +fra +fracas +fractal +fractals +fraction +fractional +fractionally +fractionation +fractions +fractious +fracture +fractured +fractures +fracturing +fraga +fragile +fragility +fragment +fragmentary +fragmentation +fragmented +fragmenting +fragments +fragrance +fragrances +fragrant +fraid +frail +frailties +frailty +frakes +fram +frame +framed +framer +framers +frames +framework +frameworks +framing +framingham +frampton +fran +franc +franca +francais +francaise +france +frances +francesca +francesco +franchise +franchised +franchisee +franchisees +franchiser +franchises +franchising +franchisor +francia +francie +francine +francis +franciscan +franciscans +francisco +franciso +franck +franco +francois +francoise +francophile +francophone +francs +frank +franke +frankel +frankenstein +frankfort +frankfurt +frankfurter +frankie +frankish +frankl +franklin +franklins +frankly +frankness +franks +franky +frannie +frans +fransisco +frantic +frantically +frantz +franz +franzen +franzoni +fraser +frasier +frat +frater +fraternal +fraternities +fraternity +fraternize +fraternizing +fratricidal +fratricide +frau +fraud +frauds +fraudsters +fraudulence +fraudulent +fraudulently +fraught +fraulein +fray +frayed +fraying +frayn +frazer +frazier +frazzled +freak +freaked +freaking +freaks +freaky +freckle +freckled +freckles +fred +freda +freddie +freddy +frederic +frederica +frederick +fredericks +fredericksburg +frederico +fredericton +fredric +fredrick +fredrickson +fredrik +free +freebie +freebies +freeborn +freed +freedman +freedom +freedoms +freefall +freeform +freehand +freehold +freeholder +freeholders +freeing +freelance +freelancer +freelancing +freeland +freeloader +freeloaders +freely +freeman +freemans +freemason +freemasonry +freemasons +freemen +freemont +freeport +freer +frees +freest +freestanding +freestone +freestyle +freethinker +freeware +freeway +freeways +freewheeling +freewill +freeze +freezer +freezers +freezes +freezing +frei +freiburg +freight +freighter +freighters +freightliner +freire +freitas +fremont +french +frenchman +frenchmen +frenchwoman +frenetic +frenetically +frenkel +frenzel +frenzied +frenzies +frenzy +freon +frequencies +frequency +frequent +frequented +frequently +frequents +frere +freres +fresco +frescoed +frescoes +frescos +fresh +freshen +freshener +fresheners +fresher +freshest +freshly +freshman +freshmen +freshness +freshwater +fresno +fret +fretful +frets +fretted +fretting +freud +freudian +freund +frew +frey +freya +friable +friar +friars +fribourg +frick +fricke +fricker +friction +frictional +frictionless +frictions +frida +friday +fridays +fridge +fridges +fried +friedan +friedel +friedl +friedland +friedlander +friedman +friedmann +friedrich +friel +friend +friendless +friendlier +friendliest +friendliness +friendly +friends +friendship +friendships +frier +fries +friese +friesen +frieze +frigate +frigates +frigging +fright +frighten +frightened +frightening +frighteningly +frightens +frightful +frightfully +frights +frigid +frijoles +frill +frills +frilly +fringe +fringed +fringes +frisbee +frisbees +frisby +frisch +frisco +frisk +frisked +frisky +frisson +frist +frith +frito +frits +fritsch +fritter +fritts +fritz +frivolity +frivolous +frivolously +frizzell +fro +frock +frocks +froelich +frog +frogger +frogs +frolic +frolicking +frolics +from +fromage +frome +fromm +frommer +fromthe +frond +fronds +front +frontage +frontal +frontally +fronted +frontera +frontier +frontiers +fronting +frontispiece +frontline +frontrunner +frontrunners +fronts +froom +frosh +frost +frostbite +frostbitten +frosted +frosting +frosts +frosty +froth +frothing +frothy +frown +frowned +frowning +frowns +froze +frozen +fructose +frug +frugal +frugality +frugally +fruit +fruitcake +fruitcakes +fruited +fruitful +fruitfulness +fruiting +fruition +fruitless +fruitlessly +fruits +fruity +frum +frumpy +frustrate +frustrated +frustrates +frustrating +frustratingly +frustration +frustrations +fry +frye +fryer +fryers +frying +fryman +ft +fu +fuad +fuchs +fuchsia +fuck +fucked +fucking +fudd +fudge +fudged +fudges +fudging +fuel +fueled +fueling +fuelled +fuelling +fuels +fuente +fuentes +fuer +fugard +fugate +fugitive +fugitives +fugu +fugue +fugues +fuhrer +fuji +fujian +fujii +fujimori +fujimoto +fujita +fujitsu +fujiwara +fukui +fukuoka +fulani +fulbright +fulcrum +fulfil +fulfill +fulfilled +fulfilling +fulfillment +fulfills +fulfilment +fulk +full +fullback +fullbacks +fuller +fullerton +fullest +fullness +fulltime +fully +fulmer +fulminate +fulsome +fulton +fultz +fumble +fumbled +fumbles +fumbling +fume +fumed +fumes +fumigant +fumigate +fumigation +fuming +fun +function +functional +functionality +functionally +functionaries +functionary +functioned +functioning +functions +fund +fundament +fundamental +fundamentalism +fundamentalist +fundamentalists +fundamentally +fundamentals +funded +funder +funders +funding +fundraiser +fundraisers +fundraising +funds +funeral +funerals +funerary +fung +fungal +fungi +fungible +fungicide +fungicides +fungus +funicello +funk +funkiest +funks +funky +funnel +funneled +funneling +funnelled +funnels +funnier +funnies +funniest +funnily +funniness +funny +fuqua +fur +furby +furies +furious +furiously +furlong +furlough +furloughs +furman +furnace +furnaces +furnish +furnished +furnishes +furnishing +furnishings +furniture +furor +furore +furred +furrier +furriers +furrow +furrowed +furry +furs +furst +further +furtherance +furthered +furthering +furthermore +furthers +furthest +furtive +furtively +fury +fusco +fuse +fused +fuselage +fuselages +fuses +fusillade +fusing +fusion +fuss +fussed +fussell +fusses +fussing +fussy +fust +futher +futile +futilely +futility +futon +future +futures +futurism +futurist +futuristic +futurists +fuzz +fuzzier +fuzzy +fw +fwd +fy +ga +gaas +gab +gaba +gabardine +gabbard +gabble +gabby +gabe +gable +gabled +gabler +gables +gabon +gabor +gabriel +gabriela +gabriele +gabriella +gabrielle +gaby +gad +gadd +gaddis +gade +gadflies +gadfly +gadget +gadgetry +gadgets +gads +gadsby +gadsden +gadzooks +gae +gael +gaelic +gaertner +gaff +gaffe +gaffer +gaffes +gaffney +gaffs +gag +gaga +gagarin +gage +gages +gagged +gagging +gaggle +gagne +gags +gaia +gaiety +gail +gaillard +gaily +gain +gained +gainer +gainers +gaines +gainesville +gainful +gainfully +gaining +gains +gait +gaithersburg +gal +gala +galactic +galactose +galahad +galant +galante +galanter +galapagos +galarza +galas +galata +galatea +galatians +galaxies +galaxy +galbraith +gale +galea +galen +galena +galeria +galerie +gales +galesburg +gali +galicia +galil +galilean +galilee +galilei +galileo +galindo +gall +galla +gallagher +gallant +gallantry +gallardo +gallbladder +galled +gallego +gallegos +galleon +galleons +galleria +galleries +gallery +galley +galleys +galli +gallic +galling +gallium +gallivanting +gallo +gallon +gallons +gallop +galloped +galloping +galloway +gallows +galls +gallstones +gallup +gallus +galore +galoshes +galpin +gals +galt +galvanic +galvanize +galvanized +galvanizes +galvanizing +galveston +galvin +galway +gama +gamba +gambia +gambier +gambit +gambits +gamble +gambled +gambler +gamblers +gambles +gambling +gamboa +gambol +gambrel +gambrell +game +gameboy +gamely +gamer +gamers +games +gamesmanship +gamete +gamey +gaming +gamma +gammon +gammons +gams +gamut +gan +gandalf +gander +gandhi +gandolfo +gandy +gang +ganga +gangbusters +ganged +ganges +ganging +gangland +ganglia +ganglion +gangly +gangplank +gangrenous +gangs +gangsta +gangster +gangsters +ganja +gann +gannet +gannett +gannon +gans +gant +gantry +gantt +ganymede +ganz +gap +gape +gaping +gapping +gaps +gar +gara +garage +garaged +garages +garay +garb +garbage +garbed +garber +garble +garbled +garbo +garcia +gard +garde +garden +gardena +gardener +gardeners +gardenia +gardenias +gardening +gardens +gardiner +gardner +gare +gareth +garfield +garfinkel +garfunkel +garg +gargantuan +gargle +gargoyle +gargoyles +gari +garibaldi +garish +garland +garlands +garlic +garlicky +garman +garment +garments +garn +garner +garnered +garnering +garners +garnet +garnets +garnett +garnier +garnish +garnished +garnishes +garo +garofalo +garon +garrard +garret +garrett +garrick +garrido +garrison +garrisoned +garrod +garron +garrulous +garry +garson +garten +garter +garters +garth +gartner +garton +garvey +garvin +garwood +gary +garza +garzon +gas +gascon +gaseous +gases +gash +gashed +gashes +gasification +gasket +gaskets +gaskin +gaskins +gaslight +gasoline +gasolines +gasp +gaspar +gaspard +gasped +gasper +gasping +gasps +gass +gassed +gasser +gasses +gassing +gassner +gast +gaston +gastonia +gastric +gastritis +gastro +gastroenterologist +gastroenterology +gastrointestinal +gastronomic +gastronomy +gat +gate +gated +gatekeeper +gatekeepers +gately +gater +gates +gateway +gateways +gather +gathered +gatherer +gatherers +gathering +gatherings +gathers +gating +gatling +gator +gatorade +gators +gatos +gatsby +gatt +gattuso +gatwick +gauche +gaucher +gaucho +gaudet +gaudy +gauge +gauged +gauges +gaughan +gauging +gauguin +gaul +gaulle +gauls +gault +gaunt +gauntlet +gaur +gaus +gauss +gaut +gauthier +gautier +gauze +gauzy +gave +gavel +gavin +gawain +gawd +gawk +gawkers +gawking +gawky +gay +gaye +gayer +gayle +gaylord +gayness +gaynor +gays +gaz +gaza +gazan +gaze +gazebo +gazed +gazelle +gazelles +gazes +gazeta +gazette +gazi +gazing +gazpacho +gazprom +gd +gdansk +gds +ge +gean +gear +gearbox +gearboxes +geared +gearing +gears +gearshift +geary +gebel +gecko +geckos +ged +geddes +gee +geek +geeks +geeky +geena +geer +gees +geese +geez +geezer +geezers +gefilte +gehrig +gehry +geico +geiger +geigy +geir +geis +geisel +geisha +geishas +geisler +geist +gekko +gel +gelatin +gelatine +gelatinous +gelder +gelding +gell +geller +gellert +gelling +gelman +gels +gem +gemini +gemma +gems +gemstone +gemstones +gen +gena +gendarme +gender +genders +gendron +gene +genealogical +genealogists +genealogy +genentech +genera +general +generale +generalissimo +generalist +generalists +generalities +generality +generalization +generalizations +generalize +generalized +generalizes +generalizing +generally +generals +generalship +generate +generated +generates +generating +generation +generational +generations +generative +generator +generators +generic +generically +generics +generosity +generous +generously +genes +genesee +genesis +genet +genetic +genetically +geneticist +geneticists +genetics +geneva +geneve +genevieve +genghis +genial +geniality +genie +genital +genitalia +genitals +genius +geniuses +gennady +gennaro +gennifer +geno +genoa +genocidal +genocide +genoese +genome +genomic +genotype +genotypes +genova +genovese +genre +genres +gens +gent +genteel +gentian +gentile +gentiles +gentility +gentle +gentleman +gentlemanly +gentlemen +gentleness +gentler +gentles +gentlest +gently +gentrification +gentry +gents +genuine +genuinely +genuineness +genus +genzyme +geo +geochemical +geochemistry +geode +geodesic +geodesy +geoff +geoffrey +geoffroy +geographers +geographic +geographical +geographically +geographies +geography +geologic +geological +geologist +geologists +geology +geomagnetic +geometric +geometrical +geometrically +geometries +geometry +geophysical +geophysicist +geophysics +geopolitical +geopolitics +geordie +georg +george +georges +georgetown +georgette +georgi +georgia +georgian +georgians +georgie +georgina +georgio +georgy +geos +geoscience +geosphere +geostationary +geothermal +geoworks +gephardt +ger +gerace +geraghty +gerais +gerald +geraldine +geraldo +geranium +geraniums +gerard +gerardo +gerber +gerbera +gerbil +gerbils +gerd +gerda +gere +gergen +gerhard +gerhardt +gerhart +geriatric +geriatrics +gering +germ +germain +germaine +german +germane +germanic +germanium +germano +germans +germantown +germany +germanys +germinal +germinate +germinated +germinating +germination +germs +geronimo +gerontologist +gerontology +gerrit +gerry +gerrymander +gerrymandered +gerrymandering +gers +gershon +gershwin +gerson +gerstner +gert +gerth +gertler +gertrude +gertz +gervais +gery +gesell +gesellschaft +gess +gest +gestalt +gestapo +gestating +gestation +gesture +gestured +gestures +gesturing +gesundheit +get +getaway +getaways +gethsemane +gets +gettable +getter +getters +getting +getty +gettys +gettysburg +getz +geva +geyer +geyser +geysers +ghana +ghanaian +ghanaians +ghastly +ghazi +ghee +ghent +ghetto +ghettos +ghosh +ghost +ghostbusters +ghosted +ghosting +ghostlike +ghostly +ghosts +ghoul +ghoulish +ghouls +ghulam +gi +giacometti +giacomo +gian +giancarlo +gianfranco +gianna +giannini +giant +giants +giap +gib +gibb +gibbering +gibberish +gibbon +gibbons +gibbs +gibby +gibe +gibes +giblet +giblin +gibraltar +gibson +gid +giddens +giddily +giddiness +giddy +gideon +gidget +gies +gif +gifford +gift +gifted +gifting +gifts +giftware +gig +giga +gigabit +gigabits +gigabyte +gigabytes +gigahertz +gigante +gigantic +gigantism +gigawatt +gigging +giggle +giggled +giggles +giggling +giggly +gigi +giglio +gigolos +gigot +gigs +gil +gila +gilbert +gilberto +gilchrist +gild +gilda +gildea +gilded +gilder +gilding +gile +gilead +giles +gilford +gilgamesh +gilham +gill +gillan +gillard +gillen +giller +gilles +gillespie +gillette +gilley +gilliam +gillian +gillick +gillies +gilligan +gilliland +gillingham +gillis +gillman +gillmore +gills +gilly +gilman +gilmer +gilmore +gilmour +gilpin +gilroy +gilson +gilt +gilts +gimble +gimme +gimmick +gimmickry +gimmicks +gimmicky +gin +gina +ging +ginger +gingerbread +gingerly +gingham +gingrich +ginkgo +ginn +ginned +ginnie +ginning +ginny +gino +gins +ginsberg +ginsburg +ginseng +gioia +giordano +giorgi +giorgio +giovanni +gipper +gipson +giraffe +giraffes +giraldo +girard +girardi +gird +girder +girders +girding +girdle +girdles +girds +girgis +girl +girlfriend +girlfriends +girlhood +girlie +girlish +girls +girly +giro +girozentrale +girth +gis +giselle +gish +gist +git +gita +gitano +gitlin +giuliani +giuliano +giulio +giuseppe +give +giveaway +giveaways +given +givenchy +givens +giver +givers +gives +giveth +giving +giza +gizmo +gizmos +gizzard +glace +glacial +glacially +glaciated +glaciation +glacier +glaciers +glad +gladden +glade +glades +gladiator +gladiators +gladiolus +gladly +gladman +gladness +gladstone +gladys +glam +glamor +glamorize +glamorized +glamorizing +glamorous +glamour +glance +glanced +glances +glancing +gland +glands +glandular +glare +glared +glares +glaring +glaringly +glas +glaser +glasgow +glass +glassblower +glasscock +glassed +glasser +glasses +glassmaking +glassman +glassware +glassy +glauber +glaucoma +glaxo +glaze +glazebrook +glazed +glazer +glazes +glazier +glazing +gld +gleam +gleamed +gleaming +gleams +glean +gleaned +gleaning +gleason +glee +gleeful +gleefully +gleeson +glen +glenda +glendale +glendon +glengarry +glenlivet +glenn +glenna +glenny +glens +glenview +glenwood +glial +glib +glibly +glick +glide +glided +glider +gliders +glides +gliding +glimmer +glimmered +glimmering +glimmers +glimpse +glimpsed +glimpses +glint +glinting +glisten +glistened +glistening +glitch +glitches +glitter +glittered +glittering +glitters +glittery +glitz +glitzy +gloaming +gloat +gloated +gloating +gloats +glob +global +globalism +globalization +globalized +globally +globe +globes +globetrotters +globetrotting +globs +globular +globulin +globus +glock +glockenspiel +glocks +gloom +gloomier +gloomiest +gloomily +gloomy +glop +gloria +glories +glorification +glorified +glorifies +glorify +glorifying +glorious +gloriously +glory +gloss +glossaries +glossary +glossed +glosses +glossier +glossy +gloster +glottal +gloucester +glove +glovebox +gloved +glover +gloves +glow +glowed +glower +glowered +glowering +glowing +glowingly +glows +gluck +glucose +glue +glueck +glued +glues +gluing +glum +glumly +glut +glutamate +glutamic +glutathione +gluten +glutes +gluts +glutted +glutton +gluttonous +gluttons +gluttony +glycerin +glycerol +glycine +glycogen +glycol +glycolic +glyn +glynn +glyphs +gm +gmbh +gn +gnarled +gnarly +gnash +gnashing +gnat +gnats +gnaw +gnawed +gnawing +gnaws +gnocchi +gnome +gnomes +gnomic +gnostic +gnu +go +goa +goad +goaded +goading +goal +goalie +goalies +goalkeeper +goalposts +goals +goaltender +goat +goatee +goatherd +goats +gob +gobble +gobbled +gobbledygook +gobbler +gobbles +gobbling +gobi +goble +goblet +goblets +goblin +goblins +gobs +god +godard +goddam +goddamn +goddamned +goddard +goddaughter +goddess +goddesses +godel +godfather +godforsaken +godfrey +godhead +godiva +godless +godlessness +godley +godlike +godliness +godly +godmother +godot +godown +gods +godsend +godson +godspeed +godwin +godzilla +goebbels +goebel +goel +goelz +goer +goering +goers +goes +goethe +goetz +goff +goffman +goggin +goggle +goggles +gogh +gogo +goh +goin +going +goings +golan +gold +golda +goldberg +golden +goldenberg +goldeneye +goldenrod +golder +goldfarb +goldfield +goldfields +goldfinch +goldfine +goldfinger +goldfish +goldie +goldilocks +goldin +golding +goldman +goldmine +golds +goldsborough +goldsmith +goldstar +goldstein +goldstone +goldsworthy +goldwater +goldwin +goldwyn +goldy +golem +goleta +golf +golfer +golfers +golfing +golgotha +goliath +goliaths +golightly +golkar +goll +golly +goltz +golub +gomer +gomes +gomez +gomorrah +gonadotropin +gonads +gondola +gondolas +gondoliers +gone +goner +gong +gongs +gonna +gonorrhea +gonsalves +gonzaga +gonzales +gonzalez +gonzalo +gonzo +goo +goober +gooch +good +goodall +goodbye +goodbyes +goode +goodell +gooden +goodfellas +goodie +goodies +gooding +goodly +goodman +goodness +goodnight +goodrich +goods +goodson +goodspeed +goodwill +goodwin +goodwyn +goody +goodyear +gooey +goof +goofball +goofed +goofiness +goofing +goofs +goofy +googly +gook +goon +goonies +goons +goop +goos +goose +goosen +goosey +gopal +gopher +gophers +gora +goran +gorbachev +gord +gorda +gorden +gordian +gordon +gore +gored +goree +gores +gorge +gorgeous +gorges +gorgon +gorgonzola +gorham +gorilla +gorillas +goring +gorki +gorky +gorman +gormley +gorney +gorton +gory +gos +gosh +goshen +gosling +gosnell +gospel +gospels +goss +gossage +gossamer +gossard +gosselin +gossett +gossip +gossiping +gossips +gossipy +got +gotch +gotcha +goth +gotham +gothard +gothenburg +gothic +goto +gott +gotta +gotten +gottesman +gottfried +gotti +gottlieb +gottschalk +gouda +gouge +gouged +gouges +gough +gouging +goulash +gould +goulet +gourd +gourds +gourmand +gourmands +gourmet +gourmets +gout +gov +govan +gove +goverment +govern +governance +governed +governement +governess +governing +government +governmental +governmentally +governments +governor +governors +governorship +governorships +governs +govt +gowan +gowans +gowdy +gowen +gown +gowns +goy +goya +goyim +gp +gr +grab +grabbed +grabber +grabbers +grabbing +graben +graber +grable +grabs +grace +graced +graceful +gracefully +graceland +graceless +graces +gracia +gracias +gracie +gracing +gracious +graciously +graciousness +grackle +grad +gradation +gradations +grade +graded +grader +graders +grades +gradient +gradients +grading +grads +gradual +gradualism +gradualist +gradually +graduate +graduated +graduates +graduating +graduation +graduations +grady +graef +graeme +graf +graff +graffiti +graffito +graft +grafted +grafting +grafton +grafts +graham +grahams +grail +grain +grained +grainger +grains +grainy +gram +gramaphone +gramercy +gramm +gramma +grammar +grammarian +grammatical +grammatically +grammer +grammy +grammys +gramophone +grampa +gramps +grams +gran +grana +granada +grand +granda +grandad +grandchild +grandchildren +granddad +granddaddy +granddaughter +granddaughters +grande +grander +grandest +grandeur +grandfather +grandfathered +grandfatherly +grandfathers +grandiloquent +grandiose +grandiosity +grandkids +grandly +grandma +grandmas +grandmaster +grandmother +grandmotherly +grandmothers +grandpa +grandparent +grandparents +grands +grandson +grandsons +grandstand +grandstanding +grange +granger +granite +granny +grano +granola +grant +granted +grantee +grantees +grantham +granting +grantor +grants +granular +granularity +granulation +granule +granules +granulocyte +granville +grape +grapefruit +grapefruits +grapes +grapevine +grapevines +graph +graphed +graphic +graphical +graphically +graphics +graphing +graphite +graphs +grappa +grapple +grappled +grappler +grapples +grappling +gras +grasp +grasped +grasping +grasps +grass +grasser +grasses +grasshopper +grasshoppers +grassland +grasslands +grassley +grasso +grassroots +grassy +grata +grate +grated +grateful +gratefully +grater +grates +gratification +gratified +gratify +gratifying +grating +gratings +gratis +gratitude +gratton +gratuities +gratuitous +gratuitously +gratuity +grau +grauman +grave +gravediggers +gravel +gravelly +gravely +graven +graver +graves +graveside +gravest +gravestone +gravestones +graveyard +graveyards +gravies +gravis +gravitas +gravitate +gravitated +gravitates +gravitating +gravitation +gravitational +gravitationally +gravity +gravy +gray +graydon +grayer +graying +grayish +grayling +grays +grayscale +grayson +graze +grazed +grazer +graziani +graziano +grazie +grazier +grazing +grease +greased +greaser +greases +greasing +greasy +great +greater +greatest +greatful +greatly +greatness +greats +greaves +grebe +grecian +greco +greece +greed +greedily +greedy +greek +greeks +greeley +green +greenback +greenbacks +greenbaum +greenbelt +greenberg +greenblatt +greenbrier +greenburg +greene +greener +greenery +greenest +greenfield +greenhalgh +greenhill +greenhouse +greenhouses +greening +greenish +greenland +greenlaw +greenleaf +greenlee +greenly +greenman +greenness +greenpeace +greens +greensboro +greenslade +greenspan +greenstein +greenstone +greenville +greenwald +greenway +greenwell +greenwich +greenwood +greer +greet +greeted +greeter +greeters +greeting +greetings +greets +greg +gregarious +gregg +gregoire +gregor +gregorian +gregorio +gregory +gregson +greig +gremlin +gremlins +grenada +grenade +grenades +grenadines +grenier +grenoble +gresham +greta +gretchen +grete +gretel +gretna +greve +grew +grey +greyhound +greyhounds +greys +greystone +gribble +grice +grid +gridded +griddle +gridiron +gridley +gridlock +grids +grief +grier +grievance +grievances +grieve +grieved +grieves +grieving +grievous +grievously +griff +griffen +griffey +griffin +griffins +griffith +griffiths +griffon +grifters +griggs +grigsby +grill +grille +grilled +grilles +grilling +grillo +grills +grim +grimace +grimaced +grimaces +grimacing +grimaldi +grime +grimes +grimly +grimm +grimmer +grimmest +grimness +grimy +grin +grinch +grind +grinder +grinders +grinding +grindle +grinds +grindstone +gringo +gringos +grinned +grinnell +grinning +grins +grip +gripe +griped +gripes +griping +gripped +gripper +gripping +grips +gris +grise +griselda +grisham +grisly +grissom +grist +gristle +griswold +grit +grits +gritted +gritting +gritty +grizzle +grizzled +grizzlies +grizzly +gro +groan +groaned +groaning +groans +groat +groce +grocer +groceries +grocers +grocery +grog +groggy +groh +groin +groins +groom +groomed +groomer +groomers +grooming +grooms +groove +groover +grooves +groovy +grope +groped +gropes +groping +gros +grosbeak +grosjean +gross +grosse +grossed +grosser +grosses +grossi +grossing +grossly +grossman +grossmann +grosso +grosvenor +grosz +grote +grotesque +grotesquely +groton +grotto +grouch +groucho +grouchy +ground +groundbreaking +grounded +grounder +grounders +groundhog +grounding +groundless +groundlings +groundnut +grounds +groundswell +groundwater +groundwork +group +groupe +grouped +grouper +groupers +groupie +groupies +grouping +groupings +groups +grouse +grousing +grout +grove +grovel +groveling +grover +groves +grow +grower +growers +growing +growl +growled +growling +growls +growly +grown +grownup +grownups +grows +growth +growths +grozny +grs +grub +grubb +grubbing +grubby +gruber +grubs +grudge +grudges +grudging +grudgingly +gruel +grueling +gruelling +gruen +gruesome +gruff +grumble +grumbled +grumbles +grumbling +grumman +grump +grumpiness +grumpy +grunberg +grundy +gruner +grunewald +grungy +grunt +grunted +grunting +grunts +grunwald +grupo +gruppo +gruss +gryphon +gs +gt +gu +guacamole +guadalajara +guadalcanal +guadalupe +guadarrama +guadeloupe +guage +guam +guanaco +guangdong +guangzhou +guanine +guano +guantanamo +guarana +guarani +guarantee +guaranteed +guaranteeing +guarantees +guarantor +guarantors +guaranty +guard +guarded +guardia +guardian +guardians +guardianship +guarding +guardrail +guardrails +guards +guardsman +guardsmen +guarino +guarneri +guatemala +guatemalan +guatemalans +guava +guavas +gubernatorial +gucci +gude +gudgeon +gue +guelph +guenter +guenther +guerilla +guerillas +guerin +guernsey +guerra +guerre +guerrero +guerrilla +guerrillas +guess +guessed +guesser +guessers +guesses +guessing +guesstimate +guesswork +guest +guested +guesthouse +guesthouses +guesting +guests +guevara +guff +guffaw +guffawing +guffaws +guggenheim +guglielmo +guiana +guidance +guide +guidebook +guidebooks +guided +guideline +guidelines +guidepost +guideposts +guides +guiding +guido +guild +guilder +guilders +guildhall +guilds +guile +guileless +guiles +guilford +guilfoyle +guillaume +guillen +guillermo +guillot +guillotine +guilt +guiltless +guilty +guinea +guinean +guineas +guiness +guinness +guise +guises +guitar +guitarist +guitarists +guitars +gujarat +gujarati +gul +gula +gulag +gulbuddin +gulch +gulf +gulfstream +gull +gullah +gullet +gulley +gullibility +gullible +gullies +gullion +gulliver +gullo +gulls +gully +gulp +gulped +gulping +gulps +gum +gumball +gumbo +gumby +gummed +gummy +gump +gumption +gums +gumshoe +gun +gunboat +gunboats +gund +gundersen +gunderson +gundy +gunfight +gunfighter +gunfighters +gunfights +gunfire +gung +gunk +gunman +gunmen +gunn +gunnar +gunned +gunner +gunners +gunnery +gunning +gunnison +gunny +gunpoint +gunpowder +guns +gunship +gunships +gunshot +gunshots +gunslinger +gunslingers +gunter +gunther +guppy +gupta +gurgle +gurgling +gurion +gurley +gurney +guru +gurus +gus +gush +gushed +gusher +gushes +gushing +guss +gussie +gussied +gust +gustafson +gustav +gustave +gustavo +gusted +gusting +gusto +gusts +gusty +gut +gutenberg +guterman +guth +guthrie +gutierrez +gutless +gutman +gutmann +guts +gutsy +gutted +gutter +gutters +gutting +guttman +guttural +guy +guyana +guys +guyton +guzman +guzzle +guzzler +guzzlers +guzzles +guzzling +gwen +gwendolyn +gwinnett +gwyn +gwynn +gwynne +gym +gymnasium +gymnasiums +gymnast +gymnastic +gymnastics +gymnasts +gyms +gyn +gynecologic +gynecologist +gynecologists +gynecology +gyp +gypsies +gypsum +gypsy +gyrate +gyrating +gyration +gyrations +gyro +gyros +gyroscope +gyroscopes +gyroscopic +ha +haag +haan +haar +haas +haase +hab +habeas +haber +haberdashery +habib +habit +habitable +habitat +habitation +habitats +habits +habitual +habitually +habituated +habsburg +hachette +hacienda +hack +hackberry +hacked +hacker +hackers +hackett +hacking +hackle +hackles +hackman +hackney +hackneyed +hacks +hacksaw +hackworth +had +hadas +haddad +hadden +haddock +hade +haden +hades +hadfield +hadith +hadley +hadrian +hae +hafez +haff +hafner +haft +hafta +hag +hagan +hagans +hagar +hagedorn +hageman +hagen +hager +hagerstown +hagerty +haggadah +haggai +haggard +hagge +haggle +haggled +haggling +hagia +hagiography +hagman +hagopian +hags +hague +hah +haha +hahn +haifa +haig +haight +haik +haiku +hail +haile +hailed +hailes +hailey +hailing +hails +hailstorm +haim +hain +hainan +haines +hair +hairbrush +haircut +haircuts +hairdo +hairdos +hairdresser +hairdressers +hairdressing +hairdryer +haire +haired +hairless +hairline +hairpiece +hairpin +hairpins +hairs +hairspray +hairstyle +hairstylist +hairy +hait +haiti +haitian +haitians +haji +hajj +hakan +hake +hakim +hakon +hal +hala +halas +halberstam +halbert +halcyon +haldeman +hale +halen +hales +haley +half +halfback +halfhearted +halftime +halftone +halfway +halibut +halide +halifax +halite +hall +halladay +hallam +halle +hallelujah +haller +hallet +halley +halliburton +halliday +hallie +halling +hallmark +hallmarks +hallo +hallow +hallowed +halloween +halls +hallucinate +hallucinating +hallucination +hallucinations +hallucinatory +hallucinogenic +hallway +hallways +halm +halo +halogen +haloperidol +halos +halperin +halpern +halpin +halsey +halt +halted +halter +halters +halting +haltingly +halts +halve +halved +halves +halving +ham +hama +hamad +hamada +haman +hamas +hambone +hambrecht +hamburg +hamburger +hamburgers +hamdan +hamed +hamel +hamelin +hamer +hames +hamid +hamill +hamilton +hamish +hamlet +hamlets +hamlin +hamm +hamman +hammer +hammered +hammerhead +hammering +hammerlock +hammerman +hammers +hammerschmidt +hammersmith +hammerstein +hammett +hammill +hammock +hammocks +hammond +hammonds +hammurabi +hamon +hamp +hamper +hampered +hampering +hampers +hampshire +hampstead +hampton +hamptons +hams +hamster +hamsters +hamstring +hamstrings +hamstrung +hamza +hamzah +han +hana +hanan +hanauer +hanbury +hance +hancock +hand +handbag +handbags +handball +handbills +handbook +handbooks +handcrafted +handcrafts +handcuff +handcuffed +handcuffs +handed +handedly +handedness +handel +hander +handers +handful +handfuls +handgun +handguns +handheld +handhold +handicap +handicapped +handicapper +handicappers +handicapping +handicaps +handicraft +handicrafts +handier +handily +handing +handiwork +handkerchief +handkerchiefs +handle +handlebar +handlebars +handled +handler +handlers +handles +handley +handling +handmade +handout +handouts +handover +handpick +handpicked +handrail +hands +handsaw +handset +handsets +handshake +handshakes +handshaking +handsome +handsomely +handstand +handstands +handwork +handwoven +handwriting +handwritten +handy +handyman +handymen +hane +hanes +haney +hanford +hang +hangar +hangars +hangdog +hanged +hanger +hangers +hanging +hangings +hangman +hangnail +hangout +hangouts +hangover +hangovers +hangs +hangup +hangups +hani +hank +hanker +hankering +hankinson +hanks +hanky +hanley +hanlon +hann +hanna +hannah +hannan +hannibal +hannigan +hannon +hannover +hanoi +hanover +hanrahan +hans +hansa +hansard +hanse +hanseatic +hansel +hansell +hansen +hanshin +hanson +hanssen +hants +hanukkah +hao +hap +haphazard +haphazardly +hapless +happ +happen +happened +happening +happenings +happens +happenstance +happier +happiest +happily +happiness +happy +haps +hapsburg +hara +harada +harald +harangue +harangued +harangues +haranguing +harare +harass +harassed +harasser +harassers +harasses +harassing +harassment +harb +harbaugh +harbin +harbinger +harbingers +harbor +harboring +harbors +harborside +harbour +harcourt +hard +hardback +hardball +hardbound +hardcopy +hardcore +hardcover +hardcovers +hardee +harden +hardened +hardening +hardens +harder +hardest +hardesty +hardhat +hardhats +hardheaded +hardie +hardier +hardin +hardiness +harding +hardline +hardliner +hardliners +hardly +hardness +hards +hardship +hardships +hardt +hardtop +hardware +hardwick +hardwired +hardwood +hardwoods +hardworking +hardy +hare +harebrained +harem +hares +harewood +hargrave +hargreaves +hargrove +hari +haring +hariri +harish +hark +harked +harken +harker +harkin +harking +harkins +harkness +harks +harlan +harland +harlem +harlequin +harley +harleys +harlingen +harlot +harlow +harm +harman +harmed +harmel +harmer +harmful +harming +harmless +harmlessly +harmon +harmonic +harmonica +harmonically +harmonics +harmonies +harmonious +harmoniously +harmonium +harmonization +harmonize +harmonized +harmonizes +harmonizing +harmony +harms +harn +harness +harnessed +harnesses +harnessing +harold +harp +harpe +harped +harper +harpercollins +harpers +harpies +harping +harpists +harpo +harpoon +harpoons +harps +harr +harrassed +harrassment +harrell +harrelson +harrer +harried +harrier +harries +harriet +harrigan +harriman +harrington +harris +harrisburg +harrison +harrod +harrods +harron +harrow +harrower +harrowing +harry +harsh +harsher +harshest +harshly +harshness +hart +harter +hartford +hartigan +harting +hartley +hartman +hartmann +hartnett +harts +hartshorn +hartson +hartt +hartung +hartwell +hartwig +harty +hartz +harvard +harvest +harvested +harvester +harvesters +harvesting +harvests +harvey +harwell +harwood +has +hasan +hasbro +hase +hasek +hash +hashed +hashem +hashemi +hashim +hashimoto +hashing +hashish +hasidic +hasidim +haskell +haskin +haskins +haslett +hass +hassan +hassel +hassell +hassett +hassle +hassled +hassles +hassling +hast +hasta +haste +hasten +hastened +hastening +hastens +hastie +hastily +hastings +hasty +hat +hatch +hatchback +hatched +hatcher +hatcheries +hatchery +hatches +hatchet +hatchets +hatching +hate +hated +hateful +hater +haters +hates +hatfield +hath +hathaway +hating +hatley +hatred +hatreds +hats +hatter +hattersley +hattie +hatton +hattori +hau +hauer +haugen +haugh +haught +haughtily +haughton +haughty +haul +hauled +hauler +haulers +hauling +hauls +haunches +haunt +haunted +haunting +hauntingly +haunts +hauppauge +haupt +haus +hausa +hause +hauser +hausmann +haut +haute +hauteur +hav +hava +havana +have +havel +havelock +haven +havens +havent +haver +haverford +haves +haviland +havilland +having +havoc +haw +hawaii +hawaiian +hawaiians +hawes +hawing +hawk +hawke +hawked +hawken +hawker +hawkers +hawkes +hawkeye +hawking +hawkins +hawkish +hawks +hawley +hawn +haworth +haws +hawser +hawthorn +hawthorne +hay +haya +hayashi +haycock +hayden +haydn +haye +hayek +hayes +hayfield +hayfields +hayley +haymaker +hayman +haymarket +haymon +hayne +haynes +hayrides +hays +haystack +hayward +haywire +haywood +hayworth +hazan +hazard +hazardous +hazards +haze +hazel +hazell +hazelnut +hazelnuts +hazelton +hazelwood +hazen +hazing +hazleton +hazlewood +hazy +hazzard +hb +hcl +hd +he +head +headache +headaches +headband +headbands +headbanger +headboard +headcount +headdress +headdresses +headed +header +headers +headfirst +headgear +headhunter +headhunters +heading +headings +headlamps +headland +headlands +headless +headlight +headlights +headline +headlined +headliner +headliners +headlines +headlining +headlong +headman +headmaster +headmistress +headphone +headphones +headpiece +headquarter +headquartered +headquarters +headrest +headrests +headroom +heads +headscarf +headset +headsets +headship +headstart +headstone +headstones +headstrong +headwall +headwaters +headway +headwind +heady +heal +heald +healed +healer +healers +healey +healing +heals +health +healthcare +healthful +healthier +healthiest +healthy +healy +heaney +heap +heaped +heaping +heaps +hear +heard +hearers +hearing +hearings +hearken +hearn +hearne +hears +hearsay +hearse +hearst +heart +heartache +heartbeat +heartbeats +heartbreak +heartbreaker +heartbreaking +heartbreakingly +heartbroken +heartburn +hearted +heartedly +hearten +heartened +heartening +heartfelt +hearth +hearths +heartily +heartland +heartlands +heartless +heartlessly +hearts +heartsick +heartstrings +heartthrob +heartwarming +hearty +heat +heated +heatedly +heater +heaters +heath +heathcliff +heathen +heathens +heather +heathers +heathrow +heating +heaton +heats +heave +heaved +heaven +heavenly +heavens +heaves +heavier +heavies +heaviest +heavily +heaviness +heaving +heavy +heavyweight +heavyweights +hebe +heber +hebert +hebrew +hebrews +hebron +hecht +heck +heckel +hecker +heckle +heckled +heckler +hecklers +heckling +hectare +hectares +hectic +hector +hectoring +hed +hedda +hedge +hedged +hedgehog +hedgehogs +hedger +hedgers +hedges +hedging +hedley +hedonic +hedonism +hedonistic +hedrick +hedwig +hedy +hee +heed +heeded +heeding +heeds +heel +heeled +heels +heep +heer +heffernan +heffner +heflin +hefner +heft +heftier +hefty +hegarty +hegelian +hegemonic +hegemony +heh +hei +heidelberg +heidelberger +heiden +heidi +heifer +heifers +heifetz +height +heighten +heightened +heightening +heightens +heights +heil +heilman +heilmann +heim +heimann +hein +heine +heinemann +heinlein +heino +heinous +heinrich +heinrichs +heinz +heinze +heir +heirarchy +heiress +heirloom +heirlooms +heirs +heiser +heisman +heist +heitz +held +helder +helen +helena +helene +helens +helga +helical +helicon +helicopter +helicopters +helios +heliotrope +helipad +helium +helix +hell +helle +hellenic +hellenism +hellenistic +heller +hellfire +hellhole +hellish +hellman +hellmann +hellmuth +hello +hells +helluva +helm +helman +helmer +helmet +helmeted +helmets +helms +helmsley +helmsman +helmut +help +helped +helper +helpers +helpful +helpfully +helping +helpings +helpless +helplessly +helplessness +helps +helsinki +helter +helton +helvetica +hem +hematite +hematocrit +hematology +hemel +hemingway +hemisphere +hemispheres +hemispheric +hemline +hemlines +hemlock +hemmed +hemming +hemmings +hemoglobin +hemolytic +hemophilia +hemorrhage +hemorrhagic +hemorrhaging +hemorrhoids +hemp +hemphill +hempstead +hems +hen +hence +henceforth +henchman +henchmen +henderson +hendrick +hendricks +hendrickson +hendrie +hendrik +hendrix +hendry +hendryx +hendy +heng +henhouse +henley +henn +henna +hennessey +hennessy +hennigan +henning +hennings +henny +henpecked +henri +henricks +henrietta +henriette +henrik +henrique +henriques +henry +hens +hensel +hensler +hensley +henson +hentschel +henwood +hep +heparin +hepatic +hepatitis +hepburn +her +hera +herald +heralded +heraldic +heralding +heraldry +heralds +herb +herbaceous +herbal +herbalist +herbalists +herbarium +herber +herbert +herbicide +herbicides +herbie +herbivore +herbs +herbst +herc +herculean +hercules +herd +herded +herder +herders +herding +herdman +herds +herdsmen +here +hereabouts +hereafter +hereby +heredia +hereditary +heredity +hereford +herein +hereinafter +heresies +heresy +heretic +heretical +heretics +hereto +heretofore +herewith +hering +heritable +heritage +heritages +herlihy +herling +herm +herman +hermann +hermans +hermaphrodite +hermaphroditic +hermes +hermetic +hermetically +hermit +hermitage +hermits +hermon +hern +hernan +hernandez +hernando +herndon +herne +hernia +hernias +herniated +hero +herod +heroes +heroic +heroically +heroics +heroin +heroine +heroines +heroism +herold +heron +herons +heros +herpes +herr +herrera +herrick +herrin +herring +herringbone +herrings +herrington +herrmann +herrod +herron +hers +herschel +herself +hersh +hershey +hershiser +herta +hertfordshire +hertz +hertzberg +herve +hervey +herz +herzegovina +herzlinger +herzog +hes +hesitance +hesitancy +hesitant +hesitantly +hesitate +hesitated +hesitates +hesitating +hesitation +hesitations +hesketh +hess +hesse +hessian +hester +hestia +heston +het +hetero +heterodox +heterogeneity +heterogeneous +heterogenous +heterosexual +heterosexuality +heterosexuals +hetherington +hettie +heuer +heuristic +heuvel +hew +hewer +hewett +hewing +hewitt +hewlett +hewn +hews +hex +hexafluoride +hexagon +hexagonal +hexavalent +hexed +hey +heyday +heyer +heyman +heywood +hezbollah +hezekiah +hi +hialeah +hiatal +hiatt +hiatus +hiawatha +hibbard +hibbert +hibernate +hibernating +hibernation +hibernia +hibiscus +hiccup +hiccups +hick +hickey +hickman +hickok +hickory +hickox +hicks +hickson +hicksville +hid +hidalgo +hidden +hide +hideaway +hideaways +hidebound +hideo +hideous +hideously +hideout +hideouts +hides +hiding +hiebert +hier +hierarchical +hierarchies +hierarchy +hieroglyphic +hieroglyphics +hiett +higashi +higdon +higgens +higginbotham +higgins +higginson +high +highbrow +higher +highest +highflying +highland +highlander +highlanders +highlands +highlight +highlighted +highlighting +highlights +highline +highly +highness +highrise +highs +highschool +highsmith +highspeed +hight +hightail +hightech +hightower +highway +highwayman +highways +higley +hijack +hijacked +hijacker +hijackers +hijacking +hijackings +hijacks +hijinks +hike +hiked +hiker +hikers +hikes +hiking +hilarious +hilariously +hilarity +hilary +hilbert +hilda +hilde +hildebrand +hildegard +hile +hilfiger +hill +hillard +hillary +hillbillies +hillbilly +hillcrest +hillel +hillenbrand +hiller +hilliard +hillier +hillis +hillman +hillock +hills +hillsboro +hillsborough +hillside +hillsides +hilltop +hilltops +hilly +hilo +hilt +hilton +hilts +him +himalaya +himalayan +himalayas +himmel +himmelstein +himmler +himself +hin +hinch +hinckley +hind +hinder +hindered +hindering +hinders +hindi +hindquarters +hindrance +hindrances +hinds +hindsight +hindu +hinduism +hindus +hindustan +hine +hines +hiney +hing +hinge +hinged +hinges +hingham +hinging +hink +hinkle +hinkley +hinojosa +hinson +hint +hinted +hinterland +hinterlands +hinting +hinton +hints +hip +hipbone +hipness +hipp +hipped +hipper +hippest +hippie +hippies +hippo +hippocrates +hippocratic +hippodrome +hippolytus +hippopotamus +hippos +hippy +hips +hipster +hipsters +hiram +hirano +hirata +hire +hired +hires +hiring +hirings +hiro +hiroshi +hiroshima +hiroyuki +hirsch +hirschman +hirst +hirsute +hirt +his +hisham +hislop +hispanic +hispanics +hispaniola +hispano +hiss +hissed +hisself +hisses +hissing +hissy +hist +histamine +histamines +histogram +histology +historial +historian +historians +historic +historical +historically +historicity +histories +historiography +history +histrionic +histrionics +hit +hitachi +hitch +hitchcock +hitched +hitchens +hitches +hitchhike +hitchhiker +hitchhikers +hitchhiking +hitching +hitchings +hite +hither +hitherto +hitler +hitlerism +hitman +hits +hitt +hitter +hitters +hitting +hittite +hitz +hive +hives +hiya +hizbollah +hm +hmmm +hmmmm +ho +hoagie +hoagland +hoagy +hoak +hoang +hoar +hoard +hoarded +hoarding +hoardings +hoards +hoarse +hoary +hoax +hoaxes +hob +hoban +hobart +hobbes +hobbie +hobbies +hobbit +hobble +hobbled +hobbles +hobbling +hobbs +hobby +hobbyist +hobbyists +hobgoblin +hobie +hobnobbing +hobo +hoboken +hobson +hoc +hoch +hochman +hock +hocked +hockett +hockey +hocking +hocus +hod +hodge +hodgepodge +hodges +hodgkin +hodgson +hodson +hoe +hoechst +hoedown +hoekstra +hoes +hoey +hof +hofer +hoff +hoffa +hoffer +hoffman +hoffmann +hoffmeister +hofmann +hofstra +hog +hogan +hogans +hogarth +hoge +hogen +hogg +hogged +hogging +hogs +hogtied +hogue +hogwash +hoh +hohn +hoi +hoist +hoisted +hoisting +hoists +hoke +hokey +hokkaido +hokum +hola +holbrook +holbrooke +holcomb +hold +holden +holder +holders +holding +holdings +holdout +holdouts +holdover +holdovers +holds +holdsworth +holdup +holdups +hole +holed +holes +holey +holger +holiday +holidays +holier +holies +holiest +holiness +holing +holistic +holistically +holl +holland +hollandaise +hollander +hollar +holler +hollered +hollering +hollers +holley +holli +holliday +hollie +hollies +hollings +hollingsworth +hollins +hollis +hollister +hollow +holloway +hollowed +hollowell +hollowing +hollowness +hollows +holly +hollyhock +hollyhocks +hollywood +holm +holman +holmberg +holmdel +holmen +holmes +holmgren +holmium +holocaust +hologram +holograms +holographic +holst +holstein +holster +holstered +holsters +holt +holter +holtzman +holy +holyoke +holzer +hom +homa +homage +homages +hombre +hombres +homburg +home +homebody +homebound +homeboy +homeboys +homebuilder +homebuilders +homebuilding +homebuyers +homecoming +homecomings +homed +homegrown +homeland +homelands +homeless +homelessness +homelike +homely +homemade +homemaker +homemakers +homemaking +homeopathic +homeopathy +homeowner +homeowners +homeownership +homer +homeric +homers +homerun +homes +homesick +homesickness +homespun +homestead +homesteaders +homestretch +hometown +homeward +homewood +homework +homey +homicidal +homicide +homicides +homilies +homily +hominem +homing +hominid +homme +hommes +homo +homoerotic +homogeneity +homogeneous +homogenization +homogenize +homogenized +homogenous +homologous +homophobia +homophobic +homos +homosexual +homosexuality +homosexuals +hon +honan +honcho +honchos +honda +hondas +hondo +honduran +honduras +hone +honed +honer +hones +honest +honestly +honesty +honey +honeybee +honeybees +honeycomb +honeydew +honeyed +honeymoon +honeymooners +honeymoons +honeypot +honeys +honeysuckle +honeywell +hong +hongkong +honig +honing +honk +honked +honking +honky +honolulu +honor +honorable +honorably +honoraria +honorarium +honorariums +honorary +honore +honored +honoree +honorees +honorific +honoring +honors +honour +honourable +honouring +honours +honshu +hoo +hooch +hoochie +hood +hooded +hoodlum +hoodlums +hoodoo +hoods +hoodwink +hooey +hoof +hoofed +hoofing +hoofs +hoog +hook +hookah +hooked +hooker +hookers +hookey +hooking +hooks +hookup +hookups +hookworm +hooky +hooley +hooligan +hooliganism +hooligans +hoon +hoop +hooper +hoopla +hoops +hooray +hoos +hoose +hoosier +hoosiers +hoot +hooted +hooter +hooters +hoots +hoover +hooves +hop +hope +hoped +hopeful +hopefully +hopefulness +hopefuls +hopeless +hopelessly +hopelessness +hopes +hopewell +hopf +hopi +hoping +hopkins +hopkinton +hopp +hoppe +hopped +hopper +hopping +hoppy +hops +hopscotch +hopwood +hor +hora +horace +horacio +horan +horatio +horatius +horde +hordes +horgan +hori +horizon +horizons +horizontal +horizontally +hormel +hormonal +hormone +hormones +horn +hornbill +hornbills +hornblower +hornby +horne +horned +horner +hornet +hornets +horning +horns +hornsby +hornung +horny +horoscope +horoscopes +horovitz +horowitz +horrendous +horrendously +horrible +horribly +horrid +horrific +horrifically +horrified +horrifies +horrify +horrifying +horrigan +horror +horrors +horry +hors +horse +horseback +horseflesh +horsehair +horsehead +horseless +horseman +horsemanship +horsemen +horseplay +horsepower +horseradish +horses +horseshoe +horseshoes +horsetail +horsey +horsham +horsing +horsley +horst +hort +horticultural +horticulture +horticulturist +horton +horus +horvath +horwich +horwitz +hose +hosea +hosed +hoses +hosiery +hosing +hoskins +hosni +hosp +hospice +hospices +hospitable +hospital +hospitality +hospitalization +hospitalizations +hospitalized +hospitals +hoss +host +hostage +hostages +hosted +hostel +hostels +hostess +hostesses +hostessing +hostile +hostilities +hostility +hosting +hosts +hot +hotbed +hotbeds +hotcakes +hotchkiss +hotdog +hotdogs +hotel +hotelier +hoteliers +hotels +hoth +hotheaded +hothouse +hotline +hotlines +hotly +hotness +hots +hotshot +hotshots +hottentot +hotter +hottest +hou +houck +houde +houdini +hough +houghton +houle +houlihan +hound +hounded +hounding +hounds +hour +hourglass +hourlong +hourly +hours +house +houseboat +housecleaning +housed +houseful +houseguest +household +householder +householders +households +housekeeper +housekeepers +housekeeping +houseman +housemate +houser +houses +housewares +housewarming +housewife +housewives +housework +housing +housings +houston +hout +houten +hove +hovel +hovels +hover +hovercraft +hovered +hovering +hovers +hovey +how +howard +howarth +howdy +howe +howell +howells +howes +however +howie +howitzer +howitzers +howl +howland +howled +howler +howling +howls +hows +hoy +hoya +hoyer +hoyle +hoyt +hp +hr +hrs +hs +hsia +hsieh +hsu +ht +hts +hu +hua +huan +huang +hub +hubbard +hubbell +hubble +hubbub +hubby +hubcap +hubcaps +huber +hubert +hubley +hubris +hubs +huck +huckleberry +huckster +hucksters +hud +huddle +huddled +huddles +huddleston +huddling +hudson +hue +hued +huerta +hues +huey +huff +huffed +huffer +huffing +huffman +huffs +huffy +hug +huge +hugely +hugest +hugged +hugger +hugging +huggins +hugh +hughes +hugo +hugs +huguenot +huguenots +huh +hui +huie +huizenga +hula +hulbert +hulett +hulk +hulking +hulks +hull +hullabaloo +hulled +hulls +hulme +hulse +hultman +hum +human +humana +humane +humanely +humanism +humanist +humanistic +humanists +humanitarian +humanitarians +humanities +humanity +humanize +humanized +humanizing +humankind +humanlike +humanly +humanness +humanoids +humans +humber +humberside +humbert +humberto +humble +humbled +humbleness +humbler +humbles +humblest +humbling +humbly +humboldt +humbug +humdinger +humdrum +hume +humid +humidifier +humidifiers +humidity +humidor +humiliate +humiliated +humiliating +humiliation +humiliations +humility +hummel +hummer +humming +hummingbird +hummingbirds +hummus +humongous +humor +humoral +humored +humorist +humorists +humorless +humorous +humorously +humour +hump +humpback +humped +humph +humphrey +humphreys +humphries +humping +humps +humpty +hums +humus +hun +hunan +hunch +hunchback +hunchbacked +hunched +hunches +hund +hundley +hundred +hundredfold +hundreds +hundredth +hundredths +hundredweight +hundt +hung +hungarian +hungarians +hungary +hunger +hungered +hungerford +hungering +hungers +hungrier +hungrily +hungry +hunk +hunker +hunkered +hunkering +hunks +hunky +huns +hunt +hunted +hunter +hunters +hunting +huntingdon +huntington +huntley +huntress +hunts +huntsman +huntsville +hur +hurd +hurdle +hurdler +hurdles +hurdling +hurl +hurled +hurlers +hurley +hurling +hurls +hurly +huron +hurrah +hurray +hurricane +hurricanes +hurried +hurriedly +hurries +hurry +hurrying +hurst +hurston +hurt +hurtful +hurting +hurtle +hurtles +hurtling +hurts +hurwitz +husain +husband +husbandry +husbands +huse +hush +hushed +husk +husker +huskers +husks +husky +huson +huss +hussain +hussein +husseini +hussy +hustings +hustle +hustled +hustler +hustlers +hustles +hustling +huston +hut +hutch +hutcheson +hutchins +hutchinson +hutchison +huts +hutson +hutt +hutter +hutton +hutu +huxley +huxtable +huynh +hw +hwa +hwan +hwang +hy +hyacinth +hyacinths +hyannis +hyatt +hybrid +hybridization +hybridize +hybrids +hyde +hyder +hyderabad +hydra +hydrangea +hydrant +hydrants +hydrate +hydrated +hydration +hydraulic +hydraulics +hydrazine +hydride +hydro +hydrocarbon +hydrocarbons +hydrochloric +hydrodynamic +hydroelectric +hydroelectricity +hydrofluoric +hydrofoil +hydrogen +hydrogenated +hydrogenation +hydrographic +hydrological +hydrolyzed +hydrophilic +hydrophone +hydroplane +hydroponic +hydropower +hydrostatic +hydrotherapy +hydrothermal +hydroxide +hydroxyl +hyena +hyenas +hygiene +hygienist +hygienists +hyman +hymen +hymes +hymn +hymnal +hymnals +hymns +hynes +hyogo +hype +hyped +hyper +hyperactive +hyperactivity +hyperbole +hyperbolic +hypercritical +hypercube +hyperinflation +hyperion +hyperkinetic +hypermarket +hypermarkets +hypersensitive +hypersensitivity +hypersonic +hyperspace +hypertension +hypertensive +hypertext +hyperthyroidism +hypertrophic +hypes +hyphae +hyphen +hyphenate +hyphenated +hyping +hypnosis +hypnotic +hypnotist +hypnotize +hypnotized +hypnotizing +hypo +hypochondria +hypochondriac +hypocrisy +hypocrite +hypocrites +hypocritical +hypodermic +hypoglycemia +hypoglycemic +hypothalamus +hypothermia +hypotheses +hypothesis +hypothesize +hypothesized +hypothesizing +hypothetical +hypothetically +hypotheticals +hypothyroidism +hypoxia +hyssop +hysterectomies +hysterectomy +hysteria +hysteric +hysterical +hysterically +hysterics +hyun +hyundai +i +ia +iacocca +iago +iain +ian +ib +ibaraki +ibbotson +iberia +iberian +ibex +ibid +ibis +ibn +ibrahim +ibuprofen +ic +icahn +icarus +ice +iceberg +icebergs +icebox +icebreaker +icebreakers +iced +iceland +icelander +icelanders +icelandic +iceman +ices +ich +ichiro +icicle +icicles +icily +icing +icky +icon +iconic +iconoclasm +iconoclast +iconoclastic +iconography +icons +icy +id +ida +idaho +ide +idea +ideal +idealism +idealist +idealistic +idealists +idealize +idealized +ideally +ideals +ideas +iden +ident +identical +identically +identifiable +identification +identifications +identified +identifier +identifiers +identifies +identify +identifying +identikit +identities +identity +ideo +ideological +ideologically +ideologies +ideologist +ideologue +ideologues +ideology +ides +idiocy +idiom +idiomatic +idioms +idiosyncrasies +idiosyncrasy +idiosyncratic +idiot +idiotic +idiots +idle +idled +idleness +idler +idling +idly +ido +idol +idolaters +idolatrous +idolatry +idolize +idolized +idols +ids +idyll +idyllic +ie +ieee +if +ifas +ife +iffy +ifs +iggy +iglesia +iglesias +igloo +igloos +ignacio +ignatius +igneous +ignite +ignited +ignites +igniting +ignition +ignitions +ignoble +ignominious +ignominy +ignoramus +ignorance +ignorant +ignore +ignored +ignores +ignoring +igor +iguana +iguanas +ihs +ii +iie +iii +ijaz +ik +ike +ikea +il +ila +ilan +ilbo +ile +ileana +ilene +iles +ilia +iliad +ilk +ilka +ill +illegal +illegalities +illegality +illegally +illegals +illegible +illegitimacy +illegitimate +illicit +illicitly +illinois +illiquid +illiquidity +illiteracy +illiterate +illiterates +illness +illnesses +illogic +illogical +ills +illuminate +illuminated +illuminates +illuminati +illuminating +illumination +illuminations +illuminator +illumine +illusion +illusionary +illusionist +illusionists +illusions +illusive +illusory +illustrate +illustrated +illustrates +illustrating +illustration +illustrations +illustrative +illustrator +illustrators +illustrious +illyrian +ilya +im +image +imaged +imager +imagery +images +imaginable +imaginary +imagination +imaginations +imaginative +imaginatively +imagine +imagined +imagines +imaging +imagining +imago +imai +imam +imams +iman +imax +imbalance +imbalances +imbecile +imbecility +imbed +imbedded +imber +imbibe +imbibed +imbibing +imbroglio +imbue +imbued +imelda +imes +imitate +imitated +imitates +imitating +imitation +imitations +imitative +imitator +imitators +imlay +immaculate +immaculately +immanuel +immaterial +immature +immaturity +immeasurable +immeasurably +immediacy +immediate +immediately +immemorial +immense +immensely +immensity +immer +immerse +immersed +immersing +immersion +immersive +immigrant +immigrants +immigrate +immigrated +immigration +imminence +imminent +imminently +immobile +immobility +immobilization +immobilize +immobilized +immobilizing +immolate +immolation +immoral +immorality +immorally +immortal +immortality +immortalize +immortalized +immortalizing +immortals +immovable +immune +immunex +immunities +immunity +immunization +immunizations +immunize +immunized +immunizing +immunodeficiency +immunological +immunologist +immunology +immunosuppression +immunotherapy +immutable +imo +imogen +imp +impact +impacted +impacting +impacts +impair +impaired +impairing +impairment +impairments +impairs +impala +impalas +impale +impaled +impaler +impaneled +impart +imparted +impartial +impartiality +impartially +imparting +imparts +impassable +impasse +impassioned +impassive +impassively +impatience +impatiens +impatient +impatiently +impeach +impeachable +impeached +impeaching +impeachment +impeachments +impeccable +impeccably +impedance +impede +impeded +impedes +impediment +impediments +impeding +impel +impelled +impending +impenetrability +impenetrable +imperative +imperatives +imperceptible +imperceptibly +imperfect +imperfection +imperfections +imperfectly +imperial +imperialism +imperialist +imperialistic +imperialists +imperials +imperil +imperiled +imperils +imperious +impermeable +impermissible +impersonal +impersonality +impersonate +impersonated +impersonating +impersonation +impersonator +impersonators +impertinent +impervious +imperviousness +impetuous +impetus +impinge +impinges +impish +implacable +implant +implantable +implantation +implanted +implanting +implants +implausible +implausibly +implement +implementable +implementation +implementations +implemented +implementers +implementing +implements +implicate +implicated +implicates +implicating +implication +implications +implicit +implicitly +implied +implies +implode +imploded +implodes +imploding +implore +implored +implores +imploring +implosion +imply +implying +impolite +impolitic +imponderable +imponderables +import +importance +important +importantly +importation +imported +importer +importers +importing +imports +impose +imposed +imposes +imposing +imposition +impositions +impossibility +impossible +impossibly +imposter +imposters +impostor +impostors +impotence +impotency +impotent +impound +impounded +impoundment +impoundments +impoverish +impoverished +impoverishment +impracticable +impractical +impracticality +imprecations +imprecise +imprecision +impregnable +impregnate +impregnated +impregnation +impresario +impresarios +impress +impressed +impresses +impressing +impression +impressionable +impressionism +impressionist +impressionistic +impressionists +impressions +impressive +impressively +imprimatur +imprint +imprinted +imprinting +imprints +imprison +imprisoned +imprisoning +imprisonment +imprisonments +improbability +improbable +improbably +impromptu +improper +improperly +improprieties +impropriety +improv +improve +improved +improvement +improvements +improves +improving +improvisation +improvisational +improvisations +improvise +improvised +improvising +imprudence +imprudent +imprudently +impugn +impugned +impugning +impulse +impulses +impulsive +impulsively +impunity +impure +impurities +impurity +imput +imputation +impute +imputed +imputes +imputing +imre +imus +in +ina +inability +inaccessibility +inaccessible +inaccuracies +inaccuracy +inaccurate +inaccurately +inaction +inactions +inactivate +inactivated +inactivates +inactivating +inactivation +inactive +inactivity +inadequacies +inadequacy +inadequate +inadequately +inadmissible +inadvertantly +inadvertence +inadvertent +inadvertently +inadvisable +inalienable +inane +inanimate +inanities +inapplicable +inappropriate +inappropriately +inappropriateness +inarguable +inarticulate +inasmuch +inattention +inattentive +inattentiveness +inaudible +inaugural +inaugurate +inaugurated +inaugurates +inaugurating +inauguration +inauspicious +inboard +inborn +inbound +inbred +inbreeding +inbuilt +inc +inca +incalculable +incan +incandescent +incantation +incapable +incapacitate +incapacitated +incapacitating +incapacitation +incapacity +incarcerate +incarcerated +incarcerating +incarceration +incarnate +incarnated +incarnation +incarnations +incas +ince +incendiary +incense +incensed +incentive +incentives +inception +incessant +incessantly +incest +incestuous +inch +inched +inches +inching +incidence +incidences +incident +incidental +incidentally +incidentals +incidently +incidents +incinerate +incinerated +incinerating +incineration +incinerator +incinerators +incipient +incised +incision +incisions +incisive +incisor +incisors +incite +incited +incitement +incitements +incites +inciting +incivility +incl +inclement +inclination +inclinations +incline +inclined +inclines +include +included +includes +including +inclusion +inclusions +inclusive +inclusiveness +inco +incognito +incoherence +incoherent +incoherently +income +incomes +incoming +incommunicado +incomparable +incomparably +incompatibilities +incompatibility +incompatible +incompetence +incompetency +incompetent +incompetently +incompetents +incomplete +incompleteness +incomprehensible +inconceivable +inconclusive +inconclusively +incongruities +incongruity +incongruous +incongruously +inconsequential +inconsiderable +inconsiderate +inconsistencies +inconsistency +inconsistent +inconsistently +inconspicuous +inconstancy +inconstant +incontinence +incontinent +incontrovertible +incontrovertibly +inconvenience +inconvenienced +inconveniences +inconvenient +inconveniently +incorporate +incorporated +incorporates +incorporating +incorporation +incorporations +incorporeal +incorrect +incorrectly +incorrectness +incorrigible +incorruptible +increase +increased +increases +increasing +increasingly +incredible +incredibly +incredulity +incredulous +increment +incremental +incrementalism +incrementally +increments +incriminate +incriminating +incrimination +incubate +incubates +incubating +incubation +incubator +incubators +incubus +inculcate +inculcates +incumbency +incumbent +incumbents +incur +incurable +incurred +incurring +incurs +incursion +incursions +ind +inde +indebted +indebtedness +indecency +indecent +indecision +indecisive +indecisiveness +indeed +indefatigable +indefatigably +indefensible +indefinable +indefinite +indefinitely +indelible +indelibly +indelicate +indemnification +indemnified +indemnify +indemnifying +indemnities +indemnity +indent +indentation +indentations +indented +indentified +indenture +indentured +independance +independant +independence +independent +independently +independents +inderal +indescribable +indescribably +indestructibility +indestructible +indeterminacy +indeterminate +index +indexation +indexed +indexer +indexers +indexes +indexing +india +indian +indiana +indianapolis +indians +indicate +indicated +indicates +indicating +indication +indications +indicative +indicator +indicators +indices +indicia +indict +indicted +indicting +indictment +indictments +indicts +indies +indifference +indifferent +indigenous +indigent +indigents +indigestion +indignant +indignantly +indignation +indignities +indignity +indigo +indira +indirect +indirectly +indiscreet +indiscretion +indiscretions +indiscriminate +indiscriminately +indispensable +indisposition +indisputable +indisputably +indistinct +indistinguishable +indium +individual +individualism +individualist +individualistic +individualists +individuality +individualize +individualized +individually +individuals +indivisible +indo +indochina +indochinese +indoctrinate +indoctrinated +indoctrination +indolent +indomitable +indonesia +indonesian +indonesians +indoor +indoors +induce +induced +inducement +inducements +induces +inducing +induct +inducted +inductee +inductees +induction +inductive +inductors +indulge +indulged +indulgence +indulgences +indulgent +indulges +indulging +indus +industrial +industrialised +industrialist +industrialists +industrialization +industrialize +industrialized +industrializing +industrially +industrials +industrie +industries +industrious +industry +indwelling +indy +inebriated +inebriation +inedible +ineffable +ineffective +ineffectiveness +ineffectual +inefficiencies +inefficiency +inefficient +inefficiently +inelastic +ineligibility +ineligible +ineluctable +ineluctably +inept +ineptitude +ineptly +ineptness +inequalities +inequality +inequitable +inequities +inequity +inerrancy +inerrant +inert +inertia +inertial +inertness +inescapable +inevitability +inevitable +inevitably +inexact +inexcusable +inexhaustible +inexorable +inexorably +inexpedient +inexpensive +inexpensively +inexperience +inexperienced +inexplicable +inexplicably +inexpressible +inextricably +inez +infact +infallibility +infallible +infamous +infamy +infancy +infant +infante +infanticide +infantile +infantry +infantryman +infantrymen +infants +infarction +infatuated +infatuation +infeasible +infect +infected +infecting +infection +infections +infectious +infective +infectivity +infects +infer +inference +inferences +inferior +inferiority +inferiors +infernal +inferno +inferred +inferring +infers +infertile +infertility +infest +infestation +infestations +infested +infests +infidel +infidelities +infidelity +infidels +infield +infielder +infielders +infighting +infill +infiltrate +infiltrated +infiltrating +infiltration +infiltrations +infiltrators +infinite +infinitely +infinitesimal +infiniti +infinitive +infinitum +infinity +infirm +infirmary +infirmities +infirmity +inflame +inflamed +inflames +inflaming +inflammable +inflammation +inflammatory +inflatable +inflate +inflated +inflates +inflating +inflation +inflationary +inflect +inflected +inflection +inflections +inflexibility +inflexible +inflexibly +inflict +inflicted +inflicting +infliction +inflicts +inflight +inflow +inflows +influence +influenced +influences +influencing +influential +influenza +influx +info +inform +informal +informality +informally +informant +informants +informatics +information +informational +informations +informative +informed +informer +informers +informing +informix +informs +infotainment +infoworld +infra +infraction +infractions +infrared +infrastructure +infrastructures +infrequency +infrequent +infrequently +infringe +infringed +infringement +infringements +infringes +infringing +infuriate +infuriated +infuriates +infuriating +infuriatingly +infuse +infused +infuses +infusing +infusion +infusions +ing +inga +ingalls +inge +ingelheim +ingenious +ingeniously +ingenue +ingenuity +ingenuous +ingersoll +ingest +ingested +ingesting +ingestion +ingham +ingle +inglenook +ingles +inglewood +inglis +inglorious +ingo +ingot +ingots +ingraham +ingrained +ingram +ingratiate +ingratiating +ingratitude +ingredient +ingredients +ingres +ingress +ingrid +inguinal +inhabit +inhabitant +inhabitants +inhabited +inhabiting +inhabits +inhalant +inhalants +inhalation +inhale +inhaled +inhaler +inhaling +inherent +inherently +inherit +inheritable +inheritance +inherited +inheriting +inheritor +inheritors +inherits +inhibit +inhibited +inhibiting +inhibition +inhibitions +inhibitor +inhibitors +inhibitory +inhibits +inhospitable +inhouse +inhuman +inhumane +inhumanity +inhumanly +inimical +inimitable +iniquities +iniquity +init +inital +initial +initialize +initialized +initially +initials +initiate +initiated +initiates +initiating +initiation +initiations +initiative +initiatives +initiator +initiators +initio +inject +injectable +injected +injecting +injection +injections +injector +injectors +injects +injudicious +injunction +injunctions +injunctive +injure +injured +injures +injuries +injuring +injurious +injury +injustice +injustices +ink +inked +inking +inkjet +inkling +inklings +inks +inky +inlaid +inland +inlaws +inlay +inlays +inlet +inlets +inline +inman +inmate +inmates +inn +inna +innards +innate +innately +inner +innermost +innes +inning +innings +innis +innkeeper +innkeepers +innocence +innocent +innocenti +innocently +innocents +innocuous +innovate +innovated +innovating +innovation +innovations +innovative +innovatively +innovativeness +innovator +innovators +inns +innuendo +innuendoes +innuendos +innumerable +inoculate +inoculated +inoculation +inoculations +inoffensive +inoperable +inoperative +inopportune +inordinate +inordinately +inorganic +inoue +inouye +inpatient +inpatients +input +inputs +inputting +inquest +inquire +inquired +inquirer +inquirers +inquires +inquiries +inquiring +inquiry +inquisition +inquisitions +inquisitive +inquisitor +inquisitors +inroad +inroads +ins +insane +insanely +insanity +insatiable +inscribe +inscribed +inscription +inscriptions +inscrutable +insect +insecticide +insecticides +insects +insecure +insecurities +insecurity +inseminate +insemination +insensitive +insensitivity +inseparable +inseparably +insert +inserted +inserting +insertion +insertions +inserts +inset +insets +inshore +inside +insider +insiders +insides +insidious +insight +insightful +insights +insignia +insignificance +insignificant +insignificantly +insincere +insincerely +insinuate +insinuated +insinuates +insinuating +insinuation +insinuations +insipid +insist +insistance +insisted +insistence +insistent +insistently +insisting +insists +insofar +insole +insolence +insolent +insoles +insoluble +insolvency +insolvent +insomnia +insomniac +insouciance +insouciant +inspect +inspected +inspecting +inspection +inspections +inspector +inspectorate +inspectors +inspects +inspiration +inspirational +inspirations +inspire +inspired +inspirer +inspires +inspiring +inst +instabilities +instability +install +installation +installations +installed +installer +installers +installing +installment +installments +installs +instalment +instance +instances +instant +instantaneous +instantaneously +instantly +instants +instate +instated +instead +instigate +instigated +instigating +instigation +instigator +instigators +instill +instilled +instilling +instills +instinct +instinctive +instinctively +instincts +instinet +institut +institute +instituted +institutes +instituting +institution +institutional +institutionalised +institutionalization +institutionalize +institutionalized +institutionalizes +institutionalizing +institutionally +institutions +instituto +instruct +instructed +instructing +instruction +instructional +instructions +instructive +instructor +instructors +instructs +instrument +instrumental +instrumentalist +instrumentalists +instrumentality +instrumentals +instrumentation +instrumented +instruments +insubordination +insubstantial +insufferable +insufficiency +insufficient +insufficiently +insular +insularity +insulate +insulated +insulates +insulating +insulation +insulator +insulators +insulin +insult +insulted +insulting +insults +insupportable +insurability +insurable +insurance +insurances +insure +insured +insurer +insurers +insures +insurgencies +insurgency +insurgent +insurgents +insuring +insurmountable +insurrection +insurrections +int +intact +intake +intakes +intangible +intangibles +integer +integers +integra +integral +integrally +integrate +integrated +integrates +integrating +integration +integrations +integrative +integrator +integrators +integrity +intel +intellect +intellects +intellectual +intellectualism +intellectually +intellectuals +intelligence +intelligencer +intelligences +intelligent +intelligently +intelligentsia +intelligibility +intelligible +intelligibly +intels +intelsat +intemperate +intend +intended +intending +intends +intense +intensely +intensification +intensified +intensifier +intensifies +intensify +intensifying +intension +intensities +intensity +intensive +intensively +intent +intention +intentional +intentionally +intentioned +intentions +intently +intents +inter +interact +interacted +interacting +interaction +interactions +interactive +interactively +interactivity +interacts +interagency +interamerican +interbank +intercede +interceded +interceding +intercellular +intercept +intercepted +intercepting +interception +interceptions +interceptor +interceptors +intercepts +intercession +interchange +interchangeability +interchangeable +interchangeably +interchanged +interchanges +intercity +intercollegiate +intercom +intercompany +intercoms +interconnect +interconnected +interconnectedness +interconnecting +interconnection +interconnections +intercontinental +intercourse +intercultural +interdepartmental +interdependence +interdependencies +interdependent +interdict +interdicted +interdicting +interdiction +interdigital +interdisciplinary +interest +interested +interesting +interestingly +interests +interface +interfaced +interfaces +interfacing +interfaith +interfax +interfere +interfered +interference +interferences +interferes +interfering +interferometer +interferometry +interferon +intergalactic +intergenerational +intergovernmental +intergraph +intergroup +interim +interior +interiors +interject +interjected +interjecting +interjection +interjections +interjects +interlaced +interlacing +interleaf +interleaved +interleaving +interline +interlink +interlinked +interlock +interlocked +interlocking +interlocutor +interlocutors +interloper +interlopers +interlude +interludes +intermarriage +intermarried +intermarry +intermedia +intermediaries +intermediary +intermediate +intermediates +intermediation +interment +interminable +interminably +intermingle +intermingled +intermingling +intermission +intermissions +intermittent +intermittently +intermix +intermixed +intermixing +intermountain +intern +internacional +internal +internalize +internalized +internalizes +internalizing +internally +internals +international +internationale +internationalism +internationalist +internationalists +internationalization +internationalize +internationally +internationals +internecine +interned +internee +internees +internet +internetworking +interneuron +internist +internists +internment +interns +internship +internships +interoffice +interoperability +interpersonal +interplanetary +interplay +interpol +interpolate +interpolated +interpolation +interpose +interposition +interpret +interpretation +interpretations +interpreted +interpreter +interpreters +interpreting +interpretive +interprets +interpublic +interracial +interred +interregnum +interrelate +interrelated +interrelation +interrelationship +interrelationships +interrogate +interrogated +interrogating +interrogation +interrogations +interrogator +interrogatories +interrogators +interrogatory +interrupt +interrupted +interrupters +interruptible +interrupting +interruption +interruptions +interrupts +interscholastic +intersect +intersected +intersecting +intersection +intersections +intersects +interspecies +interspersed +intersperses +interstate +interstates +interstellar +intersting +intertribal +intertwine +intertwined +intertwining +interval +intervals +intervene +intervened +interveners +intervenes +intervening +intervenor +intervention +interventional +interventionism +interventionist +interventionists +interventions +interview +interviewed +interviewee +interviewees +interviewer +interviewers +interviewing +interviews +interweave +interweaving +interwoven +intestinal +intestine +intestines +inthe +intifada +intimacy +intimate +intimated +intimately +intimates +intimation +intimations +intimidate +intimidated +intimidates +intimidating +intimidation +intl +into +intolerable +intolerably +intolerance +intolerant +intonation +intonations +intoned +intones +intoning +intourist +intoxicate +intoxicated +intoxicating +intoxication +intra +intracranial +intractable +intramural +intransigence +intransigent +intransitive +intraocular +intrastate +intrauterine +intravenous +intravenously +intrepid +intricacies +intricacy +intricate +intricately +intrigue +intrigued +intrigues +intriguing +intriguingly +intrinsic +intrinsically +intro +introduce +introduced +introduces +introducing +introduction +introductions +introductory +intron +intros +introspection +introspective +introversion +introvert +introverted +intrude +intruded +intruder +intruders +intrudes +intruding +intrusion +intrusions +intrusive +intrusiveness +intubation +intuit +intuition +intuitive +intuitively +inuit +inundate +inundated +inundating +inundation +inure +inured +invade +invaded +invader +invaders +invades +invading +invalid +invalidate +invalidated +invalidates +invalidating +invalidation +invalids +invaluable +invariably +invariant +invasion +invasions +invasive +invective +invent +invented +inventing +invention +inventions +inventive +inventiveness +inventor +inventoried +inventories +inventors +inventory +invents +inverness +inverse +inversely +inversion +inversions +invert +invertebrate +invertebrates +inverted +inverter +inverting +inverts +invesco +invest +invested +investigate +investigated +investigates +investigating +investigation +investigational +investigations +investigative +investigator +investigators +investigatory +investing +investiture +investment +investments +investor +investors +invests +inveterate +invidious +invigorate +invigorated +invigorates +invigorating +invincibility +invincible +inviolability +inviolable +inviolate +invisibility +invisible +invitation +invitational +invitations +invite +invited +invitees +invites +inviting +invocation +invoice +invoices +invoicing +invoke +invoked +invokes +invoking +involuntarily +involuntary +involve +involved +involvement +involvements +involves +involving +invulnerability +invulnerable +inward +inwardly +inwards +inwood +io +iodide +iodine +iomega +ion +iona +ionian +ionic +ionics +ionization +ionizing +ionosphere +ions +ios +iota +iowa +iowan +iowans +ip +ipl +ipso +iqbal +ir +ira +iran +irani +iranian +iranians +iraq +iraqi +iraqis +iras +irascible +irate +ire +ireland +irene +irian +iridescent +iridium +irina +iris +irises +irish +irishman +irk +irked +irks +irksome +irma +iron +ironclad +ironed +ironic +ironical +ironically +ironies +ironing +ironman +irons +ironside +ironsides +ironweed +ironwood +ironworkers +irony +iroquois +irradiate +irradiated +irradiation +irrational +irrationality +irrationally +irreconcilable +irrefutable +irregardless +irregular +irregularities +irregularity +irregularly +irregulars +irrelevance +irrelevancy +irrelevant +irremediable +irreparable +irreparably +irreplacable +irreplaceable +irrepressible +irrepressibly +irresistible +irresistibly +irresolution +irrespective +irresponsibility +irresponsible +irresponsibly +irretrievably +irreverence +irreverent +irreversible +irreversibly +irrevocable +irrevocably +irrigate +irrigated +irrigation +irritability +irritable +irritant +irritants +irritate +irritated +irritates +irritating +irritation +irritations +irs +irvin +irvine +irving +irwin +is +isa +isaac +isaacs +isaacson +isabel +isabella +isabelle +isaiah +iscariot +ischemic +ise +iser +ish +ishaq +isherwood +ishi +ishibashi +ishida +ishihara +ishii +ishikawa +ishmael +ishtar +isidore +isis +isl +islam +islamabad +islamic +islamist +islamists +island +islander +islanders +islands +isle +isles +islet +islets +isley +islington +islip +ism +ismael +ismail +isms +isnt +iso +isolate +isolated +isolates +isolating +isolation +isolationism +isolationist +isolationists +isolators +isomer +isomers +isometric +isopropyl +isosceles +isotonic +isotope +isotopes +isotopic +israel +israeli +israelis +israelite +israelites +issa +issac +issuance +issuances +issue +issued +issuer +issuers +issues +issuing +ist +istanbul +isthmus +ists +isu +isuzu +it +ita +ital +italia +italian +italiana +italianate +italiano +italians +italic +italics +italy +itasca +itch +itched +itches +itching +itchy +item +itemize +itemized +itemizes +itemizing +items +iterate +iterated +iteration +iterations +iterative +ites +ithaca +itinerant +itineraries +itinerary +ito +its +itself +itsy +itty +itzhak +iv +iva +ivan +ivana +ivanhoe +ive +iver +iversen +iverson +ives +ivey +ivins +ivo +ivor +ivory +ivy +iw +iwai +iwasaki +ix +izard +izetbegovic +izumi +izvestia +izzard +izzat +izzy +ja +jaap +jab +jabbed +jabbering +jabbing +jaber +jabir +jabs +jac +jacaranda +jacek +jacinto +jack +jackal +jackals +jackass +jacked +jacket +jacketed +jackets +jackhammer +jackhammers +jackie +jacking +jacklin +jackman +jacko +jackpot +jackpots +jackrabbit +jacks +jackson +jacksons +jacksonville +jacky +jaco +jacob +jacobean +jacobi +jacobin +jacobite +jacobo +jacobs +jacobsen +jacobson +jacobus +jacoby +jacquard +jacque +jacqueline +jacquelyn +jacques +jacquie +jacuzzi +jad +jade +jaded +jades +jae +jaeger +jaffa +jaffar +jaffe +jaffee +jaffray +jag +jagdish +jager +jagged +jagger +jaggers +jago +jags +jaguar +jaguars +jah +jahn +jai +jail +jailbird +jailbreak +jailed +jailer +jailers +jailhouse +jailing +jails +jaime +jain +jainism +jakarta +jake +jakes +jalalabad +jalapeno +jam +jama +jamaica +jamaican +jamaicans +jamal +jamb +jambalaya +jamboree +james +jameson +jamestown +jami +jamie +jamieson +jamison +jammed +jammer +jammers +jamming +jammu +jammy +jams +jan +jana +janacek +janata +jane +janeiro +janes +janet +janette +janey +jang +jangle +janice +janie +janine +janis +janitor +janitorial +janitors +jankowski +jann +janna +janney +janos +jansen +janssen +janssens +january +janus +janvier +janzen +jap +japan +japanese +japans +japonica +japs +jaques +jar +jara +jaramillo +jarboe +jardin +jardine +jared +jargon +jarman +jarreau +jarred +jarrell +jarret +jarrett +jarring +jars +jaruzelski +jarvis +jas +jasmine +jason +jasper +jaspers +jass +jaundice +jaundiced +jaunt +jauntily +jaunts +jaunty +java +javanese +javed +javelin +javier +javits +jaw +jawad +jawbone +jawed +jaworski +jaws +jay +jaya +jaycee +jaye +jayhawk +jayne +jays +jaywalking +jazz +jazzed +jazzier +jazzing +jazzman +jazzy +je +jealous +jealousies +jealously +jealousy +jean +jeane +jeanette +jeanie +jeanine +jeanne +jeannette +jeannie +jeans +jebel +jed +jedi +jee +jeep +jeeps +jeer +jeered +jeering +jeers +jeeves +jeez +jefe +jeff +jefferies +jeffers +jefferson +jeffersonian +jeffery +jeffords +jeffrey +jeffreys +jeffries +jeffry +jeffs +jehovah +jekyll +jelinek +jell +jellied +jellies +jelling +jello +jelly +jellybean +jellyfish +jem +jemima +jemison +jen +jena +jenin +jenkin +jenkins +jenkinson +jenks +jenna +jenner +jennie +jennifer +jennings +jenny +jenrette +jens +jensen +jenson +jenssen +jeopardize +jeopardized +jeopardizes +jeopardizing +jeopardy +jepson +jer +jerald +jere +jeremiah +jeremy +jerez +jeri +jericho +jerk +jerked +jerking +jerks +jerky +jermaine +jeroboam +jerold +jerome +jerrold +jerry +jersey +jerseys +jerusalem +jerzy +jesper +jespersen +jess +jesse +jessica +jessie +jessup +jessy +jest +jester +jesters +jests +jesuit +jesuits +jesus +jet +jeter +jethro +jetliner +jetliners +jets +jetsons +jetstream +jett +jetta +jetted +jetties +jetting +jettison +jettisoned +jettisoning +jetty +jew +jewel +jeweler +jewelers +jewell +jewellers +jewellery +jewelry +jewels +jewess +jewett +jewish +jewishness +jewry +jews +jg +ji +jian +jiang +jiangsu +jibe +jibes +jif +jiffy +jig +jigger +jiggers +jiggle +jiggles +jiggling +jiggly +jigs +jigsaw +jihad +jill +jillian +jillion +jilted +jim +jima +jimenez +jimi +jimmie +jimmy +jin +jing +jingle +jingles +jingling +jingoism +jingoistic +jingsheng +jinks +jinx +jirga +jiri +jitney +jitter +jitterbug +jitters +jittery +jive +jo +joachim +joakim +joan +joann +joanna +joanne +joao +joaquim +joaquin +job +jobber +jobbers +jobless +joblessness +jobs +jobson +jocelyn +jocelyne +jochen +jock +jockey +jockeyed +jockeying +jockeys +jocks +jocular +jodi +jodie +jody +joe +joel +joelle +joerg +joes +joey +joffe +jog +jogged +jogger +joggers +jogging +jogs +joh +johan +johann +johanna +johannes +johannesburg +johansen +johanson +johansson +john +johnnie +johnny +johns +johnsen +johnson +johnsons +johnston +johnstone +johnstown +johny +joie +join +joined +joiner +joiners +joinery +joining +joins +joint +jointed +jointly +joints +joist +joists +jojo +joke +joked +joker +jokers +jokes +jokester +jokey +joking +jokingly +jolene +joliet +jolla +jolley +jollies +jolly +jolson +jolt +jolted +jolting +jolts +jon +jonah +jonas +jonathan +jonathon +jones +joneses +jong +joni +jonker +jonny +jonsson +joost +joplin +jordache +jordan +jordanian +jordanians +jordans +jorden +jorg +jorge +jorgenson +jos +jose +josef +joseph +josephine +josephs +josephson +josephus +josette +josey +josh +joshi +joshua +josiah +josie +joslin +jospin +joss +jost +jostle +jostled +jostling +jot +jotted +joubert +joule +joules +jour +journal +journaling +journalism +journalist +journalistic +journalistically +journalists +journals +journey +journeyed +journeying +journeyman +journeymen +journeys +jours +joust +jousting +jousts +jovanovic +jove +jovi +jovial +jovian +jowl +jowls +joy +joyce +joyful +joyfully +joyner +joyous +joyousness +joyride +joyriding +joys +joystick +joysticks +jozef +jr +js +jsut +ju +juan +juana +juanita +juarez +jubilant +jubilation +jubilee +jud +judah +judaic +judaica +judaism +judas +judd +jude +judea +juden +judge +judged +judgement +judgemental +judgements +judges +judgeship +judgeships +judging +judgment +judgmental +judgments +judi +judice +judicial +judicially +judiciary +judicious +judiciously +judith +judo +judson +judy +juergen +jug +juggernaut +juggernauts +juggle +juggled +juggler +jugglers +juggles +juggling +jugs +jugular +juice +juiced +juices +juicier +juiciest +juicy +juive +jujitsu +juju +juke +jukebox +jukeboxes +jukes +jule +julep +juleps +jules +juli +julia +julian +juliana +julianne +julie +julien +julienne +juliet +julieta +juliette +julio +julius +july +jumble +jumbled +jumbo +jumbos +jump +jumped +jumper +jumpers +jumpin +jumping +jumps +jumpstart +jumpsuit +jumpsuits +jumpy +jun +junco +junction +junctions +juncture +junctures +june +juneau +junes +jung +jungle +jungles +junior +juniors +juniper +junk +junked +junker +junket +junkets +junkie +junkies +junking +junks +junky +junkyard +junkyards +juno +junta +jupiter +jura +jurassic +jure +jurek +jurgen +juries +juris +jurisdiction +jurisdictional +jurisdictions +jurisprudence +jurist +jurists +juror +jurors +jury +jus +just +juster +justice +justices +justifiable +justifiably +justification +justifications +justified +justifies +justify +justifying +justin +justine +justinian +justly +justness +justo +justus +jut +jute +juts +jutting +juvenal +juvenile +juveniles +juxtapose +juxtaposed +juxtaposition +juxtapositions +ka +kaas +kabbalah +kabel +kabuki +kabul +kachina +kadish +kael +kafka +kagan +kahan +kahane +kahl +kahle +kahn +kai +kailash +kain +kaiser +kakar +kal +kala +kalamazoo +kalashnikov +kalb +kale +kaleida +kaleidoscope +kaley +kali +kalin +kaliningrad +kalinowski +kalis +kalla +kallman +kalman +kam +kama +kamal +kamehameha +kamel +kamen +kami +kamikaze +kamin +kaminski +kamloops +kammer +kampala +kampf +kan +kana +kanan +kanata +kandahar +kandel +kane +kang +kanga +kangaroo +kangaroos +kangas +kanji +kann +kano +kansai +kansan +kansas +kant +kanter +kantor +kantrowitz +kao +kaolin +kaplan +kaplow +kapok +kapoor +kapp +kappa +kapur +kapuscinski +kaput +kara +karabakh +karachi +karadzic +karan +karaoke +karas +karat +karate +kareem +karel +karen +kari +karim +karin +karina +karl +karla +karlin +karlson +karlsruhe +karlsson +karma +karman +karn +karner +karol +karolinska +karon +karp +karpinski +karr +karren +karsh +karst +karsten +karyn +kasey +kashmir +kashmiri +kaspar +kasper +kass +kassebaum +kassel +kassem +kastner +kat +katana +katarina +kate +kates +kath +katha +katharina +katharine +katherine +kathi +kathie +kathleen +kathryn +kathy +kati +katia +katie +katja +katmandu +kato +katrina +kats +katt +katy +katya +katyusha +katz +katzenbach +katzenberg +kauai +kauffman +kaufman +kaufmann +kaul +kaunda +kauri +kavanagh +kavanaugh +kawai +kawamura +kawasaki +kay +kaya +kayak +kayaking +kayaks +kayan +kaye +kays +kayser +kazakh +kazakhstan +kazan +kazoo +kazuo +kbps +kc +kcal +kea +keach +kean +keane +keanu +kearney +kearns +keast +keating +keaton +keats +kebab +kebabs +keck +kee +keebler +keech +keefe +keefer +keegan +keel +keele +keeler +keeley +keeling +keels +keely +keen +keenan +keene +keener +keenest +keeney +keenly +keenness +keep +keeper +keepers +keeping +keeps +keepsake +keepsakes +keeton +keg +kegs +kehoe +keifer +keiko +keillor +keir +keiretsu +keiser +keith +keithley +keizai +kell +kelleher +keller +kellerman +kellett +kelley +kelliher +kellner +kellogg +kells +kelly +kellys +kelowna +kelp +kelsey +kelso +kelson +kelt +keltner +kelton +kelty +kelvin +kem +kemal +kemble +kemp +kempe +kemper +kempner +kempthorne +kempton +ken +kenaf +kenan +kendal +kendall +kendell +kendra +kendrick +keng +kenilworth +kenmore +kennard +kennebunkport +kennedy +kennedys +kennel +kennelly +kennels +kenner +kennesaw +kenneth +kennett +kenney +kenny +keno +kens +kensington +kent +kenton +kentuckians +kentucky +kenwood +kenya +kenyan +kenyans +kenyon +kenzo +keogh +kepler +keppel +kept +ker +kerala +keratin +kerb +kerchief +kerchiefs +kerem +kerfuffle +keri +kerman +kermit +kern +kernan +kernel +kernels +kerner +kerney +kerns +kerosene +kerosine +kerouac +kerr +kerrey +kerri +kerrigan +kerrville +kerry +kersey +kershaw +kershner +kersten +kerwin +kessler +kester +kestrel +keswick +ketch +ketchum +ketchup +ketone +kettering +kettle +kettles +kettlewell +kevin +kevlar +kevorkian +key +keyboard +keyboards +keyed +keyes +keyhole +keying +keyless +keynes +keynesian +keynote +keynotes +keypad +keypads +keys +keyser +keystone +keystroke +keystrokes +keyword +keywords +kfar +kg +khair +khaki +khakis +khaled +khalid +khalifa +khalil +khalsa +khamenei +khan +khartoum +khat +khmer +khomeini +khoo +khosla +khost +khouri +khrushchev +khufu +ki +kia +kibble +kibbutz +kibbutzim +kick +kickback +kickbacks +kicked +kicker +kickers +kicking +kickoff +kickoffs +kicks +kid +kidd +kidder +kiddie +kiddies +kidding +kiddo +kiddy +kidman +kidnap +kidnaping +kidnapped +kidnapper +kidnappers +kidnapping +kidnappings +kidnaps +kidney +kidneys +kids +kidwell +kiefer +kiel +kielbasa +kieran +kierkegaard +kiernan +kiester +kieth +kiev +kigali +kiki +kikkoman +kilborn +kile +kiley +kilgore +kilimanjaro +kilkenny +kill +killed +killeen +killen +killer +killers +killing +killings +killjoy +kills +kilmer +kiln +kilns +kilo +kilobytes +kilogram +kilograms +kilometer +kilometers +kilos +kiloton +kilowatt +kilowatts +kilpatrick +kilroy +kilt +kilter +kilts +kilty +kim +kimba +kimball +kimberley +kimberlin +kimberly +kimble +kimchi +kime +kimmel +kimmelman +kimono +kimonos +kims +kimura +kin +kina +kincaid +kinch +kind +kinda +kinder +kindergarten +kindergartens +kindest +kindle +kindled +kindliness +kindling +kindly +kindness +kindnesses +kindred +kinds +kinesiology +kinetic +kinetics +king +kingdom +kingdome +kingdoms +kingfish +kingfisher +kingfishers +kingly +kingmaker +kingman +kingpin +kingpins +kings +kingsbury +kingship +kingsland +kingsley +kingsport +kingston +kingsway +kington +kink +kinks +kinky +kinnear +kinney +kinsella +kinsey +kinshasa +kinship +kinsley +kinsman +kinsmen +kiosk +kiosks +kip +kipling +kipper +kippers +kippur +kiran +kirby +kirchhoff +kirchner +kiri +kirk +kirker +kirkland +kirkpatrick +kirkuk +kirkwood +kirn +kirsch +kirschner +kirsten +kirstie +kirt +kirtland +kirtley +kirwan +kisan +kisch +kisco +kish +kishore +kismet +kiss +kissane +kissed +kisser +kisses +kissimmee +kissing +kissinger +kist +kit +kita +kitagawa +kitamura +kitch +kitchen +kitchener +kitchenette +kitchens +kitchenware +kitchin +kite +kites +kiting +kits +kitsch +kitt +kitted +kitten +kittens +kittle +kitts +kitty +kiwanis +kiwi +kiwis +kjell +kkk +kl +klaas +klan +klansman +klapper +klar +klare +klatch +klaus +klausner +klaxon +kleck +klee +kleenex +klees +kleiman +klein +kleiner +kleinman +kleinwort +klerk +klezmer +klick +klima +kline +kling +klinger +klingon +klink +klondike +klug +kluge +klute +klutz +kluwer +klux +km +kmart +kn +knack +knacks +knapp +knapsack +knauer +knave +knavery +knaves +knead +kneading +knee +kneecap +kneecaps +kneed +kneejerk +kneel +kneeling +knees +knell +knelt +knepper +knesset +knew +knick +knicker +knickerbocker +knickers +knicks +knife +knifed +knifes +knight +knighted +knighthood +knightly +knights +knit +knits +knitted +knittel +knitter +knitting +knitwear +knives +knob +knobby +knoblauch +knobs +knock +knockdown +knocked +knocking +knockoff +knockoffs +knockout +knocks +knoll +knop +knopf +knorr +knot +knothole +knots +knott +knotted +knotts +knotty +know +knowable +knowing +knowingly +knowledgable +knowledge +knowledgeable +knowledgeably +knowles +knowlton +known +knowns +knows +knox +knoxville +knuckle +knuckled +knucklehead +knuckleheads +knuckles +knuckling +knudsen +knudson +knut +knuth +knutsen +knutson +ko +koala +koalas +koan +kobayashi +kobe +kobi +koblenz +kobrin +koch +kock +kodak +kodiak +koehler +koenig +koerner +koff +kofi +kogan +kohl +kohlberg +kohler +kohli +kohn +kohut +koichi +koji +kojima +kok +koki +koko +kokomo +kol +kola +kolar +kolb +kolbe +kolker +koller +kolstad +komodo +kon +kona +kondo +kong +konrad +konstantin +kook +kooks +kooky +kool +koon +koons +koontz +koop +kootenay +kopeks +kopel +kopf +kopp +koppel +kopper +koppes +kops +kora +koran +koranic +korea +korean +koreans +koreas +koreatown +koresh +korey +kori +korman +kormos +korn +kornberg +kornfeld +korona +koruna +kory +kos +kosher +kosinski +kosovo +koss +kot +kothari +koto +kotter +kottke +kou +kovar +kowalczyk +kowtow +kowtowing +koza +kozak +kozinski +kozloff +kozlowski +kph +kr +krabbe +kraemer +kraft +kraftwerk +krakow +kral +krall +kramer +krantz +kranzler +kras +krasner +krasnoyarsk +krasny +kraus +krause +kraushaar +krauss +kraut +krauthammer +kravis +kravitz +krawczyk +krebs +kreis +kremer +kremlin +krenz +kress +kreuger +krieger +krill +kris +krishna +krishnan +krispies +kriss +krista +kristen +kristin +kristina +kristine +kristof +kristol +kristy +kriz +kroger +krogh +krohn +krol +kroll +kromer +kron +krona +krone +kroner +kronor +kronos +krs +krueger +krug +kruger +krugman +kruk +krupa +krupp +krups +kruse +krypton +kryptonite +krzysztof +kt +ku +kuala +kuan +kubik +kubota +kubrick +kucera +kuchma +kuck +kuczynski +kudo +kudos +kudu +kuehn +kuhn +kuiper +kula +kulkarni +kumar +kummer +kumquat +kun +kuna +kung +kunkel +kunst +kunz +kuo +kuper +kupper +kurd +kurdish +kurdistan +kurds +kuril +kuroda +kurosawa +kurt +kurtz +kurtzman +kuru +kusa +kush +kushner +kutch +kuttab +kuttner +kuwait +kuwaiti +kuwaitis +kwai +kwak +kwan +kwang +kwanza +kwasniewski +kwh +kwiatkowski +kwik +kwok +kwon +ky +kyd +kyi +kyl +kyle +kyles +kylie +kym +kyocera +kyodo +kyoto +kyra +kyrgyzstan +kyte +kyu +kyung +kyushu +la +lab +labatt +labbe +label +labeled +labeling +labelle +labelled +labelling +labels +labia +labor +laboratories +laboratory +laborde +labored +laborer +laborers +laboring +laborious +laboriously +labors +labour +labourers +labouring +labrador +labrum +labs +labyrinth +labyrinthine +lac +lacayo +lace +laced +laceration +lacerations +laces +lacey +lachlan +lachman +lachrymose +lacing +lack +lackadaisical +lackawanna +lacked +lackey +lackeys +lacking +lackland +lackluster +lacks +laconic +lacquer +lacquered +lacroix +lacrosse +lactase +lactate +lactating +lactation +lactic +lactobacillus +lactose +lacy +lad +lada +ladd +ladder +ladders +laddie +laden +ladies +ladin +lading +ladino +ladle +ladled +ladles +ladner +lads +lady +ladybird +ladybug +ladybugs +ladylike +lafarge +lafayette +laff +laffer +lafferty +lafitte +lafleur +lafond +lafontaine +lag +lagan +lager +lagerfeld +laggard +laggards +lagged +lagging +lago +lagoon +lagoons +lagos +lagrange +lags +laguardia +laguna +lagunas +lahey +lahore +lahr +lai +laid +laidlaw +lain +laine +laing +lair +laird +laissez +lait +laity +lajoie +lak +lake +lakefront +lakehead +lakeland +laker +lakers +lakes +lakeshore +lakeside +lakewood +lakin +lakota +lala +lall +lally +lalo +lalonde +lam +lama +lamar +lamas +lamaze +lamb +lambada +lambaste +lambasted +lambasting +lambda +lambe +lambert +lamberth +lambeth +lambie +lambing +lamborghini +lambs +lambskin +lame +lamely +lameness +lament +lamentable +lamentably +lamentation +lamentations +lamented +lamenting +laments +lamer +lamina +laminar +laminate +laminated +laminates +laminating +lamination +lamm +lammers +lamond +lamont +lamotte +lamp +lampkin +lampoon +lampooned +lampoons +lamppost +lampposts +lamprey +lamps +lampshade +lamy +lan +lana +lanai +lancashire +lancaster +lance +lanced +lancelot +lancer +lancers +lances +lancet +lancia +lancing +land +landa +landau +landauer +lande +landed +lander +landers +landesbank +landesman +landfall +landfill +landfills +landholders +landholding +landholdings +landing +landings +landis +landlady +landless +landline +landlocked +landlord +landlords +landman +landmark +landmarks +landmass +landmine +landmines +lando +landon +landover +landowner +landowners +landrum +landry +lands +landsat +landsberg +landscape +landscaped +landscaper +landscapers +landscapes +landscaping +landslide +landslides +landsman +landward +landwehr +landy +lane +lanes +laney +lang +langan +langauge +langdon +lange +langer +langford +langley +langridge +langston +language +languages +languedoc +languid +languish +languished +languishes +languishing +lanham +lani +lanier +lank +lanka +lankan +lankford +lanky +lanny +lanolin +lansbury +lansdale +lansdowne +lansing +lantana +lantern +lanterns +lantz +lanyards +lanza +lao +laos +laotian +lap +laparoscopic +laparoscopy +lapd +lapdog +lapdogs +lapel +lapels +lapham +lapidary +lapin +lapis +lapland +lapointe +laporte +lapp +lapped +lapping +laps +lapse +lapsed +lapses +lapsing +laptop +laptops +lar +lara +laramie +larcenous +larceny +larch +lard +larded +larder +lardner +laredo +large +largely +largemouth +largeness +largent +larger +largess +largesse +largest +largish +largo +lariat +larissa +lark +larkin +larkins +larks +larkspur +laroche +larose +larouche +larry +lars +larsen +larson +larsson +larva +larvae +larval +laryngeal +laryngitis +larynx +las +lasagna +lasalle +lascivious +lase +laser +laserjet +lasers +lash +lashed +lasher +lashes +lashing +lashings +lask +laska +lasky +lass +lasse +lasser +lasseter +lassie +lasso +last +lasted +laster +lastest +lasting +lastly +lasts +laszlo +lat +lata +latch +latched +latches +latchkey +late +latecomer +latecomers +lated +lately +latency +lateness +latent +later +lateral +laterally +laterals +lateran +latest +latex +lath +latham +lathan +lathe +lather +lathered +lathers +lathrop +latimer +latin +latina +latinate +latino +latinos +latins +latitude +latitudes +latitudinal +latour +latrine +latrines +latrobe +lats +latter +lattice +lattices +latticework +lattimore +latvia +latvian +lau +laub +lauch +laud +laudable +laudanum +laudatory +laude +lauded +lauder +lauderdale +lauding +lauds +lauer +laugh +laughable +laughably +laughed +laugher +laughing +laughingly +laughingstock +laughlin +laughs +laughter +launch +launched +launcher +launchers +launches +launching +launchings +launchpad +launder +laundered +launderer +launderers +laundering +laundries +laundromat +laundry +laur +laura +laurance +laureate +laureates +laurel +laurels +lauren +laurence +laurent +laurentian +lauri +laurie +laurin +lauro +lausanne +lauter +laux +lava +laval +lavan +lavatories +lavatory +lave +lavell +lavelle +lavender +laver +lavergne +laverne +laverty +lavey +lavigne +lavin +lavinia +lavish +lavished +lavishes +lavishing +lavishly +lavoro +law +lawbreaker +lawbreakers +lawbreaking +lawful +lawfully +lawfulness +lawler +lawless +lawlessness +lawmaker +lawmakers +lawmaking +lawman +lawmen +lawn +lawnmower +lawnmowers +lawns +lawrence +lawrenceville +lawry +laws +lawson +lawsuit +lawsuits +lawton +lawyer +lawyering +lawyerly +lawyers +lax +laxative +laxatives +laxer +laxity +lay +layaway +layed +layer +layered +layering +layers +laying +layman +laymen +layne +layoff +layoffs +layout +layouts +layover +layperson +lays +layton +layup +lazar +lazard +lazare +lazaro +lazarus +lazed +lazenby +lazer +lazier +laziest +lazily +laziness +lazio +lazo +lazuli +lazy +lazzaro +lb +lbs +lc +lcs +ld +le +lea +leach +leached +leaches +leaching +leachman +lead +leadbetter +leaded +leaden +leader +leaderless +leaders +leadership +leaderships +leading +leadoff +leads +leaf +leafed +leafhoppers +leafing +leafless +leaflet +leaflets +leafs +leafy +league +leaguer +leaguers +leagues +leah +leahy +leak +leakage +leake +leaked +leaker +leaking +leaks +leaky +leal +lean +leander +leandro +leaned +leaner +leanest +leaning +leanings +leans +leant +leap +leaped +leaper +leapfrog +leapfrogged +leapfrogging +leaping +leaps +leapt +lear +learjet +learn +learned +learner +learners +learning +learns +learnt +leary +leas +lease +leaseback +leased +leasehold +leaseholders +leaseholds +leases +leash +leashed +leashes +leasing +least +leat +leather +leatherback +leatherman +leathers +leatherwood +leathery +leave +leaved +leaven +leavened +leavening +leavens +leavenworth +leaver +leaves +leaving +leavings +leavitt +leavy +lebanese +lebanon +lebaron +lebeau +leben +leber +leblanc +lebow +lebowitz +lebrecht +lebrun +lech +leche +lecher +lecherous +lechmere +lechner +lecithin +leck +leclair +leclerc +lectern +lector +lecture +lectured +lecturer +lecturers +lectures +lecturing +led +leda +ledbetter +ledeen +lederer +lederle +lederman +ledesma +ledge +ledger +ledgers +ledges +ledoux +leds +ledyard +lee +leech +leeches +leed +leeds +leek +leeks +leen +leer +leering +leers +leery +lees +leesburg +leeson +leet +leetle +leeward +leeway +lefebvre +lefever +lefevre +left +lefthand +lefthanded +lefties +leftish +leftism +leftist +leftists +leftover +leftovers +lefts +leftward +leftwing +lefty +leg +lega +legacies +legacy +legal +legalese +legalism +legalistic +legalities +legality +legalization +legalize +legalized +legalizes +legalizing +legally +legato +lege +legend +legendary +legends +leger +legerdemain +legere +legg +legge +legged +legging +leggings +leggy +leghorn +legibility +legible +legibly +legion +legionnaire +legionnaires +legions +legislate +legislated +legislates +legislating +legislation +legislative +legislatively +legislator +legislators +legislature +legislatures +legit +legitimacy +legitimate +legitimately +legitimization +legitimize +legitimized +legitimizes +legitimizing +legless +lego +legrand +legroom +legros +legs +legume +legumes +legwork +lehigh +lehman +lehmann +lehnert +lehr +lehrer +lehrman +lehtinen +lei +leiber +leibniz +leibovitz +leibowitz +leicester +leiden +leif +leigh +leighton +leila +lein +leipzig +leis +leishman +leisure +leisurely +leiter +leith +leitmotif +leitmotiv +leitz +lek +leland +leman +lemay +lemke +lemma +lemme +lemming +lemmings +lemmon +lemon +lemonade +lemond +lemons +lemony +lemur +lemurs +len +lena +lend +lender +lenders +lending +lends +lene +leng +length +lengthen +lengthened +lengthening +lengthens +lengthier +lengths +lengthwise +lengthy +leniency +lenient +leniently +lenin +leningrad +leninism +leninist +lennart +lennon +lennox +lenny +leno +lenora +lenore +lenox +lens +lense +lenses +lent +lenten +lenticular +lentil +lentils +lentini +lento +leo +leominster +leon +leona +leonard +leonardo +leone +leong +leonhardt +leoni +leonid +leonidas +leonine +leopard +leopards +leopold +leora +leos +leotard +lepage +leper +lepers +lepley +leppard +leprechaun +leprechauns +leprosy +lerman +lerner +leroux +leroy +les +lesa +lesbian +lesbianism +lesbians +lesher +lesion +lesions +lesley +leslie +lesotho +less +lessard +lessee +lessees +lessen +lessened +lessening +lessens +lesser +lesson +lessons +lessor +lessors +lest +lestat +lester +let +leta +letdown +lethal +lethality +lethargic +lethargy +leticia +letitia +letizia +leto +letourneau +lets +lett +letter +lettered +letterhead +lettering +letterman +letters +lettice +letting +lettuce +letty +letup +leu +leucadia +leukemia +leukocyte +leukocytes +leumi +leung +lev +levant +leve +levee +levees +level +leveled +leveling +levelled +levels +levene +levenson +leventhal +lever +leverage +leveraged +leverages +leveraging +levering +levers +levesque +levey +levi +leviathan +levied +levies +levin +levine +levinson +levis +levitan +levitate +levitated +levitating +levitation +leviticus +levitsky +levitt +levittown +levity +levitz +levon +levy +levying +lew +lewd +lewin +lewis +lewiston +lewitt +lewy +lex +lexical +lexicographer +lexicon +lexicons +lexington +lexis +lexus +ley +leyden +leyland +leyva +lf +lg +lhasa +li +lia +liabilities +liability +liable +liaising +liaison +liaisons +liam +lian +liana +liane +liang +liao +liar +liars +liason +lib +libation +libations +libby +libel +libelous +liberal +liberalism +liberality +liberalization +liberalize +liberalized +liberalizing +liberally +liberals +liberate +liberated +liberates +liberating +liberation +liberator +liberators +liberia +liberman +libertarian +libertarianism +libertarians +liberties +libertine +liberty +libido +libra +librarian +librarians +libraries +library +libration +libre +librettist +libretto +libs +libya +libyan +libyans +lice +licence +licenced +licences +licencing +license +licensed +licensee +licensees +licenses +licensing +licensor +licensure +licentious +lichen +lichens +lichtenberg +lichtenstein +lick +licked +lickety +licking +licks +licorice +lid +liddell +liddle +liddy +lidia +lido +lids +lie +lieb +liebe +lieber +lieberman +liebermann +liebling +liebman +liebowitz +liechtenstein +lied +lieder +lief +liege +lien +liens +lies +lieu +lieut +lieutenant +lieutenants +lif +life +lifeblood +lifeboat +lifeboats +lifeguard +lifeguards +lifeless +lifelike +lifeline +lifelines +lifelong +lifer +lifers +lifes +lifesaver +lifesavers +lifesaving +lifespan +lifestyle +lifestyles +lifetime +lifetimes +liff +lifo +lift +lifted +lifter +lifters +lifting +liftoff +lifts +ligament +ligaments +ligand +ligation +ligatures +liggett +light +lightbulb +lightbulbs +lighted +lighten +lightened +lightening +lighter +lighters +lightest +lightfoot +lightheaded +lightheadedness +lighthearted +lighthouse +lighthouses +lighting +lightly +lightness +lightning +lightnings +lights +lightship +lightweight +ligne +lignin +lignite +lika +likable +like +likeable +liked +likelier +likeliest +likelihood +likely +liken +likened +likeness +likenesses +likening +likens +likes +likewise +liking +liklihood +likud +lila +lilac +lilacs +lile +liles +lili +lilia +lilian +lilies +lilith +lille +lillehammer +lilley +lillian +lillie +lillis +lilly +lilt +lily +lim +lima +liman +limas +limb +limbaugh +limbed +limber +limbless +limbo +limbs +limburger +lime +limehouse +limelight +limerick +limericks +limes +limestone +limey +liming +limit +limitation +limitations +limited +limiter +limiters +limiting +limitless +limits +limo +limon +limos +limousine +limousines +limp +limped +limpid +limping +limps +lims +lin +lina +linares +linc +linchpin +lincoln +lincolns +lincolnshire +lind +linda +lindane +lindauer +lindberg +lindbergh +linde +lindell +lindeman +linden +linder +lindgren +lindh +lindholm +lindley +lindner +lindo +lindquist +lindsay +lindsey +lindy +line +lineage +lineages +lineal +linear +linearity +linearly +linebacker +linebackers +lined +lineman +linemen +linen +linens +liner +liners +lines +linesman +linesmen +lineup +lineups +linford +ling +linger +lingered +lingerie +lingering +lingers +lingo +lingua +lingual +linguist +linguistic +linguistically +linguistics +linguists +linh +lining +linings +link +linkage +linkages +linke +linked +linker +linking +links +linkup +linley +linn +linnell +linnet +linney +lino +linoleic +linoleum +linotype +lins +linseed +linsey +lint +lintel +lintner +linton +linus +linwood +linz +lion +lionel +lioness +lionheart +lionized +lions +liotta +lip +lipa +lipari +lipid +lipids +lipinski +lipman +lipoprotein +lipoproteins +liposuction +lippard +lippe +lipped +lipper +lippert +lippi +lippy +lips +lipsky +lipstick +lipsticks +lipton +liquefaction +liquefied +liquefy +liqueur +liqueurs +liquid +liquidate +liquidated +liquidates +liquidating +liquidation +liquidations +liquidator +liquidators +liquide +liquidity +liquids +liquified +liquor +liquors +lira +lire +lis +lisa +lisbon +lise +lisette +lish +lisk +lisle +lisp +liss +lissa +list +listed +listen +listened +listener +listeners +listenership +listening +listens +lister +listeria +listing +listings +listless +listlessly +liston +lists +liszt +lit +litan +litanies +litany +litchfield +lite +litem +liter +literacy +literal +literalism +literally +literalness +literary +literate +literati +literature +literatures +liters +lites +lithe +lithgow +lithium +lithograph +lithographic +lithographs +lithography +lithuania +lithuanian +lithuanians +litigant +litigants +litigate +litigated +litigating +litigation +litigations +litigator +litigators +litigious +litle +litmus +litre +litres +lits +litt +litter +littered +littering +litters +little +littlefield +littlejohn +littler +littles +littlest +littleton +littman +litton +littoral +liturgical +liturgies +liturgy +liu +liv +livable +live +liveable +lived +livelier +liveliest +livelihood +livelihoods +liveliness +lively +liven +livened +liver +livered +livermore +liverpool +livers +livery +lives +livestock +livia +livid +living +livingroom +livings +livingston +livingstone +liz +liza +lizard +lizards +lizzie +lizzy +ll +llama +llamas +llano +llanos +llewellyn +llosa +lloyd +lloyds +lm +ln +lo +loa +loach +load +loaded +loader +loaders +loading +loadings +loads +loaf +loafer +loafers +loam +loamy +loan +loaned +loaner +loaning +loans +loath +loathe +loathed +loathing +loathsome +loaves +lob +lobbed +lobbied +lobbies +lobbing +lobby +lobbying +lobbyist +lobbyists +lobe +lobel +lobes +lobo +lobos +lobotomy +lobs +lobster +lobsters +loc +loca +local +locale +locales +localities +locality +localization +localize +localized +locally +locals +locate +located +locates +locating +location +locational +locations +locator +locators +loch +loci +lock +lockable +lockdown +locke +locked +locker +lockers +lockhart +lockheed +locking +locklear +lockout +lockridge +locks +locksmith +lockstep +lockup +lockyer +loco +locomotion +locomotive +locomotives +locos +locus +locust +locusts +locution +lode +loden +lodestar +lodestone +lodge +lodged +lodgepole +lodges +lodging +lodgings +lodi +loe +loeb +loess +loews +loft +loftier +lofton +lofts +loftus +lofty +log +logan +logarithmic +logarithms +logbook +loge +logged +logger +loggerheads +loggers +loggia +logging +logic +logica +logical +logically +logician +login +logistic +logistical +logistically +logistics +logitech +logjam +logjams +logo +logos +logs +logue +lohengrin +lohman +lohr +loin +loincloth +loins +loire +lois +loiselle +loiter +loitering +loke +lola +lolita +loll +lollapalooza +lolling +lollipop +lollipops +lolly +loma +loman +lomas +lomax +lomb +lombard +lombardi +lombardo +lompoc +london +londonderry +londoner +londoners +londono +lone +loneliness +lonely +loner +lonergan +loners +lonesome +long +longacre +longan +longboat +longbow +longed +longer +longest +longevity +longfellow +longhair +longhand +longhorn +longhorns +longhouse +longing +longingly +longings +longish +longitude +longitudes +longitudinal +longleaf +longley +longman +longmont +longo +longs +longshore +longshoremen +longshot +longstanding +longterm +longtime +longview +longwood +longworth +loni +lonnie +lonny +loo +look +lookalike +looked +looker +looking +lookit +lookout +looks +lookup +loom +loomed +looming +loomis +looms +loon +looney +loonies +loons +loony +loop +looped +looper +loopers +loophole +loopholes +looping +loops +loopy +loos +loose +loosed +loosely +loosen +loosened +looseness +loosening +loosens +looser +looses +loosest +loosing +loot +looted +looter +looters +looting +lop +lope +loper +lopes +lopez +lopped +lopping +lops +lopsided +loquacious +loquat +loquitur +lor +lora +loraine +loral +loran +lord +lording +lords +lordship +lordy +lore +lorelei +loren +lorentz +lorenz +lorenzo +loreto +loretta +lori +loria +lorimer +lorin +loring +loris +lorna +lorne +loro +lorraine +lorrie +lorries +lorry +lorsch +lory +los +lose +loser +losers +loses +losh +losing +loss +losses +lost +lot +loth +lothian +lotion +lotions +lots +lott +lotta +lotte +lotteries +lottery +lottie +lotto +lotus +lotz +lou +louche +loud +louder +loudest +loudly +loudmouth +loudon +loudspeaker +loudspeakers +louella +lough +loughlin +loughran +loughrey +louie +louis +louisa +louise +louisiana +louisville +lounge +lounges +lounging +loup +loupe +lourdes +lourie +louse +lousy +louts +louvre +louw +lovable +love +loveable +lovebirds +loved +loveday +lovejoy +lovelace +loveless +lovell +lovely +lovemaking +lover +lovers +loves +lovesick +lovett +lovey +lovin +loving +lovingly +low +lowder +lowdown +lowe +lowed +lowell +lowenstein +lowenthal +lower +lowercase +lowered +lowering +lowers +lowery +lowes +lowest +lowing +lowland +lowlands +lowlife +lowly +lowman +lown +lowrey +lowry +lows +lowther +lowy +lox +loy +loya +loyal +loyalist +loyalists +loyally +loyalties +loyalty +loyd +loyola +lozada +lozenge +lozenges +lp +ls +lt +ltd +lu +luau +luba +lubbers +lubbock +lube +lublin +lubricant +lubricants +lubricate +lubricated +lubricating +lubrication +luby +luc +luca +lucas +lucasfilm +lucca +luce +lucent +lucerne +lucero +lucia +lucian +luciano +lucid +lucida +lucidity +lucie +lucien +lucifer +lucille +lucinda +lucio +lucite +lucius +luck +luckier +luckiest +luckily +luckless +lucks +lucky +lucrative +lucratively +lucy +lud +ludden +luddite +luddites +ludicrous +ludicrously +ludlow +ludlum +ludwick +ludwig +lufkin +lufthansa +lug +lugano +lugar +luge +luggage +lugging +lugo +lugs +lugubrious +luigi +luis +luisa +luiz +lujan +lukacs +lukasiewicz +luke +lukewarm +lula +luling +lull +lullabies +lullaby +lulled +lulling +lulls +lulu +lum +lumbago +lumbar +lumber +lumbered +lumbering +lumberjack +lumberjacks +lumen +lumina +luminance +luminaries +luminary +luminescence +luminescent +luminosity +luminous +lump +lumpectomy +lumped +lumping +lumpkin +lumps +lumpur +lumpy +lun +luna +lunacy +lunar +lunatic +lunatics +lunch +lunched +luncheon +luncheonette +luncheons +lunches +lunching +lunchroom +lunchtime +lund +lundberg +lundgren +lundy +lung +lunge +lunged +lunges +lunging +lungs +lunn +lunt +luong +lupa +lupe +lupin +lupine +lupo +lupton +lupus +lurch +lurched +lurches +lurching +lure +lured +lures +lurid +lurie +luring +lurk +lurked +lurking +lurks +lusaka +lusardi +luscious +lush +lusher +lushly +lusk +luskin +lussier +lust +lusted +luster +lustful +lustig +lusting +lustre +lustrous +lusts +lusty +lute +luteal +luther +lutheran +lutherans +lutz +lux +luxe +luxembourg +luxemburg +luxor +luxuriant +luxuriating +luxuries +luxurious +luxuriously +luxury +luzhkov +lv +lxi +ly +lyall +lybrand +lycee +lyceum +lycoming +lycra +lydia +lydon +lye +lying +lyke +lyle +lyles +lyman +lyme +lymph +lymphatic +lymphocyte +lymphocytes +lymphoma +lymphomas +lyn +lynch +lynchburg +lynched +lynching +lynchings +lynd +lynda +lynde +lynden +lyndhurst +lyndon +lyne +lynette +lynn +lynne +lynx +lyon +lyonnais +lyonnaise +lyons +lyra +lyre +lyric +lyrical +lyrically +lyricism +lyricist +lyricists +lyrics +lys +lysander +lysine +lysis +lysol +lytle +lytton +ma +maarten +maas +maass +maastricht +mab +mabel +mabey +mable +mac +macabre +macadam +macadamia +macao +macaques +macaroni +macarthur +macartney +macau +macauley +macaw +macaws +macbeth +macbride +maccabean +maccabees +macdonald +macdougal +macdowell +mace +macedonia +macedonian +macedonians +macfarlane +macgregor +mach +machado +machel +machen +machete +machetes +machiavelli +machiavellian +machin +machina +machination +machinations +machine +machined +machineries +machinery +machines +machining +machinist +machinists +machismo +macho +macias +macintosh +macintoshes +macintyre +mack +mackay +mackenzie +mackerel +mackey +mackie +mackin +mackinaw +mackinnon +mackintosh +macks +maclachlan +maclaine +maclaren +maclean +macleans +maclennan +macleod +macmahon +macmillan +macmurray +macneil +macomb +macon +macphail +macpherson +macquarie +macrae +macro +macroeconomic +macroeconomics +macromedia +macrophage +macros +macrovision +macs +macular +macworld +macy +mad +madagascar +madalena +madam +madame +madcap +madd +madden +maddened +maddening +maddeningly +madder +madding +maddison +maddock +maddox +maddux +maddy +made +madeira +madeleine +madeline +mademoiselle +maden +madera +madero +madge +madhouse +madigan +madison +madly +madman +madmen +madness +madonna +madras +madre +madrid +madrigal +madrigals +madrone +mads +madsen +mae +maeda +maelstrom +maersk +maes +maestri +maestro +maestros +maffei +mafia +mafias +mag +magana +magazine +magaziner +magazines +magda +magdalen +magdalena +magdalene +mage +magee +magellan +magenta +mages +maggie +maggiore +maggot +maggots +maggs +magi +magic +magical +magically +magician +magicians +magid +maginnis +maginot +magisterial +magistrate +magistrates +magma +magna +magnanimity +magnanimous +magnate +magnates +magnesia +magnesite +magnesium +magness +magnet +magnetic +magnetically +magnetics +magnetism +magnetite +magnetized +magneto +magnetometer +magnetometers +magnetron +magnets +magnification +magnificence +magnificent +magnificently +magnifico +magnified +magnifier +magnifies +magnifique +magnify +magnifying +magnitude +magnitudes +magnolia +magnolias +magnum +magnums +magnus +magnusson +magowan +magpie +magruder +mags +maguire +magus +magyar +magyars +mah +maha +mahajan +mahal +mahan +maharaj +maharaja +maharashtra +maharishi +mahathir +mahatma +mahayana +mahdi +mahendra +maher +mahesh +mahfouz +mahler +mahmood +mahmoud +mahmud +mahogany +mahon +mahone +mahoney +mahonia +mai +maia +maid +maida +maiden +maidenhead +maidens +maids +maier +mail +mailbag +mailbox +mailboxes +mailed +mailer +mailers +mailing +mailings +maillet +mailman +mailroom +mails +maim +maimed +maiming +maims +main +maine +mainframe +mainframes +mainichi +mainland +mainlanders +mainline +mainly +mains +mainsail +mainspring +mainstay +mainstays +mainstream +mainstreaming +mainstreet +maintain +maintainability +maintainance +maintained +maintainer +maintaining +maintains +maintenance +mainz +maio +mair +maire +mais +maisel +maison +maitland +maitre +maitres +maize +maj +majestic +majestically +majesties +majesty +majeure +majid +major +majored +majoring +majoritarian +majorities +majority +majors +mak +make +makeover +makepeace +maker +makers +makes +makeshift +maketh +makeup +maki +makin +making +makings +makinson +mako +mal +mala +malabar +malacca +malachi +malachite +maladies +maladjusted +maladroit +malady +malaga +malagasy +malaise +malamud +malan +malaprop +malaria +malarial +malarkey +malathion +malawi +malay +malayan +malays +malaysia +malaysian +malaysians +malcolm +malcom +malcontent +malcontents +malden +maldives +maldonado +male +malefactor +malefactors +malek +maleness +maler +males +malevolence +malevolent +maley +malfeasance +malformation +malformations +malformed +malfunction +malfunctioned +malfunctioning +malfunctions +malhotra +mali +malibu +malice +malicious +maliciously +malign +malignancies +malignancy +malignant +maligned +malik +malina +malingering +malinowski +malkiel +malkin +malkovich +mall +mallard +mallards +malleability +malleable +mallet +mallett +malley +mallinckrodt +mallon +mallory +mallow +mallows +malloy +malls +mally +malm +malmo +malnourished +malnutrition +malo +malodorous +malone +maloney +malouf +malpractice +malt +malta +malted +malter +maltese +maltose +maltreated +maltreatment +malts +maltz +malvern +mam +mama +mamas +mamba +mambo +mameluke +mamie +mamma +mammal +mammalian +mammals +mammary +mammas +mammogram +mammograms +mammography +mammon +mammoth +mammoths +man +manage +manageable +managed +management +managements +manager +managerial +managers +manages +managing +managment +managua +manasseh +manatee +manatees +mancha +manchester +manchu +manchuria +mancini +mancino +mancuso +mand +mandalay +mandamus +mandarin +mandarins +mandate +mandated +mandates +mandating +mandatorily +mandatory +mandel +mandela +mandelbaum +mandell +mander +manders +manderson +mandeville +mandible +mandl +mandola +mandolin +mandrake +mandy +mane +maned +manes +manet +maneuver +maneuverability +maneuvered +maneuvering +maneuvers +manford +manfred +manfully +mang +manga +manganese +mange +mangel +mangels +mangement +manger +mangers +manges +mangle +mangled +mangles +mangling +mango +mangoes +mangold +mangos +mangrove +mangy +manhandle +manhandled +manhandling +manhattan +manhattans +manhole +manholes +manhood +manhours +manhunt +mani +mania +maniac +maniacal +maniacally +maniacs +manias +manic +manicure +manicured +manicurist +manifest +manifestation +manifestations +manifested +manifesting +manifestly +manifesto +manifests +manifold +manifolds +manila +manilow +manipulate +manipulated +manipulates +manipulating +manipulation +manipulations +manipulative +manipulator +manipulators +manis +manish +manitoba +manitowoc +mankind +manley +manliness +manly +manmade +mann +manna +manned +mannequin +mannequins +manner +mannered +mannerism +mannerisms +mannerist +manners +mannes +mannesmann +mannheim +mannie +manning +mannish +manno +manny +mano +manoir +manolo +manor +manors +manos +manpower +manrique +mans +manse +manser +mansfield +mansion +mansions +manslaughter +manso +manson +mansour +mansur +manta +mantel +mantilla +mantis +mantle +mantles +mantra +mantras +mantua +manu +manual +manually +manuals +manuel +manuela +manufacture +manufactured +manufacturer +manufacturers +manufactures +manufacturing +manure +manus +manuscript +manuscripts +manville +manx +many +manzanillo +manzi +manzo +mao +maoism +maoist +maori +maoris +map +mapes +maple +maples +maplewood +mapp +mapped +mapping +maps +maputo +mar +mara +marais +maras +maraschino +marathi +marathon +marathoner +marathons +marauder +marauders +marauding +maravilla +marbella +marble +marbled +marblehead +marbles +marbury +marc +marceau +marcel +marcella +marcello +marcellus +marcelo +march +marchand +marched +marchers +marches +marchesi +marchetti +marchi +marching +marci +marcia +marcial +marciano +marcie +marco +marconi +marcos +marcus +marcy +mardi +mare +marek +mares +marfa +marg +marga +margaret +margarine +margarita +margaritas +margarite +margaux +marge +margery +margie +margin +marginal +marginalize +marginalized +marginally +marginals +margined +margins +margo +margolin +margolis +margot +marguerite +margulies +mari +maria +mariachi +mariachis +mariah +mariam +marian +mariana +marianas +mariani +marianna +marianne +mariano +marie +mariel +marietta +marigold +marigolds +marijuana +marilyn +marimba +marin +marina +marinade +marinades +marinara +marinas +marinate +marinated +marine +mariner +mariners +marines +marini +marino +mario +marion +marionette +mariposa +maris +marisa +mariscal +marissa +marist +marital +maritime +maritimes +marius +marjorie +mark +markdown +markdowns +marke +marked +markedly +markel +marker +markers +market +marketability +marketable +marketed +marketeer +marketeers +marketer +marketers +marketing +marketplace +marketplaces +markets +markey +markham +markie +marking +markings +markka +markle +markley +markman +marko +markoff +markov +markowitz +marks +marksman +marksmanship +marksmen +markup +markups +markus +marky +marla +marlboro +marlborough +marlena +marlene +marler +marley +marlin +marlins +marlon +marlow +marlowe +marly +marmalade +marmalades +marmara +marmion +marmon +marmor +marmoset +marmot +marmots +maro +maroc +maroon +marooned +marquand +marquardt +marque +marquee +marquees +marques +marquess +marquetry +marquette +marquez +marquis +marquise +marr +marra +marrakech +marred +marriage +marriageable +marriages +married +marrieds +marries +marring +marriott +marron +marrow +marry +marrying +mars +marsala +marsalis +marsden +marse +marseillaise +marseille +marseilles +marsh +marsha +marshal +marshaled +marshall +marshalled +marshalling +marshalls +marshals +marshes +marshfield +marshland +marshlands +marshmallow +marshmallows +marshy +marsteller +marston +marsupial +mart +marta +martel +martell +martello +marten +martens +martha +marti +martial +martials +martian +martians +martin +martina +martindale +martine +martineau +martinez +martingale +martini +martinique +martinis +martino +martins +martinsville +marts +marty +martyn +martyr +martyrdom +martyred +martyrs +martz +marubeni +marv +marvel +marvell +marvelled +marvellous +marvelous +marvelously +marvels +marvin +marwick +marx +marxism +marxist +marxists +mary +maryann +maryanne +maryland +marymount +marys +marysville +marzipan +mas +masa +masai +masaru +masayuki +mascara +mascot +mascots +masculine +masculinity +masek +maser +mash +masha +mashed +masher +mask +masked +maskell +masking +masks +maslin +masochism +masochist +masochistic +mason +masonic +masonite +masonry +masons +masood +masque +masquerade +masquerades +masquerading +masri +mass +massa +massachusetts +massachussetts +massacre +massacred +massacres +massacring +massage +massaged +massager +massages +massaging +massaro +masse +massed +masses +masseur +masseurs +masseuse +masseuses +massey +massif +massimo +massing +massive +massively +masson +mast +mastectomy +masted +master +mastercard +mastered +masterful +masterfully +mastering +masterly +mastermind +masterminded +masterminding +masterminds +masterpiece +masterpieces +masters +masterson +masterstroke +masterwork +masterworks +mastery +masthead +mastic +mastiff +masts +masturbate +masturbated +masturbating +masturbation +masur +mat +mata +matador +matadors +matagorda +matalin +matamoros +match +matchbook +matchbox +matched +matches +matching +matchless +matchmaker +matchmakers +matchmaking +matchup +matchups +mate +mated +mateo +mater +material +materialism +materialist +materialistic +materialists +materiality +materialize +materialized +materializes +materializing +materially +materials +materiel +maternal +maternity +mates +matey +math +mathe +mathematic +mathematica +mathematical +mathematically +mathematician +mathematicians +mathematics +mather +mathers +mathes +matheson +mathew +mathews +mathewson +mathias +mathiesen +mathieson +mathieu +mathis +maths +mathur +matias +matic +matilda +matinee +matinees +mating +matisse +matlack +matlock +mato +matos +matra +matriarch +matriarchal +matrices +matrilineal +matrimonial +matrimony +matrix +matron +matronly +matrons +mats +matson +matsui +matsumoto +matsushita +matt +matta +matte +matted +mattel +matteo +matter +mattered +matters +mattes +matthew +matthews +matthey +matthias +mattias +mattie +matting +mattingly +mattison +mattox +mattress +mattresses +mattson +matty +maturation +mature +matured +maturely +matures +maturing +maturities +maturity +matz +matza +matzo +mau +maud +maude +maudlin +maui +maul +maulana +mauldin +mauled +mauling +maumee +mauna +maung +maura +maureen +maurer +maurice +mauricio +mauritania +mauritanian +mauritius +mauro +maury +maus +mausoleum +mauve +maven +mavens +maverick +mavericks +mavis +maw +mawkish +mawr +max +maxed +maxey +maxfield +maxi +maxie +maxim +maxima +maximal +maximalist +maximally +maxime +maximilian +maximise +maximization +maximize +maximized +maximizer +maximizes +maximizing +maxims +maximum +maximums +maximus +maxine +maxon +maxtor +maxwell +maxx +may +maya +mayan +mayans +maybe +mayberry +mayday +mayer +mayers +mayfair +mayfield +mayflower +mayhem +mayhew +mayland +maynard +mayne +mayo +mayonnaise +mayor +mayoral +mayoralty +mayorga +mayors +mayotte +mayr +mays +maytag +mazda +maze +mazel +mazer +mazes +mazur +mazurka +mazurkas +mazza +mazzone +mb +mbeki +mbps +mc +mcadam +mcadams +mcafee +mcallen +mcallister +mcalpine +mcardle +mcarthur +mcatee +mcauley +mcauliffe +mcbain +mcbeth +mcbride +mccabe +mccafferty +mccaffrey +mccain +mccall +mccallum +mccance +mccann +mccarney +mccarran +mccarron +mccarter +mccarthy +mccarthyism +mccarthyite +mccartney +mccarty +mccauley +mccaw +mcclanahan +mcclean +mcclellan +mcclelland +mcclintock +mccloskey +mccloud +mccloy +mcclure +mccoll +mccollum +mccomb +mcconnell +mcconville +mccool +mccord +mccormack +mccormick +mccourt +mccown +mccoy +mccoys +mccracken +mccraw +mccray +mccrea +mccreary +mccree +mccrory +mccue +mccullers +mcculloch +mccullough +mccully +mccurdy +mccurry +mccutcheon +mcdade +mcdaniel +mcdermott +mcdevitt +mcdonagh +mcdonald +mcdonalds +mcdonnel +mcdonnell +mcdonough +mcdougal +mcdougall +mcdowall +mcdowell +mcduff +mceachern +mcelroy +mcenroe +mcentee +mcentire +mcevoy +mcewan +mcewen +mcfadden +mcfarland +mcfarlane +mcg +mcgann +mcgarry +mcgarvey +mcgaughey +mcgaw +mcgee +mcgeehan +mcgeorge +mcghee +mcgill +mcgillis +mcginley +mcginn +mcginnis +mcginniss +mcginty +mcglynn +mcgough +mcgovern +mcgowan +mcgrady +mcgrath +mcgraw +mcgreevy +mcgregor +mcgrew +mcgriff +mcgrory +mcguffin +mcguigan +mcguinness +mcguire +mcgurn +mcgwire +mchale +mchenry +mchugh +mcilvaine +mcilwain +mcinerney +mcinnis +mcintosh +mcintyre +mciver +mckay +mckean +mckechnie +mckee +mckeever +mckelvey +mckenna +mckenzie +mckeon +mckillop +mckim +mckinley +mckinney +mckinnon +mckinsey +mckittrick +mcknight +mclachlan +mclain +mclane +mclaren +mclarty +mclaughlin +mclean +mcleish +mclennan +mcleod +mcmahon +mcmanus +mcmaster +mcmath +mcmichael +mcmillan +mcmillin +mcminn +mcmoran +mcmullen +mcmurdo +mcmurray +mcmurtry +mcnab +mcnabb +mcnair +mcnall +mcnally +mcnamara +mcnamee +mcnary +mcnaughton +mcnay +mcnealy +mcneese +mcneil +mcneill +mcnulty +mcnutt +mcphail +mcphee +mcpherson +mcquade +mcquaid +mcquay +mcqueen +mcrae +mcreynolds +mcshane +mcsweeney +mctaggart +mcteer +mcvay +mcveigh +mcvey +mcwilliams +md +me +mea +mead +meade +meadow +meadowlands +meadowlark +meadows +meager +meagher +meal +meals +mealtime +mealy +mean +meander +meandered +meandering +meanders +meaner +meanest +meaney +meanie +meaning +meaningful +meaningfully +meaningless +meaninglessness +meanings +meanness +means +meant +meantime +meanwhile +meany +mears +measles +measly +measurable +measurably +measure +measured +measurement +measurements +measurer +measures +measuring +meat +meatball +meatballs +meath +meathead +meatier +meatless +meatloaf +meatpacking +meats +meaty +meaux +mecca +meccas +mech +mecham +mechanic +mechanical +mechanically +mechanicals +mechanics +mechanicsburg +mechanism +mechanisms +mechanistic +mechanization +mechanized +mecklenburg +med +meda +medal +medalist +medalists +medallion +medallions +medals +meddle +meddler +meddlesome +meddling +mede +medea +medellin +medford +medgar +medi +media +mediaeval +medial +median +medians +medias +mediate +mediated +mediating +mediation +mediations +mediator +mediators +medic +medica +medicaid +medical +medically +medicare +medicate +medicated +medication +medications +medici +medicinal +medicine +medicines +medico +medics +medieval +medina +medio +mediocre +mediocrities +mediocrity +meditate +meditates +meditating +meditation +meditations +meditative +mediterranean +medium +mediums +medlar +medley +medlock +medtronic +medulla +medusa +medved +medvedev +mee +meed +meehan +meek +meeker +meekly +meekness +meeks +meer +mees +meese +meet +meeting +meetings +meets +meg +mega +megabit +megabits +megabucks +megabyte +megabytes +megahertz +megalomania +megalomaniac +megalomaniacal +megalopolis +megan +megaphone +megaphones +megastore +megastores +megatons +megawatt +megawatts +megs +mehdi +mehmet +mehta +mei +meier +meiji +meikle +mein +meir +meisner +meissner +meister +mejia +mekong +mel +melamed +melamine +melancholic +melancholy +melander +melanesian +melanesians +melange +melanie +melanin +melanoma +melatonin +melba +melbourne +melby +melchizedek +meld +melded +melding +meldrum +mele +melee +melendez +melin +melinda +melissa +melita +mell +melle +mellen +meller +mellifluous +melling +mellish +mellitus +mello +mellon +mellow +mellowed +mellowing +mellowness +melman +melnick +melo +melodic +melodies +melodious +melodrama +melodramas +melodramatic +melody +melon +melons +melrose +melt +meltdown +melted +melting +melton +melts +meltwater +meltzer +melville +melvin +melvyn +melzer +mem +member +members +membership +memberships +membrane +membranes +membranous +meme +memento +mementoes +mementos +memo +memoir +memoirs +memorabilia +memorable +memorably +memoranda +memorandum +memorandums +memorex +memoria +memorial +memorialize +memorialized +memorials +memoriam +memories +memorization +memorize +memorized +memorizing +memory +memos +memphis +men +mena +menace +menaces +menachem +menacing +menacingly +menagerie +menard +mencken +mend +mendacity +mended +mendel +mendell +mendelsohn +mendelson +mendelssohn +mendes +mendez +mending +mendocino +mendoza +mends +menem +menendez +menfolk +meng +mengele +menger +menial +menil +meningitis +meniscus +menlo +mennonite +mennonites +meno +menominee +menon +menopausal +menopause +menorah +mens +mensa +mensch +menstrual +menstruation +menswear +ment +mental +mentality +mentally +menthol +mention +mentioned +mentioning +mentions +menton +mentor +mentored +mentoring +mentors +mentorship +menu +menus +menzel +menzies +meow +meowed +meows +meps +mer +merc +mercado +mercantile +mercantilism +mercantilist +mercaptan +mercator +merce +merced +mercedes +mercenaries +mercenary +mercer +merch +merchandise +merchandiser +merchandisers +merchandising +merchant +merchantability +merchants +merci +mercier +mercies +merciful +mercifully +merciless +mercilessly +merck +mercosur +mercurial +mercurio +mercury +mercy +mere +meredith +merely +merest +meretz +merge +merged +merger +mergers +merges +merging +merida +meriden +meridian +meridien +merino +merit +merited +meritless +meritocracy +meritocratic +meritorious +merits +meriwether +merk +merkel +merkin +merl +merle +merlin +merlot +mermaid +mermaids +merman +mero +merrell +merriam +merrick +merrie +merrier +merrifield +merrill +merrily +merrimack +merriman +merriment +merrit +merritt +merrow +merry +merryman +mers +merton +merv +mervyn +meryl +merz +mesa +mescalero +mescaline +mesh +meshed +meshes +meshing +mesmerism +mesmerize +mesmerized +mesmerizing +mesolithic +meson +mesopotamia +mesopotamian +mesothelioma +mesquita +mesquite +mess +message +messages +messaging +messed +messenger +messengers +messer +messes +messiah +messiahs +messianic +messier +messina +messing +messner +messrs +messy +mest +mester +mestizo +mestre +met +meta +metabolic +metabolism +metabolites +metabolize +metabolized +metabolizing +metairie +metal +metall +metallic +metallica +metallurgical +metallurgist +metallurgy +metals +metalwork +metalworkers +metalworking +metamorphic +metamorphose +metamorphosis +metaphase +metaphor +metaphorical +metaphorically +metaphors +metaphysical +metaphysically +metaphysics +metastasize +metastasized +metastatic +metcalf +metcalfe +mete +meted +meteor +meteoric +meteorite +meteorites +meteorological +meteorologist +meteorologists +meteorology +meteors +meter +metered +metering +meters +metes +meth +methadone +methamphetamine +methane +methanol +metheny +methinks +method +methodical +methodically +methodism +methodist +methodists +methodological +methodologically +methodologies +methodology +methods +methotrexate +methuselah +methyl +methylene +meticulous +meticulously +meting +metis +metlife +metre +metres +metric +metrical +metrics +metro +metrodome +metromedia +metronome +metroplex +metropolis +metropolitan +metros +mets +mette +mettle +metz +metzenbaum +metzger +metzler +meunier +meuse +mew +mexican +mexicana +mexicano +mexicanos +mexicans +mexico +meyer +meyerowitz +meyers +meyerson +mezzanine +mezzo +mf +mfg +mg +mgm +mgr +mh +mhz +mi +mia +miami +miamisburg +mian +mias +miasma +miata +mib +mic +mica +micah +mice +mich +michael +michaela +michaels +michaelsen +michal +michaux +micheal +michel +michelangelo +michele +michelin +michell +michelle +michelson +michigan +mick +mickelson +mickey +mickie +mickle +micky +micra +micro +microbe +microbes +microbial +microbiologist +microbiology +microchip +microchips +microcircuits +microclimate +microclimates +microcode +microcomputer +microcomputers +microcosm +microeconomic +microeconomics +microelectronic +microelectronics +microfiche +microfilm +micrografx +microgram +micrograms +micromanagement +micrometer +micrometers +micron +micronesia +micronics +microns +microorganism +microorganisms +microphone +microphones +microprocessor +microprocessors +micros +microscope +microscopes +microscopic +microscopically +microscopy +microsecond +microseconds +microsoft +microstructure +microsystems +microwavable +microwave +microwaved +microwaves +mid +midafternoon +midair +midas +midcourse +midday +middle +middlebrook +middleburg +middleman +middlemen +middles +middlesex +middleton +middletown +middleweight +middling +mideast +midfield +midfielder +midge +midges +midget +midgets +midi +midland +midlands +midler +midlife +midline +midmorning +midnight +midori +midpoint +midrange +midriff +midseason +midsection +midshipman +midshipmen +midst +midstream +midsummer +midterm +midterms +midtown +midway +midweek +midwest +midwestern +midwesterner +midwesterners +midwife +midwifery +midwinter +midwives +midyear +mielke +mien +miers +mies +miffed +mifflin +mig +migdal +might +mightier +mightiest +mightily +mighty +mignon +migraine +migraines +migrant +migrants +migrate +migrated +migrates +migrating +migration +migrations +migratory +migs +miguel +mika +mikado +mikael +mike +mikel +mikes +mikey +mikhail +mikulski +mikva +mil +mila +milagro +milam +milan +milanese +milani +milano +milbank +milburn +milch +mild +milder +mildest +mildew +mildly +mildred +mile +mileage +milepost +mileposts +miler +miles +milestone +milestones +miley +milford +milieu +militancy +militant +militantly +militants +militaries +militarily +militarism +militaristic +militarize +militarized +military +militate +militating +militello +militia +militiamen +militias +milk +milked +milken +milking +milkman +milks +milkshake +milkweed +milky +mill +millar +millard +millbank +mille +milled +millen +millenia +millenium +millennia +millennial +millennium +millenniums +miller +millers +millet +millhouse +milli +millie +milligan +milligram +milligrams +milliken +millimeter +millimeters +millimetres +milliner +milling +million +millionaire +millionaires +millions +millionth +millionths +millipede +millipedes +millisecond +milliseconds +millman +mills +millstone +millwood +milly +milne +milner +milnes +milo +milos +milosevic +milpitas +milquetoast +mils +milstein +milt +milton +milwaukee +mim +mime +mimeograph +mimes +mimi +mimic +mimicked +mimicking +mimicry +mimics +mimosa +mims +min +mina +minami +minar +minaret +minarets +minas +mince +minced +mincemeat +minces +mincing +mincy +mind +minda +mindanao +minded +mindedly +mindedness +minden +minder +minders +mindful +minding +mindless +mindlessly +mindlessness +minds +mindset +mindy +mine +mined +minefield +minefields +mineola +miner +mineral +mineralogy +minerals +miners +minerva +mines +minesweeper +ming +minge +mingle +mingled +mingles +mingling +mingus +minh +mini +miniature +miniatures +miniaturist +miniaturization +miniaturize +miniaturized +minibus +minibuses +minicar +minicomputer +minicomputers +minimal +minimalism +minimalist +minimally +minimise +minimization +minimize +minimized +minimizes +minimizing +minimum +minimums +mining +minion +minions +minis +miniscule +miniseries +miniskirt +miniskirts +minister +ministerial +ministering +ministers +ministership +ministration +ministrations +ministries +ministry +minitel +minivan +minivans +mink +minkoff +minks +minneapolis +minnelli +minnesota +minnesotans +minnetonka +minnick +minnie +minnow +minnows +mino +minoan +minolta +minor +minorities +minority +minors +minot +minotaur +minow +mins +minsk +minsky +minster +minstrel +minstrels +mint +minted +minter +minting +mints +minty +mintz +minuet +minus +minuscule +minuses +minute +minutely +minuteman +minutemen +minutes +minutia +minutiae +mips +mir +mira +mirabelle +mirabile +mirabilis +miracle +miracles +miraculous +miraculously +mirador +mirage +mirages +miramar +miramax +miranda +mire +mired +mires +miri +miriam +mirna +miro +miron +mirren +mirror +mirrored +mirroring +mirrors +mirsky +mirth +mirthful +mirv +mirza +mis +misadventure +misadventures +misaligned +misalignment +misallocation +misanthrope +misanthropy +misapplication +misapplied +misapprehension +misappropriated +misappropriating +misappropriation +misbegotten +misbehave +misbehaved +misbehaving +misbehavior +misc +miscalculate +miscalculated +miscalculation +miscalculations +miscarriage +miscarriages +miscast +miscellaneous +miscellany +mischaracterization +mischaracterized +mischief +mischievous +misclassification +misclassified +miscommunication +miscommunications +misconceived +misconception +misconceptions +misconduct +misconstrue +misconstrued +miscounted +miscreant +miscreants +miscue +miscues +misdeed +misdeeds +misdemeanor +misdemeanors +misdiagnosed +misdiagnosis +misdirect +misdirected +misdirection +mise +miser +miserable +miserably +miseries +miserly +misery +misfire +misfires +misfit +misfits +misfortune +misfortunes +misgiving +misgivings +misguided +mish +misha +mishandled +mishandling +mishap +mishaps +misheard +mishkin +mishmash +mishra +misidentification +misidentified +misinform +misinformation +misinformed +misinforming +misinterpret +misinterpretation +misinterpretations +misinterpreted +misinterpreting +misjudge +misjudged +misjudging +misjudgment +misjudgments +mislaid +mislead +misleading +misleadingly +misleads +misled +mismanage +mismanaged +mismanagement +mismanaging +mismatch +mismatched +mismatches +misnomer +miso +misogynist +misogyny +misperception +misperceptions +misplace +misplaced +misplacement +misplayed +misprint +misprints +mispronounced +mispronunciation +misquote +misquoted +misquotes +misread +misreading +misreads +misreported +misrepresent +misrepresentation +misrepresentations +misrepresented +misrepresenting +misrepresents +misrule +miss +missal +missed +misses +misshapen +missile +missiles +missing +mission +missionaries +missionary +missions +mississauga +mississippi +mississippian +missive +missives +missoula +missouri +misspell +misspelled +misspelling +misspellings +misspells +misspent +misstate +misstated +misstatement +misstatements +misstating +misstep +missteps +missus +missy +mist +mistake +mistaken +mistakenly +mistakes +mistaking +mister +misters +mistimed +misting +mistletoe +mistook +mistral +mistranslated +mistranslation +mistreat +mistreated +mistreating +mistreatment +mistreats +mistress +mistresses +mistrial +mistrust +mistrusted +mistrustful +mistry +mists +misty +misunderstand +misunderstanding +misunderstandings +misunderstands +misunderstood +misuse +misused +misuses +misusing +mit +mitch +mitchel +mitchell +mitchum +mite +mitel +miter +mites +mitigate +mitigated +mitigates +mitigating +mitigation +mitra +mitral +mitre +mitsubishi +mitt +mittal +mitten +mittens +mitterrand +mitts +mitty +mitzi +mitzvah +mix +mixed +mixer +mixers +mixes +mixing +mixon +mixture +mixtures +mixup +miyamoto +miyazawa +mizar +mize +mizrahi +mk +ml +mladic +mm +mme +mmmm +mn +mnemonic +mnemonics +mo +moab +moan +moaned +moaning +moans +moat +moats +mob +mobbed +mobil +mobile +mobiles +mobility +mobilization +mobilizations +mobilize +mobilized +mobilizes +mobilizing +mobius +mobs +mobster +mobsters +mobutu +moby +moca +moccasin +moccasins +mocha +mock +mocked +mockery +mocking +mockingbird +mocks +mod +modal +modalities +modality +mode +model +modeled +modeler +modeling +modelled +modelling +models +modem +modems +modena +moder +moderate +moderated +moderately +moderates +moderating +moderation +moderator +moderators +modern +moderne +modernism +modernist +modernists +modernity +modernization +modernize +modernized +modernizing +moderns +modes +modest +modestly +modesto +modesty +modi +modicum +modifiable +modification +modifications +modified +modifier +modifiers +modifies +modify +modifying +modo +mods +modular +modularity +modulate +modulated +modulating +modulation +modulations +modulator +modulators +module +modules +modulus +modus +mody +moe +moeller +moet +moffat +moffatt +moffett +moffit +moffitt +mogadishu +mogg +mogul +moguls +mohair +mohamad +mohamed +mohammad +mohammed +mohan +mohandas +mohawk +mohawks +mohican +mohl +mohr +mohs +moi +moines +moir +moira +moise +moises +moist +moisten +moistened +moisture +moisturizer +moisturizers +moisturizing +mojave +mojo +mok +mol +mola +molar +molas +molasses +mold +moldavia +molded +molder +molders +molding +moldings +moldova +molds +moldy +mole +molecular +molecule +molecules +molehill +moler +moles +molest +molestation +molestations +molested +molester +molesters +molesting +moliere +molina +molinari +moline +molineaux +molitor +moll +molle +moller +mollie +mollified +mollify +molloy +molls +mollusks +molly +molnar +molokai +moloney +molotov +molson +molt +molten +moly +molybdenum +mom +moma +mombasa +moment +momenta +momentarily +momentary +momento +momentous +moments +momentum +momma +mommies +mommy +momo +moms +mon +mona +monaco +monaghan +monahan +monarch +monarchical +monarchies +monarchist +monarchists +monarchs +monarchy +monasteries +monastery +monastic +monasticism +moncada +mondadori +mondale +mondavi +monday +mondays +monde +mondial +mondo +mondragon +mondrian +mone +monet +monetarily +monetarism +monetarist +monetary +monetization +monetized +money +moneybags +moneychangers +moneyed +moneyline +moneymaker +moneymakers +moneymaking +moneypenny +moneys +mong +monger +mongering +mongers +mongol +mongolia +mongolian +mongoloid +mongols +mongoose +mongooses +mongrels +monica +monicker +monied +monies +moniker +monikers +monique +monitor +monitored +monitoring +monitors +monk +monkees +monkey +monkeying +monkeys +monks +monmouth +mono +monochromatic +monochrome +monoclonal +monoculture +monogamous +monogamy +monogram +monogrammed +monograph +monographs +monohull +monolingual +monolith +monolithic +monoliths +monologue +monologues +monomer +monomers +mononucleosis +monopole +monopolies +monopolist +monopolistic +monopolists +monopolization +monopolize +monopolized +monopolizes +monopolizing +monopoly +monorail +monosodium +monotheism +monotheistic +monotone +monotonous +monotony +monotype +monoxide +monroe +monrovia +mons +monsanto +monsieur +monsignor +monson +monsoon +monsoonal +monsoons +monster +monsters +monstrosities +monstrosity +monstrous +monstrously +mont +montag +montage +montagne +montague +montalban +montalvo +montana +montanan +montclair +monte +montebello +montego +montenegro +monterey +montero +monterrey +montes +montessori +montevideo +montezuma +montford +montfort +montgomery +month +monthlies +monthlong +monthly +months +monti +monticello +montiel +montini +montoya +montpelier +montreal +montrose +monts +monty +monument +monumental +monumentally +monuments +mony +mood +moods +moody +moog +mool +moon +moonbeam +mooney +moonie +mooning +moonless +moonlight +moonlighting +moonlit +moons +moonshine +moonstone +moonstruck +moonwalk +moony +moor +moore +moored +moorer +moores +moorhead +moorhouse +mooring +moorings +moorish +moorland +moorman +moors +moosa +moose +moot +mooted +moots +mop +mope +moped +moping +mopped +mopping +mops +mor +mora +moraga +moraine +morais +moral +morale +morales +moralism +moralist +moralistic +moralists +morality +moralize +moralizing +morally +morals +moran +morass +moratoria +moratorium +moratoriums +moravian +moray +morbid +morbidity +morbidly +mordant +mordecai +mordechai +more +moreau +morehead +moreira +morel +moreland +morell +morella +morelli +morello +morels +morena +moreno +moreover +mores +moreso +moretti +morey +morford +morgan +morgana +morgen +morgenstern +morgenthau +morgue +morgues +mori +moria +moriarty +moribund +morice +morimoto +morin +morita +moritz +morley +mormon +mormonism +mormons +morn +morning +mornings +morningstar +moro +moroccan +moroccans +morocco +moron +moroni +moronic +morons +moros +morose +morph +morphine +morphing +morphological +morphology +morphs +morrell +morrie +morrill +morris +morrison +morrissette +morrissey +morristown +morro +morrow +morse +morsel +morsels +mort +mortal +mortality +mortally +mortals +mortar +mortars +morte +mortem +morten +mortensen +mortenson +mortgage +mortgaged +mortgagee +mortgages +mortgaging +mortician +morticians +mortification +mortified +mortify +mortifying +mortimer +mortis +morton +mortuary +mos +mosaic +mosaics +mosbacher +moscoso +moscow +mose +mosel +moseley +mosely +moser +moses +mosey +moshe +mosher +moskowitz +moslem +moslems +mosley +mosman +mosque +mosques +mosquito +mosquitoes +mosquitos +moss +mossad +mosses +mossman +mossy +most +mostly +mosul +mot +mota +mote +motel +motels +motes +moth +mothball +mothballed +mothballing +mothballs +mother +motherboard +motherboards +motherhood +mothering +motherland +motherless +motherly +mothers +moths +motif +motifs +motility +motion +motioned +motioning +motionless +motions +motivate +motivated +motivates +motivating +motivation +motivational +motivations +motivator +motivators +motive +motives +motley +moto +motocross +moton +motor +motorbike +motorbikes +motorboat +motorcade +motorcar +motorcars +motorcoach +motorcycle +motorcycles +motorcycling +motorcyclist +motorcyclists +motoring +motorist +motorists +motorized +motorola +motors +motorsports +motorway +motorways +motown +motrin +mots +mott +motte +mottled +motto +mottoes +mottos +motz +mouche +mould +moulded +moulding +mouldings +moulds +moulin +moulton +mound +mounds +mount +mountain +mountaineer +mountaineering +mountaineers +mountainous +mountains +mountainside +mountainsides +mountaintop +mountaintops +mounted +mounties +mounting +mountings +mounts +mourn +mourned +mourner +mourners +mournful +mourning +mourns +mouse +mouser +mouses +mousetrap +mousetraps +moussa +mousse +moustache +mouth +mouthed +mouthful +mouthing +mouthpiece +mouthpieces +mouths +mouthwash +mouthwatering +mouthy +mouton +movable +move +moveable +moved +movement +movements +mover +movers +moves +movie +moviegoers +moviegoing +moviemaker +moviemakers +moviemaking +movies +moving +movingly +mow +mowbray +mowed +mower +mowers +mowing +mown +mows +moxie +moy +moya +moyen +moyer +moyers +moyle +moynihan +mozambique +mozart +mozzarella +mpeg +mpg +mph +mr +mrs +ms +mscs +msg +msgr +mss +mt +mtd +mtn +mu +muammar +mubarak +much +mucha +mucho +muck +mucking +muckraker +muckraking +mucky +mucosa +mucosal +mucous +mucus +mud +mudd +mudder +muddied +muddier +muddies +muddle +muddled +muddles +muddling +muddy +muddying +muds +mudslides +mudslinging +mueller +muenster +muezzin +muff +muffet +muffin +muffins +muffle +muffled +muffler +mufflers +muffs +muffy +mufti +mug +mugabe +mugged +mugger +muggers +mugging +muggings +muggy +mugs +mugu +muhammad +muhammed +muharram +mui +muir +muise +mujahadeen +mujahedeen +mujahid +mujahideen +mujeres +mukesh +mukherjee +mukhtar +mula +mulatto +mulberry +mulcahy +mulch +mulched +mulching +mulder +mule +mules +mulford +mulhern +mulholland +mulhouse +mull +mulla +mullah +mullahs +mullane +mulled +mullen +muller +mullet +mullets +mulligan +mullin +mulling +mullins +mulls +mulroney +mult +multi +multibillion +multichannel +multicolor +multicolored +multicultural +multiculturalism +multidimensional +multidisciplinary +multiethnic +multifaceted +multifamily +multifunction +multilateral +multilateralism +multilaterally +multilayer +multilayered +multilevel +multilingual +multimedia +multimillion +multimillionaire +multimillionaires +multimode +multinational +multinationals +multipart +multiparty +multiphase +multiplayer +multiple +multiples +multiplex +multiplexed +multiplexer +multiplexers +multiplexing +multiplication +multiplicity +multiplied +multiplier +multipliers +multiplies +multiply +multiplying +multiprocessor +multipurpose +multiracial +multistate +multisync +multitask +multitasking +multitude +multitudes +multiuser +multivitamin +multivitamins +multiyear +mulvey +mum +mumble +mumbled +mumbles +mumbling +mumbo +mumford +mumm +mummers +mummies +mummification +mummified +mummy +mumps +mums +mun +munch +munched +munchies +munching +muncie +mund +mundane +munday +mundell +mundo +mundt +mundy +munger +mungo +muni +munich +municipal +municipalities +municipality +municipally +munificence +munificent +munition +munitions +muniz +munns +munoz +munro +munroe +munshi +munson +munster +munsters +muntz +muppets +mura +murad +murakami +mural +murals +murano +murat +murawski +murch +murder +murdered +murderer +murderers +murdering +murderous +murders +murdoch +murdock +muriel +murillo +murine +murk +murkier +murkowski +murky +murmur +murmured +murmuring +murmurs +muro +murph +murphy +murphys +murr +murrah +murray +murrey +murrow +murry +murtagh +murtha +mus +musa +muscat +muscle +muscled +muscles +muscling +muscovite +muscovites +muscovy +muscular +musculature +musculoskeletal +muse +mused +musee +muses +museum +museums +musgrove +mush +musher +mushroom +mushroomed +mushrooming +mushrooms +mushy +musial +music +musica +musical +musicality +musically +musicals +musician +musicians +musicianship +musicologist +musil +musing +musings +musique +musk +musket +musketeer +musketeers +muskie +muskogee +muskrat +muskrats +musky +muslim +muslims +muslin +muss +mussed +mussel +mussels +musselwhite +mussolini +must +mustache +mustaches +mustachioed +mustafa +mustang +mustangs +mustard +mustards +muster +mustered +mustering +musters +musto +musty +mutable +mutagen +mutagens +mutant +mutants +mutate +mutated +mutating +mutation +mutations +mutch +mute +muted +muth +mutilate +mutilated +mutilating +mutilation +mutilations +mutineer +mutineers +muting +mutinous +mutiny +mutt +mutter +muttered +muttering +mutterings +mutters +mutton +mutts +mutual +mutualism +mutuality +mutually +muzak +muzzle +muzzled +muzzles +muzzy +mw +my +myanmar +myasthenia +mycenaean +mycology +myelin +myeloid +myer +myers +myerson +mylar +myles +myocardial +myopia +myopic +myosin +myra +myriad +myriads +myriam +myrick +myrmidons +myrna +myron +myrrh +myrtle +myrtles +myself +mystere +mysteries +mysterious +mysteriously +mystery +mystic +mystical +mysticism +mystics +mystification +mystified +mystifies +mystify +mystifying +mystique +myth +mythic +mythical +mythological +mythologies +mythology +myths +myung +na +nab +nabbed +nabbing +nabi +nabil +nabisco +nablus +nabob +nabobs +nabs +nace +nacelle +nach +nacho +nachos +nacional +nacogdoches +nad +nada +nadel +nader +nadia +nadine +nadir +nadja +nadler +naf +nafta +nag +naga +nagano +nagasaki +nagata +nagel +nagged +nagging +nagle +nagorno +nagoya +nags +nagy +nah +nahum +naik +nail +nailed +nailing +nails +nair +naira +nairobi +naish +naive +naively +naivete +najera +najib +nakagawa +nakajima +nakamura +nakayama +naked +nam +name +named +nameless +namely +nameplate +nameplates +namer +namers +names +namesake +namibia +naming +nan +nana +nanaimo +nance +nancy +nandi +nanette +nanjing +nanking +nannies +nanny +nano +nanogram +nanograms +nanometer +nanosecond +nanoseconds +nantucket +naomi +nap +napa +napalm +nape +naperville +naphtha +naphthalene +napier +napkin +napkins +naples +napoleon +napoleonic +napoli +napolitano +nappa +napped +napper +napping +nappy +naproxen +naps +naqvi +nar +nara +naranjo +narasimha +narayan +narayanan +narc +narcissism +narcissistic +narcissus +narcolepsy +narcotic +narcotics +narita +narragansett +narrate +narrated +narrates +narrating +narration +narrative +narratives +narrator +narrators +narrow +narrowed +narrower +narrowest +narrowing +narrowly +narrowness +narrows +nary +nasa +nasal +nasally +nascar +nascent +nasdaq +nash +nashua +nashville +nasr +nass +nassar +nassau +nasser +nast +nastier +nasties +nastiest +nastiness +nasty +nat +nata +natal +natale +natalia +natalie +natasha +natch +natchez +nate +nath +nathalie +nathan +nathanael +nathaniel +natick +nation +national +nationale +nationalism +nationalist +nationalistic +nationalists +nationalities +nationality +nationalization +nationalize +nationalized +nationalizing +nationally +nationals +nationhood +nations +nationsbank +nationwide +native +natives +nativist +nativity +nato +natter +nattering +natty +natural +naturalism +naturalist +naturalistic +naturalists +naturalization +naturalize +naturalized +naturalizing +naturally +nature +natured +naturedly +natures +natus +naught +naughton +naughty +nauman +naumann +nausea +nauseam +nauseated +nauseating +nauseous +nauseum +nautica +nautical +nautilus +nav +nava +navaho +navajo +navajos +naval +navarre +navarro +navas +nave +navel +navi +navies +navigable +navigate +navigated +navigates +navigating +navigation +navigational +navigator +navigators +navis +navistar +navy +naw +nawab +nay +naylor +nays +naysayers +nazar +nazarene +nazareth +nazario +nazi +nazim +nazionale +nazir +nazis +nazism +nb +nco +nd +ne +nea +neal +neale +neanderthal +neanderthals +neapolitan +near +nearby +neared +nearer +nearest +nearing +nearly +nearness +nears +nearsighted +neary +neat +neater +neatest +neath +neatly +neatness +neb +nebel +nebraska +nebraskan +nebraskans +nebuchadnezzar +nebula +nebulous +nec +neccessary +necessarily +necessary +necessitate +necessitated +necessitates +necessitating +necessities +necessity +neck +necked +necker +necklace +necklaces +neckline +necks +necktie +neckties +neckwear +necropolis +necropsy +necrosis +nectar +nectarine +ned +nederland +nee +need +needed +needful +needham +neediest +needing +needle +needled +needleman +needlepoint +needler +needles +needless +needlessly +needlework +needling +needs +needy +neel +neela +neely +neer +neeson +neet +nefarious +nefertiti +neff +neg +negara +negate +negated +negates +negating +negation +negative +negatively +negatives +negativism +negativity +negev +neglect +neglected +neglectful +neglecting +neglects +negligence +negligent +negligently +negligible +negotiable +negotiate +negotiated +negotiates +negotiating +negotiation +negotiations +negotiator +negotiators +negrete +negro +negroes +negros +negus +nehemiah +nehru +nei +neigh +neighbor +neighborhood +neighborhoods +neighboring +neighbors +neighbour +neighbourhood +neighbouring +neighbours +neil +neill +neilsen +neilson +neiman +nein +neisseria +neither +nell +nellie +nelligan +nellis +nelly +nelson +nelsons +nematode +nematodes +nemesis +nemeth +nemo +nemours +nene +neo +neoclassic +neoclassical +neolithic +neologism +neon +neonatal +neophyte +neophytes +neoprene +nep +nepal +nepalese +nepali +nephew +nephews +nephrologist +nepotism +neptune +neptunium +nerd +nerds +neri +nero +nerve +nerves +nervosa +nervous +nervously +nervousness +nervy +nes +nesbit +nesbitt +nesmith +ness +nesses +nessie +nest +nested +nesters +nesting +nestle +nestled +nestles +nestling +nestor +nestorian +nests +net +netanyahu +netcom +neth +nether +netherlands +netherworld +neto +nets +netted +netter +netting +nettle +nettles +nettlesome +nettleton +netware +network +networked +networking +networks +neu +neuberger +neue +neuendorf +neuer +neuhaus +neuman +neumann +neural +neuraminidase +neuro +neurobiology +neurodegenerative +neuroendocrine +neurofibromatosis +neurologic +neurological +neurologist +neurologists +neurology +neuron +neuronal +neurons +neuropathy +neuroscience +neuroscientist +neuroses +neurosis +neurosurgeon +neurosurgery +neurotic +neuroticism +neurotransmitter +neustadt +neuter +neutered +neutering +neutral +neutrality +neutralization +neutralize +neutralized +neutralizes +neutralizing +neutrals +neutrino +neutrinos +neutrogena +neutron +neutrons +neutrophils +nevada +nevadans +neve +never +neverending +neverland +nevermind +nevermore +nevertheless +neves +neville +nevin +nevins +nevis +nevitt +new +newark +newberg +newberry +newbold +newborn +newborns +newbridge +newburg +newbury +newby +newcastle +newcomb +newcombe +newcomer +newcomers +newell +newer +newest +newfangled +newfield +newfound +newfoundland +newhall +newhart +newhouse +newington +newish +newkirk +newly +newlywed +newlyweds +newman +newmark +newmarket +newmont +newness +newpaper +newport +news +newscast +newscaster +newscasters +newscasts +newsday +newsgroup +newsgroups +newshour +newsies +newsletter +newsletters +newsline +newsmagazine +newsman +newsmen +newsom +newsome +newspaper +newspaperman +newspapermen +newspapers +newspeak +newsprint +newsreader +newsreel +newsreels +newsroom +newsrooms +newsstand +newsstands +newsweek +newswire +newsworthiness +newsworthy +newsy +newt +newton +newtonian +newtons +newts +nexis +next +nextdoor +nextstep +nexus +ney +ng +ngai +ngo +ngos +nguyen +ni +niacin +niagara +niall +nib +nibble +nibbled +nibbles +nibbling +nibs +nic +nicad +nicaragua +nicaraguan +nicaraguans +niccolo +nice +nicely +nicene +niceness +nicer +nicest +niceties +nicety +niche +niches +nichol +nicholas +nicholl +nicholls +nichols +nicholson +nicht +nick +nicked +nickel +nickell +nickelodeon +nickels +nickerson +nicki +nicklaus +nickle +nickles +nickname +nicknamed +nicknames +nickolas +nicks +nicky +nico +nicol +nicola +nicolae +nicolas +nicolaus +nicolay +nicole +nicoll +nicolo +nicols +nicolson +nicosia +nicotine +nicotinic +nida +nidal +niebuhr +niece +nieces +nied +niehaus +niels +nielsen +nielson +nieman +niemann +niemeyer +nies +nietzsche +nieves +nifty +nigel +nigella +niger +nigeria +nigerian +nigerians +niggardly +nigger +niggers +niggling +nigh +night +nightclub +nightclubs +nighter +nighters +nightfall +nightgown +nighthawk +nighthorse +nightie +nightime +nightingale +nightlife +nightline +nightly +nightmare +nightmares +nightmarish +nights +nightshade +nightshirt +nightspot +nightspots +nightstand +nightstick +nighttime +nihilism +nihilists +nihon +nik +nike +nikes +nikita +nikkei +nikki +nikko +niko +nikolai +nikolaus +nikon +nikos +nil +niland +nile +niles +nils +nilsson +nimble +nimbly +nimbus +nimby +nimh +nimitz +nimoy +nimrod +nims +nina +nincompoop +nine +niner +nines +nineteen +nineteenth +nineties +ninety +ning +ninja +nino +ninos +nintendo +ninth +nip +nipped +nipper +nipping +nipple +nipples +nippon +nippy +nips +niro +nirvana +nis +nisbet +nishi +nissan +nissen +nist +nit +nita +nite +nited +nitpick +nitpicking +nitrate +nitrates +nitric +nitride +nitro +nitrocellulose +nitrogen +nitrogenous +nitroglycerin +nitroglycerine +nitrous +nits +nitty +nitwits +nitze +niven +nix +nixed +nixes +nixon +nizam +nl +nm +no +noaa +noah +noam +nobel +nobility +nobis +noble +nobleman +nobles +noblesse +noblest +noblewoman +nobly +nobodies +nobody +nock +nocturnal +nod +noda +nodded +nodding +node +nodes +nods +nodular +nodule +nodules +noe +noel +noelle +noes +nogales +noggin +noh +noir +noire +noise +noiseless +noisemakers +noises +noisier +noisiest +noisily +noisy +nokes +nokia +nola +nolan +noland +nolde +noll +nolo +nolte +nom +nomad +nomadic +nomads +nome +nomenclature +nominal +nominally +nominate +nominated +nominates +nominating +nomination +nominations +nominee +nominees +noms +nomura +non +nona +nonacademic +nonaggression +nonaligned +nonbelievers +nonbusiness +noncash +nonchalance +nonchalant +nonchalantly +noncombatants +noncommercial +noncommittal +noncompetitive +noncompliance +nonconforming +nonconformist +nonconformists +nonconformity +noncontroversial +nonconventional +noncore +nondescript +nondestructive +nondisclosure +nondiscrimination +nondiscriminatory +nondurable +none +noneconomic +nonentity +nonessential +nonesuch +nonetheless +nonexclusive +nonexistence +nonexistent +nonfarm +nonfatal +nonfiction +nonflammable +nong +nongovernment +nongovernmental +nonhuman +noninterference +nonintervention +nonlethal +nonlinear +nonlocal +nonmarket +nonmember +nonmembers +nonmilitary +nonnuclear +nonparty +nonpayment +nonperformance +nonplussed +nonpolitical +nonprescription +nonproductive +nonprofessional +nonprofit +nonproliferation +nonpublic +nonrefundable +nonresident +nonresidential +nonresidents +nonsectarian +nonsense +nonsensical +nonsmoker +nonsmokers +nonsmoking +nonspecific +nonstandard +nonstarter +nonstick +nonstop +nontechnical +nontoxic +nontraditional +nontrivial +nonunion +nonverbal +nonviolence +nonviolent +nonvolatile +nonwhite +nonwoven +nonzero +noodle +noodles +noodling +nook +nooks +noon +noonan +noonday +noone +noontime +noor +noose +nooses +nope +nor +nora +norbert +norberto +norcross +nord +nordgren +nordic +nordstrom +noreen +norelco +norfolk +nori +noriega +nork +norm +norma +normal +normalcy +normality +normalization +normalize +normalized +normalizing +normally +normals +norman +normand +normandy +normans +normative +norms +norris +norse +norsemen +norsk +norske +norte +nortel +north +northam +northampton +northbound +northbrook +northeast +northeasterly +northeastern +northeastward +norther +northerly +northern +northerner +northerners +northernmost +northfield +northgate +northland +northridge +northrop +northrup +northstar +northward +northwest +northwesterly +northwestern +norton +norwalk +norway +norwegian +norwegians +norwest +norwich +norwood +nos +nose +nosebleed +nosebleeds +nosed +nosedive +noses +nosey +nosh +nosing +nostalgia +nostalgic +nostra +nostradamus +nostril +nostrils +nostrums +nosy +not +nota +notable +notables +notably +notaries +notarized +notary +notation +notations +notch +notched +notches +note +notebook +notebooks +noted +notepad +notepads +notes +noteworthy +nother +nothin +nothing +nothingness +nothings +noticable +notice +noticeable +noticeably +noticed +notices +noticing +notification +notifications +notified +notifies +notify +notifying +noting +notion +notional +notions +notoriety +notorious +notoriously +notre +nots +nott +nottage +nottingham +notwithstanding +noun +nouns +nourish +nourished +nourishing +nourishment +nous +nouveau +nouvelle +nouvelles +nov +nova +novak +novas +novation +novato +novel +novelist +novelistic +novelists +novelization +novell +novella +novels +novelties +novelty +november +novgorod +novi +novia +novice +novices +novo +novocaine +novotny +novum +novus +novy +now +nowadays +nowell +nowhere +nowlan +nowy +nox +noxious +noy +noyes +nozzle +nozzles +np +npr +nr +ns +nt +nth +ntsc +nu +nuance +nuanced +nuances +nub +nubian +nuclear +nuclei +nucleic +nucleonics +nucleotide +nucleus +nude +nudes +nudge +nudged +nudges +nudging +nudie +nudist +nudists +nudity +nuevo +nuff +nugatory +nugent +nugget +nuggets +nuisance +nuisances +nuke +nuked +nukem +nukes +null +nullification +nullified +nullifies +nullify +nullifying +num +numb +numbed +number +numbered +numbering +numberless +numbers +numbing +numbingly +numbness +numbs +numeral +numerals +numerator +numeric +numerical +numerically +numero +numerology +numerous +numismatic +nun +nuncio +nunez +nunn +nunnally +nunnery +nuns +nuova +nuovo +nuptial +nuptials +nuremberg +nureyev +nuri +nurse +nursed +nurseries +nursery +nurseryman +nurses +nursing +nurture +nurtured +nurturer +nurtures +nurturing +nussbaum +nut +nutcase +nutcracker +nuthin +nuthouse +nutmeg +nutrasweet +nutrient +nutrients +nutrition +nutritional +nutritionally +nutritionist +nutritionists +nutritious +nutritive +nuts +nutshell +nutt +nuttall +nutter +nutting +nutty +nuys +nuzzle +nv +ny +nyah +nyberg +nye +nylon +nylons +nymph +nymphomaniac +nymphs +nynex +nyquist +nyssa +oaf +oahu +oak +oakbrook +oakdale +oaken +oakes +oakland +oakley +oakmont +oaks +oakville +oakwood +oaky +oar +oars +oas +oases +oasis +oat +oates +oath +oaths +oatmeal +oats +oaxaca +ob +oba +obedience +obedient +obediently +obeisance +obelisk +ober +oberg +oberlin +obermeyer +oberon +obese +obesity +obey +obeyed +obeying +obeys +obfuscate +obfuscated +obfuscating +obfuscation +obie +obispo +obit +obits +obituaries +obituary +object +objected +objectify +objecting +objection +objectionable +objections +objective +objectively +objectives +objectivism +objectivity +objector +objectors +objects +objet +oblast +oblate +obligate +obligated +obligates +obligation +obligations +obligatorily +obligatory +oblige +obliged +obliges +obliging +obligingly +oblique +obliquely +obliterate +obliterated +obliterating +obliteration +oblivion +oblivious +obliviously +oblong +obnoxious +obnoxiously +oboe +obs +obscene +obscenely +obscenities +obscenity +obscurantism +obscure +obscured +obscures +obscuring +obscurities +obscurity +obsequious +observable +observance +observances +observant +observation +observational +observations +observatories +observatory +observe +observed +observer +observers +observes +observing +obsess +obsessed +obsessing +obsession +obsessional +obsessions +obsessive +obsessively +obsidian +obsolescence +obsolescent +obsolete +obstacle +obstacles +obstetric +obstetrician +obstetricians +obstetrics +obstinacy +obstinate +obstruct +obstructed +obstructing +obstruction +obstructionism +obstructionist +obstructions +obstructive +obstructs +obtain +obtainable +obtained +obtaining +obtains +obtrusive +obtuse +obtuseness +obverse +obviate +obviated +obviates +obviating +obvious +obviously +oc +occasion +occasional +occasionally +occasioned +occasions +occassion +occassionally +occident +occidental +occipital +occluded +occlusion +occult +occultism +occupancy +occupant +occupants +occupation +occupational +occupations +occupied +occupier +occupiers +occupies +occupy +occupying +occur +occured +occurence +occuring +occurred +occurrence +occurrences +occurring +occurs +ocean +oceanfront +oceania +oceanic +oceanographer +oceanographic +oceanography +oceans +oceanside +ocelot +och +ochoa +ochre +ochs +oclc +oct +octagon +octagonal +octahedron +octane +octave +octaves +octavia +octavio +octavius +octet +october +octobers +octogenarian +octopi +octopus +octopuses +od +oda +odd +oddball +oddballs +odder +oddest +oddities +oddity +oddly +odds +ode +oded +odell +oden +odeon +oder +odes +odessa +odette +odier +odin +odious +odle +odom +odometer +odometers +odor +odorant +odorless +odorous +odors +odum +odysseus +odyssey +oe +oedipal +oedipus +oeuvre +oeuvres +of +ofer +off +offbeat +offed +offence +offences +offend +offended +offender +offenders +offending +offends +offense +offenses +offensive +offensively +offensiveness +offensives +offer +offered +offering +offerings +offeror +offerors +offers +offhand +offical +office +officeholder +officeholders +officer +officers +offices +official +officialdom +officially +officials +officiate +officiated +officiating +officio +offing +offline +offload +offloaded +offloading +offs +offscreen +offset +offsets +offsetting +offshoot +offshoots +offshore +offside +offsite +offspring +offsprings +offstage +oft +often +oftentimes +og +ogata +ogawa +ogden +ogilvie +ogilvy +ogle +ogled +ogling +ogre +ogres +oh +ohhh +ohio +ohioan +ohioans +ohm +ohms +ohs +oi +oie +oil +oiled +oiler +oilers +oilfield +oilfields +oiling +oilman +oilmen +oils +oilseed +oilseeds +oilwell +oily +oink +ointment +ointments +oj +ojai +ojibwa +ok +oka +okamoto +okay +okayed +okey +oki +okie +okinawa +oklahoma +oklahoman +oklahomans +okra +ol +ola +olaf +olav +old +olde +olden +oldenburg +older +oldest +oldfield +oldham +oldie +oldies +oldman +olds +oldsmobile +oldsters +ole +olea +oleander +olefin +olefins +oleg +oleo +oles +olfactory +olga +oligarch +oligarchs +oligarchy +oligopolistic +oligopoly +olin +oliphant +oliva +olivares +olive +oliveira +oliver +olivera +olives +olivet +olivetti +olivia +olivier +olla +ollie +olly +olmos +olmstead +olney +olof +olsen +olson +olvera +olympia +olympiad +olympian +olympians +olympic +olympics +olympus +om +omaha +oman +omar +ombudsman +omega +omelet +omelette +omen +omens +omer +omicron +ominous +ominously +omission +omissions +omit +omits +omitted +omitting +omni +omnia +omnibus +omnidirectional +omnipotence +omnipotent +omnipresence +omnipresent +omniscience +omniscient +omnivorous +omron +on +ona +onassis +onboard +once +oncologist +oncologists +oncology +oncoming +one +oneida +oneness +onerous +ones +oneself +onetime +ong +ongoing +onion +onions +online +onlooker +onlookers +only +onna +ono +onofre +onondaga +onrushing +ons +onscreen +onset +onshore +onside +onsite +onslaught +onstage +ont +ontarians +ontario +onto +ontological +ontology +onus +onward +onwards +onyx +oodles +ooh +oohs +oolong +oomph +oona +oooh +oooo +ooooh +ooops +oops +oos +ooze +oozed +oozes +oozing +op +opacity +opal +opals +opaque +opaqueness +opec +oped +opel +open +opened +opener +openers +opening +openings +openly +openness +opens +openshaw +openwork +oper +opera +operability +operable +operandi +operas +operate +operated +operates +operatic +operating +operation +operational +operationally +operations +operative +operatives +operator +operators +operetta +operettas +ophelia +ophthalmic +ophthalmologist +ophthalmologists +ophthalmology +ophthalmoscope +opiate +opiates +opic +opie +opine +opined +opines +opining +opinion +opinionated +opinions +opium +oporto +opossum +opossums +opp +oppenheim +oppenheimer +opponent +opponents +opportune +opportunism +opportunist +opportunistic +opportunistically +opportunists +opportunites +opportunities +opportunity +oppose +opposed +opposes +opposing +opposite +opposites +opposition +oppositional +oppositions +oppress +oppressed +oppressing +oppression +oppressive +oppressor +oppressors +opprobrium +oprah +opry +ops +opt +opted +optic +optica +optical +optically +optician +opticians +optics +optima +optimal +optimally +optimism +optimist +optimistic +optimistically +optimists +optimization +optimize +optimized +optimizes +optimizing +optimum +opting +option +optional +optionally +optioned +options +optometrist +optometry +opts +opulence +opulent +opus +or +ora +oracle +oracles +oral +orally +oran +orange +oranges +orangutan +orangutans +oration +orations +orator +oratorical +oratorio +orators +oratory +orb +orbach +orbis +orbit +orbital +orbited +orbiter +orbiters +orbiting +orbits +orbs +orc +orca +orcas +orchard +orchards +orchestra +orchestral +orchestras +orchestrate +orchestrated +orchestrates +orchestrating +orchestration +orchestrations +orchestrator +orchestre +orchid +orchids +ord +ordain +ordained +ordaining +ordeal +ordeals +order +ordered +ordering +orderliness +orderly +orders +ordinance +ordinances +ordinaries +ordinarily +ordinary +ordinate +ordination +ordnance +ordonez +ore +oregano +oregon +oregonian +oregonians +orel +orellana +orem +oren +orenstein +ores +orestes +orf +org +organ +organic +organically +organics +organisation +organisations +organised +organising +organism +organisms +organist +organization +organizational +organizationally +organizations +organize +organized +organizer +organizers +organizes +organizing +organogenesis +organophosphate +organs +orgasm +orgasmic +orgasms +orgies +orgone +orgy +orhan +ori +orient +oriental +orientalism +orientalist +orientals +orientated +orientation +orientations +oriented +orienting +orients +orifice +orifices +orig +origami +origin +original +originality +originally +originals +originate +originated +originates +originating +origination +originations +originator +originators +origins +orignal +orin +orinoco +oriole +orioles +orion +orlando +orleans +orly +orman +orme +ormolu +ormond +ormsby +ornament +ornamental +ornamentation +ornamented +ornaments +ornate +orne +ornithological +ornithologist +ornithology +ornstein +orosco +orozco +orphan +orphanage +orphanages +orphaned +orphans +orpheum +orpheus +orr +orrin +orsay +orsini +orso +orson +ort +orta +ortega +orth +ortho +orthodontic +orthodontics +orthodontist +orthodontists +orthodox +orthodoxies +orthodoxy +orthogonal +orthography +orthopaedic +orthopedic +orthopedics +orthopedist +ortiz +orton +orvieto +orville +orwell +orwellian +orwig +ory +oryx +os +osage +osaka +osborn +osborne +osbourne +osburn +oscar +oscars +oscillate +oscillates +oscillating +oscillation +oscillations +oscillator +oscillators +oscilloscope +ose +oser +osgood +osha +oshkosh +osi +osiris +osler +oslo +osman +osmond +osmosis +osmotic +osprey +oss +ossa +ossification +ossified +osso +ost +ostensible +ostensibly +ostentation +ostentatious +ostentatiously +osteoarthritis +osteopathic +osteoporosis +oster +osterberg +ostertag +ostia +ostracism +ostracize +ostracized +ostrander +ostrich +ostriches +ostrowski +oswald +oswego +ot +otani +otello +otero +othe +othello +other +otherness +others +otherwise +otherworldly +othman +otis +ott +ottawa +otten +otter +otters +otto +ottoman +ottomans +ou +ouch +oude +ought +oughta +oui +ouija +ounce +ounces +our +ours +ourself +ourselves +oust +ousted +ouster +ousting +ousts +out +outa +outage +outages +outback +outbid +outboard +outbound +outbreak +outbreaks +outbuilding +outburst +outbursts +outcast +outcasts +outclass +outclassed +outcome +outcomes +outcries +outcry +outdated +outdid +outdo +outdone +outdoor +outdoors +outed +outer +outermost +outerwear +outfall +outfield +outfielder +outfielders +outfit +outfits +outfitted +outfitter +outfitters +outfitting +outflank +outflanked +outflow +outflows +outfox +outfoxed +outgo +outgoing +outgrew +outgrow +outgrowing +outgrown +outgrowth +outgun +outgunned +outhouse +outhouses +outing +outings +outland +outlandish +outlast +outlasted +outlasts +outlaw +outlawed +outlawing +outlaws +outlay +outlays +outlet +outlets +outlier +outliers +outline +outlined +outlines +outlining +outlive +outlived +outlook +outlooks +outlying +outmaneuvered +outmoded +outnumber +outnumbered +outnumbering +outnumbers +outpace +outpaced +outpaces +outpacing +outpatient +outpatients +outperform +outperformance +outperformed +outperforming +outperforms +outplacement +outplayed +outpost +outposts +outpouring +outpourings +output +outputs +outrage +outraged +outrageous +outrageously +outrageousness +outrages +outraging +outran +outre +outreach +outrider +outrigger +outright +outrun +outrunning +outs +outscore +outscored +outsell +outselling +outsells +outset +outshine +outshone +outside +outsider +outsiders +outsides +outsize +outsized +outskirt +outskirts +outsmart +outsmarting +outsold +outsource +outsourcing +outspend +outspent +outspoken +outspokenness +outstanding +outstandingly +outstretched +outstrip +outstripped +outstripping +outstrips +outta +outtake +outtakes +outvoted +outward +outwardly +outwards +outweigh +outweighed +outweighing +outweighs +outwit +outwitting +oval +ovals +ovarian +ovaries +ovary +ovate +ovation +ovations +oven +ovens +over +overabundance +overabundant +overachieving +overact +overacting +overactive +overall +overalls +overarching +overbearing +overblown +overboard +overbook +overbooked +overbooking +overbought +overbuilding +overbuilt +overburden +overburdened +overcame +overcapacity +overcast +overcharge +overcharged +overcharges +overcharging +overcoat +overcoats +overcome +overcomes +overcoming +overconfidence +overconfident +overcook +overcooked +overcrowded +overcrowding +overdid +overdo +overdoing +overdone +overdose +overdosed +overdoses +overdraft +overdrafts +overdrawn +overdrive +overdue +overeager +overeat +overeating +overemphasize +overemphasized +overenthusiastic +overestimate +overestimated +overestimates +overestimating +overestimation +overexcited +overexpansion +overexpose +overexposed +overexposure +overextended +overfed +overfeeding +overfishing +overflight +overflights +overflow +overflowed +overflowing +overflows +overfly +overflying +overgrazing +overgrown +overgrowth +overhang +overhanging +overhangs +overhaul +overhauled +overhauling +overhauls +overhead +overheads +overhear +overheard +overhearing +overheat +overheated +overheating +overindulgence +overjoyed +overkill +overlaid +overland +overlap +overlapped +overlapping +overlaps +overlay +overlayed +overlaying +overlays +overload +overloaded +overloading +overloads +overlock +overlook +overlooked +overlooking +overlooks +overlord +overlords +overly +overlying +overman +overmatched +overnight +overnighter +overpaid +overpass +overpasses +overpay +overpaying +overpayment +overpayments +overplay +overplayed +overplaying +overpopulated +overpopulation +overpower +overpowered +overpowering +overpriced +overproduce +overproduced +overproduction +overprotective +overqualified +overran +overrate +overrated +overreach +overreached +overreaches +overreaching +overreact +overreacted +overreacting +overreaction +overreliance +overrepresented +overridden +override +overrides +overriding +overripe +overrode +overrule +overruled +overruling +overrun +overrunning +overruns +overs +oversaw +oversea +overseas +oversee +overseeing +overseen +overseer +overseers +oversees +oversell +overselling +oversensitive +overshadow +overshadowed +overshadowing +overshadows +overshoot +overshooting +overshot +oversight +oversights +oversimplification +oversimplified +oversimplify +oversimplifying +oversize +oversized +overslept +oversold +overspeed +overspend +overspending +overspent +overspill +overstate +overstated +overstatement +overstatements +overstates +overstating +overstay +overstayed +oversteer +overstep +overstepped +overstepping +oversteps +overstock +overstocked +overstreet +overstressed +overstretching +overstuffed +oversubscribed +oversubscription +oversupply +overt +overtake +overtaken +overtakes +overtaking +overtax +overtaxed +overtaxing +overthrew +overthrow +overthrowing +overthrown +overtime +overtimes +overtly +overton +overtone +overtones +overtook +overtraining +overture +overtures +overturn +overturned +overturning +overturns +overuse +overused +overusing +overvaluation +overvalue +overvalued +overview +overviews +overwater +overweight +overwhelm +overwhelmed +overwhelming +overwhelmingly +overwhelms +overwork +overworked +overwrite +overwrites +overwriting +overwritten +overwrought +overzealous +ovid +ovitz +ovoid +ovonic +ovulation +ovule +ovum +ow +owe +owed +owen +owens +owensboro +ower +owes +owing +owings +owl +owlish +owls +own +owned +owner +owners +ownership +owning +owns +ox +oxalic +oxbow +oxcart +oxen +oxfam +oxford +oxfords +oxfordshire +oxidant +oxidants +oxidation +oxidative +oxide +oxides +oxidize +oxidized +oxidizer +oxidizing +oxley +oxnard +oxy +oxygen +oxygenate +oxygenated +oxygenates +oxymoron +oxymoronic +oxytocin +oy +oyster +oysters +oz +ozarks +ozawa +ozick +ozone +ozzie +ozzy +pa +paal +paar +pablo +pabst +pabulum +pac +pace +paced +pacemaker +pacemakers +pacer +pacers +paces +pacesetter +pacheco +pachinko +pachyderm +pacific +pacifica +pacification +pacifico +pacificorp +pacified +pacifier +pacifiers +pacifism +pacifist +pacifists +pacify +pacing +pacino +pack +package +packaged +packager +packagers +packages +packaging +packard +packards +packed +packer +packers +packet +packets +packing +packs +packwood +pacman +paco +pacs +pact +pacts +pad +padded +paddies +padding +paddington +paddle +paddled +paddles +paddling +paddock +paddocks +paddy +paderborn +padgett +padilla +padlock +padlocked +padlocks +padre +padres +pads +paducah +paean +paeans +paella +paez +pagan +paganini +paganism +pagans +page +pageant +pageantry +pageants +paged +pagel +pagemaker +pager +pagers +pages +paget +pagination +paging +paglia +pagoda +pah +pahlavi +paid +paige +paik +pail +pails +pain +paine +pained +painewebber +painful +painfully +painkiller +painkillers +painless +painlessly +pains +painstaking +painstakingly +paint +paintball +paintbrush +paintbrushes +painted +painter +painterly +painters +painting +paintings +paints +pair +paired +pairing +pairings +pairs +pais +paisley +paiute +pajama +pajamas +pak +pakistan +pakistani +pakistanis +pal +palace +palaces +palacio +paladin +paladins +palais +palance +palatable +palate +palates +palatial +palatine +palau +palaver +palazzi +palazzo +pale +paled +paleness +paleocene +paleolithic +paleontologist +paleontologists +paleontology +paleozoic +paler +palermo +pales +palest +palestine +palestinian +palestinians +palette +palettes +paley +palfrey +pali +palindrome +palindromes +palisade +palisades +pall +palladium +pallas +pallet +pallets +pallette +palliative +pallid +palliser +pallone +palm +palma +palmdale +palmer +palmeri +palmetto +palmieri +palmira +palmolive +palms +palmyra +palo +paloma +palomar +palomino +palpable +palpably +palpitation +palpitations +pals +palsy +paltry +palumbo +pam +pamela +pampas +pamper +pampered +pampering +pampers +pamphlet +pamphleteer +pamphlets +pamplona +pan +panacea +panache +panam +panama +panamanian +panamanians +panamsat +panasonic +pancake +pancaked +pancakes +panchayat +pancho +pancreas +pancreatic +pancreatitis +panda +pandas +pandemic +pandemonium +pander +pandered +pandering +pandit +pandora +pandya +pane +panel +paneled +paneling +panelist +panelists +panels +panes +panetta +pang +pangs +panhandle +panhandler +panhandlers +panhandling +panic +panicked +panicking +panicky +panics +panky +panmure +panned +pannell +panning +panoply +panorama +panoramas +panoramic +panos +pans +pansies +pansy +pant +pantaloons +pantano +panted +panter +pantheistic +pantheon +panther +panthers +panties +panting +pantomime +panton +pantries +pantry +pants +pantsuits +panty +pantyhose +panzer +pao +paola +paolo +paolucci +pap +papa +papacy +papadopoulos +papal +papandreou +paparazzi +papas +papaw +papaya +papayas +pape +paper +paperback +paperbacks +paperboard +paperboy +papered +papering +paperless +papermaking +papers +paperweight +paperweights +paperwork +papery +papier +papilloma +papillon +papo +papp +pappalardo +pappas +pappy +paprika +papua +par +para +parable +parables +parabola +parabolic +parachute +parachuted +parachutes +parachuting +parada +parade +paraded +parades +paradigm +paradigms +parading +paradis +paradise +paradiso +paradox +paradoxes +paradoxical +paradoxically +paradyne +paraffin +paragon +paragraph +paragraphs +paraguay +paraguayan +parakeet +parakeets +paralegal +paralegals +parallax +parallel +paralleled +paralleling +parallelism +parallelogram +parallels +paralympic +paralysis +paralytic +paralyze +paralyzed +paralyzes +paralyzing +paramedic +paramedics +parameter +parameters +parametric +paramilitary +paramount +paramours +paramus +parana +paranoia +paranoiac +paranoid +paranoids +paranormal +parapet +paraphernalia +paraphrase +paraphrased +paraphrases +paraphrasing +paraplegia +paraplegic +paraprofessional +paraprofessionals +paras +parasite +parasites +parasitic +paratrooper +paratroopers +paratroops +parc +parcel +parceling +parcells +parcels +parch +parched +parchment +pardee +pardo +pardon +pardoned +pardoning +pardons +pardue +pare +pared +paredes +parent +parentage +parental +parented +parentheses +parenthesis +parenthetical +parenthetically +parenthood +parenting +parents +pares +parfait +pari +pariah +pariahs +paribas +parietal +parikh +parimutuel +paring +paris +parish +parishes +parishioner +parishioners +parisian +parisians +parisienne +parities +parity +park +parka +parkas +parke +parked +parker +parkers +parkes +parkin +parking +parkinson +parkinsonism +parkland +parks +parkway +parlance +parlay +parlayed +parley +parliament +parliamentarian +parliamentarians +parliamentary +parliaments +parlor +parlors +parlour +parlous +parma +parmalat +parmentier +parmesan +parnassus +parnell +parochial +parochialism +parodi +parodied +parodies +parody +parole +paroled +parolees +paroles +parquet +parr +parra +parried +parries +parris +parrish +parrot +parroted +parrots +parrott +parry +parrying +pars +parse +parsed +parser +parses +parsifal +parsimony +parsing +parsippany +parsley +parson +parsonage +parsons +part +partake +partaking +parte +parted +parter +parthenon +parthian +parti +partial +partiality +partially +participant +participants +participate +participated +participates +participating +participation +participative +participatory +participle +particle +particles +particular +particularity +particularly +particulars +particulate +particulates +partie +partied +parties +parting +partisan +partisans +partisanship +partition +partitioned +partitioning +partitions +partly +partner +partnered +partnering +partners +partnership +partnerships +parton +partridge +partridges +parts +parttime +partway +party +partying +pary +pas +pasa +pasadena +pascal +paschal +pasco +pascoe +pascual +paseo +pash +pasha +paso +pasok +pasqua +pasquale +pass +passable +passably +passage +passages +passageway +passageways +passat +passbook +passe +passed +passel +passenger +passengers +passer +passerby +passers +passersby +passes +passing +passion +passionate +passionately +passions +passive +passively +passivity +passkey +passmore +passover +passport +passports +password +passwords +past +pasta +pastas +paste +pasted +pastel +pastels +paster +pasternak +pastes +pasteur +pasteurization +pasteurized +pastiche +pastime +pastimes +pasting +pastor +pastora +pastoral +pastors +pastrami +pastrana +pastries +pastry +pasts +pasture +pastures +pasztor +pat +patagonia +patagonian +patch +patched +patches +patching +patchwork +patchy +pate +patel +patella +patent +patentable +patented +patenting +patently +patents +pater +paternal +paternalism +paternalistic +paternity +paterno +paterson +pates +path +pathan +pathetic +pathetically +pathfinder +pathfinders +pathogen +pathogenesis +pathogenic +pathogens +pathological +pathologically +pathologies +pathologist +pathologists +pathology +pathos +paths +pathway +pathways +patience +patient +patiently +patients +patina +patinkin +patio +patios +patisserie +patois +patriarch +patriarchal +patriarchate +patriarchs +patriarchy +patrice +patricia +patrician +patricians +patricio +patrick +patridge +patrimonial +patrimony +patriot +patriotic +patriotism +patriots +patrol +patrolled +patrolling +patrolman +patrolmen +patrols +patron +patronage +patroness +patronize +patronized +patronizing +patrons +patronymic +pats +patsies +patsy +patt +patted +patten +patter +pattern +patterned +patterning +patterns +patterson +patti +pattie +patties +patting +patton +patty +paty +paucity +paul +paula +paule +paulette +pauley +paulie +paulin +paulina +pauline +pauling +paull +paulo +paulos +pauls +paulsen +paulson +paunchy +paup +pauper +paupers +pause +paused +pauses +pausing +pavarotti +pave +paved +pavel +pavement +pavements +paver +paves +pavia +pavilion +pavilions +pavillon +paving +pavlov +pavlovian +pavo +paw +pawn +pawnbrokers +pawned +pawnee +pawns +pawnshop +pawpaw +paws +pax +paxson +paxton +pay +payable +payables +payback +paycheck +paychecks +payday +payed +payee +payer +payers +paying +payless +payload +payloads +paymaster +payment +payments +payne +payoff +payoffs +payola +payout +payouts +payroll +payrolls +pays +payson +payton +paz +pc +pct +pdl +pdq +pe +pea +peabody +peace +peaceable +peaceably +peaceful +peacefully +peacefulness +peacekeeper +peacekeepers +peacekeeping +peacemaker +peacemakers +peacemaking +peacetime +peach +peaches +peachtree +peachy +peacock +peacocks +peak +peake +peaked +peaking +peaks +peal +peale +pean +peanut +peanuts +pear +pearce +pearl +pearle +pearlman +pearls +pearlstein +pearly +pearman +pears +pearse +pearson +peart +peas +peasant +peasantry +peasants +pease +peat +peavey +peavy +pebble +pebbled +pebbles +pebbly +pecan +pecans +peccadilloes +pech +peck +pecker +peckham +pecking +pecks +pecorino +pectin +pectoral +pectoris +peculiar +peculiarities +peculiarity +peculiarly +pecuniary +ped +pedagogic +pedagogical +pedagogy +pedal +pedaling +pedalling +pedals +pedant +pedantic +pedantry +peddle +peddled +peddler +peddlers +peddles +peddling +pederasty +pedersen +pederson +pedestal +pedestals +pedestrian +pedestrians +pediatric +pediatrician +pediatricians +pediatrics +pedicab +pedicure +pedicures +pedigree +pedigrees +pedophile +pedophilia +pedro +pedroza +pee +peebles +peed +peeing +peek +peeked +peeking +peeks +peel +peele +peeled +peeler +peeling +peels +peep +peephole +peeping +peeps +peer +peerage +peered +peering +peerless +peers +pees +peet +peete +peeve +peeved +peeves +peevish +peewee +peg +pegasus +pegged +pegging +peggy +pegs +pei +peine +peirce +pejorative +pekin +peking +pekka +pelagic +pele +peles +pelham +pelican +pelicans +pelkey +pell +pella +pellagra +pellegrino +pellet +pelleted +pelletier +pellets +pellucid +pelosi +pelt +pelted +peltier +pelton +pelts +peltz +pelvic +pelvis +pember +pemberton +pembroke +pemex +pen +pena +penal +penalities +penalize +penalized +penalizes +penalizing +penalties +penalty +penance +penang +pence +penchant +pencil +penciled +pencilled +pencils +pend +pendant +pendants +pendency +pender +pendergrass +pending +pendleton +pendulous +pendulum +penelope +penetrable +penetrate +penetrated +penetrates +penetrating +penetration +penfield +penfold +peng +penguin +penguins +penicillin +penile +peninsula +peninsular +peninsulas +penis +penises +penitent +penitentiary +penman +penmanship +penn +penna +pennant +pennants +penned +penney +pennies +penniless +penniman +penning +pennington +pennsylvania +pennsylvanian +pennsylvanians +penny +pennzoil +penobscot +penrose +pens +pensacola +pense +pension +pensionable +pensioner +pensioners +pensions +pensive +penske +pent +penta +pentagon +pentagons +pentathlon +pentax +pentecost +pentecostal +pentecostals +penthouse +pentium +penultimate +penumbra +penurious +penury +peon +peonies +peons +peony +people +peopled +peoples +peoria +pep +pepe +peper +pepi +pepper +peppercorn +peppercorns +pepperdine +peppered +pepperell +pepperidge +peppering +peppermint +pepperoni +peppers +peppy +pepsi +pepsico +peptic +peptide +peptides +per +pera +peralta +perc +perceive +perceived +perceives +perceiving +percent +percentage +percentages +percentile +percentiles +percents +perceptible +perception +perceptions +perceptive +perceptual +perch +perchance +perched +perches +perchlorate +percival +percolate +percolating +percolation +percolator +percussion +percussionist +percussive +percutaneous +percy +perdition +perdue +pere +peregrinations +peregrine +pereira +perelman +peremptory +perennial +perennially +perennials +peres +peretti +peretz +perez +perf +perfect +perfected +perfecting +perfection +perfectionism +perfectionist +perfectionists +perfectly +perfidious +perfidy +perforated +perforation +perforations +perforce +perform +performace +performance +performances +performed +performer +performers +performing +performs +perfume +perfumed +perfumes +perfunctory +pergola +perhaps +peri +peridot +perigee +perignon +peril +perilous +perilously +perils +perimeter +perimeters +period +periodic +periodical +periodically +periodicals +periodontal +periods +peripatetic +peripheral +peripherally +peripherals +periphery +periscope +perish +perishable +perished +peristyle +periwinkle +perjure +perjured +perjury +perk +perked +perkin +perking +perkins +perks +perky +perl +perla +perle +perlis +perlman +perlmutter +perlow +perm +permanence +permanent +permanente +permanently +permeability +permeable +permeate +permeated +permeates +permeating +permian +permissable +permissibility +permissible +permission +permissions +permissive +permissiveness +permit +permits +permitted +permittee +permitting +permutation +permutations +pernell +pernicious +pernod +pero +peron +peronist +perot +peroxide +perp +perpendicular +perpendicularity +perpetrate +perpetrated +perpetrating +perpetrator +perpetrators +perpetual +perpetually +perpetuate +perpetuated +perpetuates +perpetuating +perpetuation +perpetuity +perplex +perplexed +perplexes +perplexing +perplexity +perquisite +perquisites +perret +perrier +perrin +perrins +perron +perry +perryman +pers +perse +persecute +persecuted +persecutes +persecuting +persecution +persecutions +persecutor +persecutors +persephone +persepolis +perseus +perseverance +persevere +persevered +persevering +pershing +persia +persian +persians +persico +persimmon +persist +persistance +persisted +persistence +persistent +persistently +persisting +persists +person +persona +personable +personae +personage +personages +personal +personalities +personality +personalization +personalize +personalized +personalizes +personalizing +personally +personals +personification +personified +personifies +personify +personnel +persons +perspective +perspectives +perspiration +perspiring +persuade +persuaded +persuades +persuading +persuasion +persuasions +persuasive +persuasively +persuasiveness +persue +pert +pertain +pertained +pertaining +pertains +perth +pertinence +pertinent +pertinently +perturb +perturbation +perturbations +perturbed +perturbs +pertussis +peru +perugia +perusal +peruse +perused +peruses +perusing +peruvian +peruvians +pervade +pervaded +pervades +pervading +pervasive +pervasiveness +perverse +perversely +perversion +perversions +perversity +pervert +perverted +perverting +perverts +pes +pesa +pesach +pesci +peseta +pesetas +peshawar +pesky +peso +pesos +pessimism +pessimist +pessimistic +pessimists +pest +pestana +pester +pestered +pestering +pesticide +pesticides +pestilence +pestle +pests +pet +peta +petal +petals +petaluma +pete +peter +peterborough +petered +petering +peterman +peters +petersburg +petersen +peterson +petit +petite +petites +petition +petitioned +petitioner +petitioners +petitioning +petitions +peto +petr +petra +petree +petrels +petri +petrie +petrified +petro +petrobras +petrocelli +petrochemical +petrochemicals +petrodollars +petrol +petroleos +petroleum +petronas +petros +petrus +pets +petted +petter +petterson +pettersson +petticoat +petticoats +pettigrew +pettiness +petting +pettis +pettit +petty +petulance +petulant +petunia +petunias +peugeot +pew +pews +pewter +peyton +pf +pfaff +pfeffer +pfeiffer +pfennig +pfizer +pg +pgm +ph +phaedra +phage +phair +phalanx +phallic +phallus +pham +phan +phantasmagoria +phantom +phantoms +phar +pharaoh +pharaohs +pharaonic +phare +pharisees +pharm +pharma +pharmaceutical +pharmaceuticals +pharmacia +pharmacies +pharmacist +pharmacists +pharmacologic +pharmacological +pharmacologist +pharmacology +pharmacotherapy +pharmacy +pharoah +pharos +pharynx +phase +phased +phaseout +phaser +phasers +phases +phasing +phat +pheasant +pheasants +phelan +phelps +phenix +phenobarbital +phenol +phenolic +phenom +phenomena +phenomenal +phenomenally +phenomenology +phenomenon +phenotypes +phenotypic +phenylalanine +phenylketonuria +pheromone +pheromones +phew +phi +phil +philadelphia +philadelphian +philadelphians +philander +philandering +philanthropic +philanthropies +philanthropist +philanthropists +philanthropy +philately +philbrick +philharmonic +philip +philippa +philippe +philippi +philippine +philippines +philips +philistines +phill +phillies +phillip +phillipe +phillipines +phillippe +phillips +philly +philo +philodendron +philological +philology +philosopher +philosophers +philosophic +philosophical +philosophically +philosophies +philosophizing +philosophy +philosphy +philpot +philpott +phipps +phlebitis +phlegm +phlegmatic +phnom +phobia +phobias +phobic +phobos +phoebe +phoenicia +phoenician +phoenicians +phoenix +phone +phonebook +phoned +phoneme +phonemes +phones +phonetic +phonetically +phonetics +phoney +phonic +phonics +phoniness +phoning +phonograph +phony +phooey +phosphate +phosphates +phosphor +phosphorescence +phosphoric +phosphorous +phosphors +phosphorus +photo +photochemical +photocopied +photocopier +photocopiers +photocopies +photocopy +photocopying +photodynamic +photoelectric +photogenic +photograph +photographed +photographer +photographers +photographic +photographically +photographing +photographs +photography +photojournalism +photojournalist +photometer +photomontage +photon +photonics +photons +photoreceptor +photos +photoshop +photosynthesis +photovoltaic +phrase +phrased +phraseology +phrases +phrasing +phrygian +phu +phut +phyllis +phylum +phys +physic +physical +physicality +physically +physicals +physician +physicians +physicist +physicists +physics +physio +physiologic +physiological +physiologically +physiologist +physiology +physiotherapist +physiotherapy +physique +phytoplankton +pi +pia +piacenza +piaget +pianist +pianists +piano +pianos +piazza +pic +pica +picard +picaresque +picas +picasso +picayune +piccadilly +piccolo +pick +pickard +picked +pickens +picker +pickerel +pickering +pickers +picket +picketed +picketers +picketing +pickets +pickett +picking +pickings +pickle +pickled +pickles +pickling +pickoff +pickpocket +pickpockets +picks +pickup +pickups +pickwick +picky +picnic +picnicking +picnics +pico +picoseconds +pics +pict +pictet +picton +pictorial +picture +pictured +pictures +picturesque +picturing +piddle +piddling +pidgeon +pie +piece +pieced +piecemeal +pieces +piecework +piecing +pied +piedmont +piedra +pier +pierce +pierced +pierces +piercing +piero +pierpont +pierre +piers +pierson +pies +piet +pieter +pieties +pietro +piety +piffle +pig +pigeon +pigeonhole +pigeonholed +pigeonholing +pigeons +pigging +piggish +piggly +piggy +piggyback +piggybacked +piggybacking +piglet +piglets +pigment +pigmentation +pigments +pignone +pigs +pigskin +pik +pike +piker +pikes +pil +pilar +pilasters +pilate +pilcher +pile +piled +piles +pileup +pilferage +pilfered +pilfering +pilgrim +pilgrimage +pilgrimages +pilgrims +piling +pilings +pilkington +pill +pillage +pillaged +pillaging +pillar +pillared +pillars +pillbox +piller +pilling +pillion +pilloried +pillory +pillow +pillows +pills +pillsbury +pilon +pilot +piloted +piloting +pilotless +pilots +pilsner +pima +pimentel +pimento +pimentos +pimp +pimpernel +pimping +pimple +pimples +pimps +pin +pina +pinafore +pinball +pincer +pincers +pinch +pinched +pincher +pinchers +pinching +pinckney +pincus +pinder +pine +pineal +pineapple +pineapples +pined +pineda +pines +pinewood +piney +ping +pinhead +pinhole +pinholes +pining +pinion +pink +pinkas +pinker +pinkerton +pinkie +pinkish +pinkney +pinko +pinks +pinky +pinn +pinna +pinnacle +pinnacles +pinned +pinner +pinning +pino +pinocchio +pinochet +pinon +pinot +pinpoint +pinpointed +pinpointing +pinpoints +pinprick +pinpricks +pins +pinsker +pinsky +pinson +pinstripe +pinstriped +pinstripes +pint +pinta +pinter +pinto +pints +pion +pioneer +pioneered +pioneering +pioneers +pious +piously +pip +pipe +piped +pipeline +pipelines +piper +pipers +pipes +pipette +pipettes +piping +pipkin +pipkins +pippen +pippin +pips +pipsqueak +piquant +pique +piqued +piques +pir +piracy +piranha +pirate +pirated +pirates +piratical +pirating +pirelli +pires +pirie +piro +pirouette +pirouettes +pirro +pisa +pisani +pisano +piscataway +pisces +pisco +piss +pissed +pisses +pissing +pistachio +pistachios +piste +pistol +pistols +piston +pistons +pit +pita +pitas +pitch +pitched +pitcher +pitchers +pitches +pitchfork +pitchforks +pitching +pitchman +pitfall +pitfalls +pith +pithy +pitiable +pitied +pities +pitiful +pitifully +pitiless +pitman +pitney +pits +pitt +pittance +pittard +pitted +pitter +pitting +pittman +pitts +pittsburg +pittsburgh +pittsfield +pitty +pituitary +pity +pitying +piu +pius +pivot +pivotal +pivoted +pivoting +pix +pixar +pixel +pixels +pixie +pizazz +pizza +pizzas +pizzazz +pizzeria +pizzerias +pk +pl +pla +placard +placards +placate +placating +place +placebo +placebos +placed +placement +placements +placenta +placer +places +placid +placidly +placido +placing +placket +plagiarism +plagiarize +plagiarized +plagiarizing +plague +plagued +plagues +plaguing +plaid +plain +plainclothes +plainer +plainfield +plainly +plainness +plains +plainsong +plaint +plaintiff +plaintiffs +plaintive +plaintively +plait +plaited +plan +planar +planck +plane +planed +planeload +planer +planes +planet +planetarium +planetary +planets +planing +plank +planking +planks +plankton +planned +planner +planners +planning +plano +plans +plant +plantagenet +plantain +plantains +plantar +plantation +plantations +plante +planted +planter +planters +planting +plantings +plants +plaque +plaques +plas +plasma +plasminogen +plasmodium +plass +plaster +plasterboard +plastered +plasterer +plastering +plasters +plasterwork +plastic +plasticine +plasticizer +plastics +plastique +plat +plata +plate +plateau +plateaued +plateaus +plated +platelet +platelets +platen +plater +plates +platform +platforms +plath +plating +platinum +platitude +platitudes +plato +platonic +platoon +platoons +platt +platte +platter +platters +platypus +platz +plaudits +plausibility +plausible +plausibly +play +playa +playable +playback +playbill +playbook +playbooks +playboy +played +player +players +playful +playfully +playfulness +playground +playgrounds +playhouse +playing +playmaker +playmaking +playmate +playmates +playoff +playoffs +playpen +playroom +plays +playtex +plaything +playthings +playtime +playwright +playwrights +plaza +plazas +plea +plead +pleaded +pleading +pleadings +pleads +pleas +pleasant +pleasantly +pleasanton +pleasantries +pleasantry +pleasants +please +pleased +pleaser +pleases +pleasing +pleasingly +pleasurable +pleasure +pleasures +pleat +pleated +pleats +plebeian +plebeians +plebian +plebiscite +pled +pledge +pledged +pledger +pledges +pledging +pleiades +plein +pleistocene +plena +plenary +plenitude +plentiful +plentifully +plenty +plenum +plessey +plethora +pleurisy +plex +plexiglas +plexiglass +plexus +pli +pliable +pliant +plied +pliers +plies +plight +plimpton +plinth +pliny +pliocene +plock +plod +plodded +plodder +plodding +plonk +plop +plopped +plops +plot +plots +plott +plotted +plotter +plotters +plotting +plough +ploughed +ploughing +plover +plow +plowed +plowing +plows +plowshares +ploy +ploys +pluck +plucked +plucking +plucks +plucky +plug +plugged +pluggers +plugging +plugs +plum +plumage +plumb +plumbed +plumber +plumbers +plumbing +plumbs +plume +plumer +plumes +plummer +plummet +plummeted +plummeting +plummets +plump +plumped +plums +plunder +plundered +plunderers +plundering +plunge +plunged +plunger +plunges +plunging +plunk +plunked +plunkett +plunking +plural +pluralism +pluralist +pluralistic +plurality +pluribus +plus +pluses +plush +plusses +plutarch +pluto +plutocrats +plutonic +plutonium +ply +plying +plymouth +plywood +pm +pneumatic +pneumatics +pneumococcal +pneumonia +pneumonic +po +poach +poached +poacher +poachers +poaching +poche +pocked +pocket +pocketbook +pocketbooks +pocketed +pocketful +pocketing +pockets +pockmarked +poco +pocono +pocus +pod +podesta +podge +podiatrist +podiatry +podium +podiums +pods +podunk +poe +poem +poems +poeple +poer +poet +poetess +poetic +poetically +poetics +poetry +poets +pogo +pogrom +pogroms +pogue +poh +pohl +poignancy +poignant +poignantly +poindexter +poinsettia +poinsettias +point +pointe +pointed +pointedly +pointer +pointers +pointing +pointless +points +pointy +poirier +poise +poised +poison +poisoned +poisoner +poisoning +poisonings +poisonous +poisons +poisson +poke +poked +poker +pokes +pokeweed +pokey +poking +pokorny +poky +pol +polak +poland +polar +polaris +polarities +polarity +polarization +polarize +polarized +polarizer +polarizes +polarizing +polaroid +polaroids +pole +polecat +polemic +polemical +polemicist +polemics +poles +poley +poli +police +policed +policeman +policemen +polices +policewoman +policewomen +policies +policing +policy +policyholder +policyholders +policymaker +policymakers +policymaking +poling +polio +polis +polish +polished +polishing +politburo +polite +politely +politeness +politic +political +politically +politician +politicians +politicization +politicize +politicized +politicizing +politicking +politico +politicos +politics +politique +polito +polity +polk +polka +polkas +poll +pollack +pollak +pollan +pollard +polled +pollen +pollens +polley +pollinate +pollinated +pollination +polling +pollitt +pollock +polloi +polls +pollster +pollsters +pollutant +pollutants +pollute +polluted +polluter +polluters +pollutes +polluting +pollution +pollux +polly +pollyanna +polo +polonaise +polonia +polonium +polonius +polos +pols +polson +poltergeist +poltergeists +poly +polycarbonate +polychlorinated +polychrome +polycyclic +polyester +polyesters +polyethylene +polygamous +polygamy +polyglot +polygon +polygonal +polygons +polygram +polygraph +polygraphs +polymath +polymer +polymerase +polymers +polymorphic +polymorphism +polymorphisms +polynesia +polynesian +polynomial +polynomials +polyp +polyphonic +polyphony +polypropylene +polyps +polysaccharide +polystyrene +polysyllabic +polytechnic +polytechnique +polytheistic +polyurethane +polyvinyl +pom +pomade +pombo +pomegranate +pomerantz +pomeroy +pomodoro +pomona +pomp +pompa +pompadour +pompano +pompey +pompidou +pompons +pomposity +pompous +pon +ponce +poncho +ponchos +pond +ponder +pondered +pondering +ponderosa +ponderous +ponderously +ponders +ponds +pong +pongo +ponied +ponies +pons +pont +ponte +pontiac +pontiff +pontifical +pontificate +pontificated +pontificating +pontification +pontius +pontoon +pontoons +pontus +pony +ponytail +ponytails +ponzi +poo +pooch +pooches +poodle +poodles +poof +pooh +poohed +pookie +pool +poole +pooled +pooler +pooley +pooling +pools +poolside +poon +poop +pooped +pooper +pooping +poops +poor +poore +poorer +poorest +poorhouse +poorly +poorman +poors +pop +popcorn +pope +popes +popeye +popeyes +popham +popkin +poplar +poplin +popov +popp +poppa +poppe +popped +popper +poppers +poppies +popping +poppy +poppycock +pops +popsicle +populace +populaire +popular +popularity +popularization +popularize +popularized +popularizer +popularizing +popularly +populate +populated +populates +populating +population +populations +populism +populist +populists +populous +por +porcelain +porcelains +porch +porches +porcupine +porcupines +pore +pored +pores +porgy +poring +pork +porky +porn +porno +pornographer +pornographers +pornographic +pornography +poros +porous +porpoise +porpoises +porridge +porsche +porsches +port +porta +portability +portable +portables +portage +portal +portals +porte +ported +portend +portending +portends +portent +portentous +portents +porteous +porter +porterhouse +porters +portfolio +portfolios +porthole +portia +portico +portillo +porting +portion +portions +portland +portly +portman +portnoy +porto +portrait +portraits +portray +portrayal +portrayals +portrayed +portraying +portrays +ports +portsmouth +portugal +portugese +portuguese +pos +posada +posadas +pose +posed +poseidon +posen +poser +poses +posession +poseur +posey +posh +posible +posies +posing +posit +posited +positing +position +positional +positioned +positioning +positions +positive +positively +positives +positivism +positivity +positron +positrons +posits +posner +poss +posse +posses +possess +possessed +possesses +possessing +possession +possessions +possessive +possessiveness +possessor +possibilities +possibility +possibilty +possible +possibles +possiblities +possiblity +possibly +possum +possums +post +postage +postal +postcard +postcards +postcode +postdoctoral +posted +poster +posterior +posterity +posters +postgraduate +posthumous +posthumously +posthumus +posting +postings +postion +postive +postman +postmark +postmarked +postmaster +postmasters +postmodern +postmortem +postnatal +poston +postpartum +postpone +postponed +postponement +postponements +postpones +postponing +posts +postscript +postscripts +postseason +postulate +postulated +postulates +postulating +postural +posture +postures +posturing +postwar +pot +potable +potash +potassium +potato +potatoes +potbellied +pote +potemkin +potency +potent +potentate +potentates +potential +potentialities +potentially +potentials +potholders +pothole +potholed +potholes +potier +potion +potions +potlatch +potluck +potomac +potpourri +pots +potsdam +potshots +pottage +potted +potter +potters +pottery +potting +pottinger +pottle +potts +potty +potvin +pouch +pouches +poughkeepsie +poul +poulenc +poulet +poulter +poultice +poultices +poultry +pounce +pounced +pouncing +pound +poundage +pounded +pounder +pounders +pounding +pounds +pour +poured +pouring +pours +pousse +poussin +pout +pouted +pouting +pouts +pouty +poverty +pow +powder +powdered +powdering +powders +powdery +powell +power +powerboat +powerbook +powerbooks +powered +powerful +powerfully +powergen +powerhouse +powerhouses +powering +powerless +powerlessness +powerpc +powerplant +powerplants +powerpoint +powers +powertrain +pows +pox +poynter +pozner +pp +ppb +ppi +ppm +pps +pq +pr +practicable +practical +practicality +practically +practice +practiced +practices +practicing +practise +practised +practises +practising +practitioner +practitioners +practive +prada +pradesh +prado +praetorian +prager +pragmatic +pragmatically +pragmatism +pragmatist +pragmatists +prague +prairie +prairies +praise +praised +praises +praiseworthy +praising +prakash +pram +pran +prance +prancer +prances +prancing +prange +prank +pranks +prankster +pranksters +prasad +prater +pratfall +pratfalls +prather +pratt +prattle +pravda +prawn +prawns +praxis +pray +prayed +prayer +prayerful +prayerfully +prayers +praying +prays +pre +preach +preached +preacher +preachers +preaches +preaching +preachy +preadolescent +preakness +preamble +prearranged +prebble +precancerous +precarious +precariously +precariousness +precast +precaution +precautionary +precautions +precede +preceded +precedence +precedent +precedential +precedents +precedes +preceding +preceeding +precept +precepts +precession +precinct +precincts +precious +preciously +preciousness +precipice +precipitate +precipitated +precipitating +precipitation +precipitous +precipitously +precis +precise +precisely +precision +preclinical +preclude +precluded +precludes +precluding +precocious +preconceived +preconception +preconceptions +precondition +preconditions +preconfigured +precooked +precursor +precursors +predate +predated +predates +predating +predator +predators +predatory +predawn +predecessor +predecessors +predefined +predestination +predestined +predetermine +predetermined +predicament +predicaments +predicate +predicated +predicates +predications +predict +predictability +predictable +predictably +predicted +predicting +prediction +predictions +predictive +predictor +predictors +predicts +predilection +predilections +predispose +predisposed +predisposing +predisposition +predispositions +prednisone +predominance +predominant +predominantly +predominate +predominated +predominately +predominates +predominating +preeminence +preeminent +preempt +preempted +preempting +preemption +preemptive +preemptively +preempts +preen +preening +preens +preexisting +pref +prefab +prefabricated +prefabrication +preface +prefaced +prefaces +prefacing +prefatory +prefect +prefectural +prefecture +prefectures +prefer +preferable +preferably +prefered +preference +preferences +preferential +preferentially +preferred +preferring +prefers +prefix +prefixes +preflight +preformed +prefrontal +pregnancies +pregnancy +pregnant +prego +preheat +preheated +prehistoric +preiss +prejudge +prejudgment +prejudice +prejudiced +prejudices +prejudicial +prejudicing +prelate +prelates +prelim +preliminaries +preliminarily +preliminary +prelims +preloaded +prelude +preludes +prem +premarital +premature +prematurely +premeditated +premeditation +premenstrual +premier +premiere +premiered +premieres +premiering +premiers +premiership +premise +premised +premises +premium +premiums +premodern +premonition +premonitory +prenatal +prendergast +prentice +prenuptial +preoccupation +preoccupations +preoccupied +preoccupies +preoccupy +preordained +prep +prepackaged +prepaid +preparation +preparations +preparatory +prepare +prepared +preparedness +preparer +preparers +prepares +preparing +prepay +prepaying +prepayment +prepayments +preplanned +preplanning +preponderance +preposition +prepositions +preposterous +preposterously +prepped +preppie +prepping +preppy +preprinted +preprints +preproduction +preprogrammed +preps +prepublication +prequalified +prequel +prerecorded +prerequisite +prerequisites +prerogative +prerogatives +pres +presage +presaged +presages +presaging +presbyterian +presbyterians +preschool +preschoolers +prescience +prescient +presciently +prescott +prescribe +prescribed +prescriber +prescribes +prescribing +prescription +prescriptions +preseason +preselected +presence +presences +present +presentable +presentation +presentations +presented +presenter +presenters +presenting +presently +presentment +presents +preservation +preservationist +preservative +preservatives +preserve +preserved +preserver +preservers +preserves +preserving +preset +preside +presided +presidencies +presidency +president +presidente +presidential +presidentially +presidents +presides +presiding +presidio +presley +press +presse +pressed +pressel +presser +presses +pressing +pressings +pressler +pressly +pressman +pressmen +pressure +pressured +pressures +pressuring +pressurization +pressurize +pressurized +prest +prestige +prestigious +presto +preston +presumably +presume +presumed +presumes +presuming +presumption +presumptions +presumptive +presumptively +presumptuous +presuppose +presupposes +presupposition +presuppositions +pret +pretax +preteens +pretence +pretend +pretended +pretender +pretenders +pretending +pretends +pretense +pretenses +pretension +pretensions +pretentious +pretentiousness +preternatural +pretext +pretexts +pretoria +pretrial +prettier +prettiest +pretty +pretzel +pretzels +preuss +prev +prevail +prevailed +prevailing +prevails +prevalence +prevalent +prevaricate +prevent +preventable +preventative +prevented +preventing +prevention +preventive +prevents +preview +previewed +previewing +previews +previn +previous +previously +prevost +prewar +prey +preyed +preying +preys +prez +pri +priam +price +priced +priceless +prices +pricey +prichard +pricier +priciest +pricing +prick +pricked +prickly +pricks +pricy +pride +prided +prideful +prides +pried +pries +priest +priestess +priesthood +priestley +priestly +priests +prieta +prieto +prig +priggish +prim +prima +primacy +primal +primaries +primarily +primary +primate +primates +primavera +prime +primed +primer +primers +primes +primetime +primeval +priming +primitive +primitives +primly +primo +primordial +primp +primrose +primroses +primus +prince +princely +princes +princess +princesses +princeton +principal +principalities +principality +principally +principals +principe +principi +principia +principle +principled +principles +prine +pring +pringle +print +printable +printed +printemps +printer +printers +printing +printings +printmaker +printmakers +printmaking +printout +printouts +prints +prinz +prior +priori +priorities +prioritize +prioritized +prioritizes +prioritizing +priority +priors +priory +pris +priscilla +prise +prism +prismatic +prisms +prison +prisoner +prisoners +prisons +prissy +pristine +pritchard +pritchett +pritzker +privacy +privado +privat +private +privateers +privately +privates +privation +privations +privatisation +privatization +privatizations +privatize +privatized +privatizing +privet +privies +privilege +privileged +privileges +privy +prix +prize +prized +prizes +prizing +prizm +prn +pro +proactive +proactively +prob +probabilistic +probabilities +probability +probable +probably +probate +probation +probationary +probationer +probationers +probative +probe +probed +probenecid +probes +probing +probity +problem +problematic +problematical +problematically +problems +proboscis +probus +proc +procedural +procedurally +procedure +procedures +proceed +proceeded +proceeding +proceedings +proceeds +process +processed +processes +processing +procession +processional +processions +processor +processors +proclaim +proclaimed +proclaiming +proclaims +proclamation +proclamations +proclivities +proclivity +procrastinate +procrastinating +procrastination +procrastinator +procrastinators +procreate +procreation +procter +proctor +proctors +procure +procured +procurement +procurements +procurer +procurers +procures +procuring +prod +prodded +prodding +prodi +prodigal +prodigies +prodigious +prodigiously +prodigy +prods +produce +produced +producer +producers +produces +producing +product +production +productions +productive +productively +productivity +products +prof +profane +profanities +profanity +profess +professed +professes +professing +profession +professional +professionalism +professionalized +professionalizing +professionally +professionals +professions +professor +professorial +professors +professorship +professorships +proffer +proffered +proffitt +proficiency +proficient +profile +profiled +profiler +profiles +profiling +profit +profitability +profitable +profitably +profited +profiteer +profiteering +profiteers +profiting +profits +profitt +profligacy +profligate +profound +profoundly +profs +profundity +profuse +profusely +profusion +prog +progam +progenitor +progeny +progesterone +prognoses +prognosis +prognostic +prognostication +prognostications +program +programing +programmability +programmable +programmatic +programmatically +programme +programmed +programmer +programmers +programmes +programming +programs +progress +progressed +progresses +progressing +progression +progressions +progressive +progressively +progressives +progressivism +progressivity +prohibit +prohibited +prohibiting +prohibition +prohibitionist +prohibitionists +prohibitions +prohibitive +prohibitively +prohibitory +prohibits +project +projected +projectile +projectiles +projecting +projection +projections +projective +projector +projectors +projects +prokofiev +prolactin +proles +proletarian +proletariat +proliferate +proliferated +proliferates +proliferating +proliferation +prolific +prolifically +proline +prolix +prolog +prologue +prolong +prolongation +prolonged +prolonging +prolongs +prom +promenade +prometheus +prominence +prominent +prominently +promiscuity +promiscuous +promise +promised +promises +promising +promissory +promo +promontory +promos +promote +promoted +promoter +promoters +promotes +promoting +promotion +promotional +promotions +prompt +prompted +prompter +prompting +promptly +prompts +proms +promulgate +promulgated +promulgating +promulgation +prone +proneness +prong +pronged +pronghorn +prongs +pronoun +pronounce +pronounced +pronouncement +pronouncements +pronounces +pronouncing +pronouns +pronto +pronunciation +pronunciations +proof +proofed +proofing +proofread +proofreading +proofs +prop +propaganda +propagandist +propagandistic +propagandists +propagandize +propagate +propagated +propagates +propagating +propagation +propane +propel +propellant +propellants +propelled +propeller +propellers +propelling +propels +propensities +propensity +proper +properly +properties +property +prophecies +prophecy +prophesied +prophesies +prophesy +prophesying +prophet +prophetic +prophetically +prophets +prophylactic +prophylactics +prophylaxis +propitious +propoganda +proponent +proponents +proportion +proportional +proportionality +proportionally +proportionate +proportionately +proportioned +proportions +proposal +proposals +propose +proposed +proposes +proposing +proposition +propositioned +propositioning +propositions +propound +propounded +propped +propping +propranolol +proprietary +proprietor +proprietors +proprietorship +propriety +props +propulsion +propylene +prorated +pros +prosaic +proscenium +prosciutto +proscribe +proscribed +proscribes +proscription +proscriptions +prose +prosecutable +prosecute +prosecuted +prosecutes +prosecuting +prosecution +prosecutions +prosecutor +prosecutorial +prosecutors +proselytize +proselytizing +prosody +prospect +prospecting +prospective +prospectively +prospector +prospectors +prospects +prospectus +prospectuses +prosper +prospered +prospering +prosperity +prospero +prosperous +prospers +prosser +prostaglandin +prostate +prostatectomy +prostatic +prostheses +prosthesis +prosthetic +prosthetics +prostitute +prostitutes +prostitution +prostrate +prostration +prot +protagonist +protagonists +protean +protease +protect +protectable +protected +protecting +protection +protectionism +protectionist +protectionists +protections +protective +protectively +protectiveness +protector +protectorate +protectors +protects +protege +proteges +protein +proteins +protest +protestant +protestantism +protestants +protestation +protestations +protested +protester +protesters +protesting +protestor +protestors +protests +proteus +proto +protocol +protocols +proton +protons +prototype +prototypes +prototypical +prototyping +protozoa +protozoan +protracted +protrude +protruding +protrusions +protuberance +protuberances +proud +prouder +proudest +proudfoot +proudly +proulx +proust +prout +prov +provable +provably +prove +proved +proven +provenance +provencal +provence +provender +provera +proverb +proverbial +proverbs +proves +provide +provided +providence +provident +providential +provider +providers +provides +providing +province +provinces +provincetown +provincial +provincialism +provincially +provine +proving +provision +provisional +provisionally +provisioning +provisions +proviso +provisos +provo +provocateur +provocateurs +provocation +provocations +provocative +provocatively +provoke +provoked +provokes +provoking +provolone +provost +prow +prowess +prowl +prowler +prowling +proxies +proxima +proximate +proximately +proximity +proxy +prozac +prude +prudence +prudent +prudential +prudently +prudery +prudhomme +prudish +pruitt +prune +pruned +pruner +prunes +pruning +prurient +prussia +prussian +pry +pryce +prying +pryor +psalm +psalmist +psalms +pseudo +pseudoephedrine +pseudomonas +pseudonym +pseudonymous +pseudonyms +pseudoscience +pshaw +psi +psoriasis +psoriatic +pst +psych +psyche +psyched +psychedelic +psyches +psychiatric +psychiatrist +psychiatrists +psychiatry +psychic +psychical +psychically +psychics +psycho +psychoactive +psychoanalysis +psychoanalyst +psychoanalysts +psychoanalytic +psychobabble +psychogenic +psychological +psychologically +psychologist +psychologists +psychology +psychopath +psychopathic +psychopathology +psychopaths +psychopathy +psychopharmacology +psychos +psychosis +psychosocial +psychosomatic +psychotherapeutic +psychotherapist +psychotherapists +psychotherapy +psychotic +psychotics +psychotropic +psyllium +pt +ptolemaic +ptolemy +pts +pty +pu +pub +puberty +pubic +public +publically +publican +publicans +publication +publications +publicis +publicist +publicists +publicity +publicize +publicized +publicizes +publicizing +publicly +publics +publish +published +publisher +publishers +publishes +publishing +pubs +puccini +puck +pucker +puckers +puckett +puckish +pudding +puddings +puddle +puddles +pudgy +puebla +pueblo +puente +puerile +puerto +puff +puffed +puffer +puffery +puffiness +puffing +puffins +puffs +puffy +puget +pugh +puglia +pugliese +pugnacious +puig +puke +puked +puking +pulaski +pulchritude +pulitzer +pulitzers +pull +pullback +pullbacks +pulldown +pulled +pullen +puller +pulley +pulleys +pulliam +pullin +pulling +pullman +pullout +pullouts +pullover +pullovers +pulls +pulmonary +pulp +pulping +pulpit +pulpits +pulps +pulpwood +puls +pulsar +pulsating +pulse +pulsed +pulses +pulsing +pulver +pulverize +pulverized +puma +pumice +pummel +pummeling +pummelled +pump +pumped +pumpernickel +pumping +pumpkin +pumpkins +pumps +pun +punch +punchbowl +punched +puncher +punches +punching +punchline +punchy +punctilious +punctuality +punctuate +punctuated +punctuating +punctuation +puncture +punctured +punctures +puncturing +pundit +punditry +pundits +pungent +punic +punish +punishable +punished +punisher +punishes +punishing +punishment +punishments +punitive +punjab +punjabi +punk +punks +punky +punny +puns +punt +punta +punter +punters +punting +punto +punts +puny +pup +pupa +pupil +pupils +puppet +puppeteer +puppeteers +puppetry +puppets +puppies +puppy +pups +pur +pura +purcell +purchase +purchased +purchaser +purchasers +purchases +purchasing +purdah +purdie +purdue +purdum +purdy +pure +purebred +purebreds +puree +pureed +purely +purer +purest +purgatory +purge +purged +purges +purging +puri +purification +purified +purifier +purify +purifying +purim +purina +purist +purists +puritan +puritanical +puritanism +puritans +purity +purloined +purnell +purple +purples +purplish +purport +purported +purportedly +purporting +purports +purpose +purposeful +purposefully +purposefulness +purposeless +purposely +purposes +purpura +purr +purring +purrs +purse +pursed +purser +purses +pursing +pursuant +pursue +pursued +pursuer +pursuers +pursues +pursuing +pursuit +pursuits +purty +purvey +purveyor +purveyors +purview +purvis +puryear +pus +pusan +push +pushbutton +pushed +pusher +pushers +pushes +pushing +pushover +pushups +pushy +pusillanimous +puss +pussy +pussycat +pussycats +put +putative +putatively +puting +putnam +putrid +puts +putsch +putt +putted +putter +puttering +putting +putts +putty +putz +puy +puzzle +puzzled +puzzlement +puzzler +puzzles +puzzling +pwr +pye +pygmalion +pygmies +pygmy +pyke +pyle +pyles +pylon +pylons +pylori +pyne +pyongyang +pyramid +pyramidal +pyramids +pyrenees +pyro +pyromaniacs +pyron +pyrotechnic +pyrotechnics +pyrrhic +pythagoras +pythagorean +python +pyxis +qantas +qatar +qed +qi +qian +qiao +qing +qingdao +ql +qs +qt +qu +qua +quack +quackery +quacks +quad +quadra +quadrangle +quadrant +quadrants +quadratic +quadrennial +quadriceps +quadrilateral +quadrillion +quadriplegic +quadrupeds +quadruple +quadrupled +quadrupling +quads +quaffing +quagmire +quagmires +quai +quaid +quail +quails +quaint +quaintly +quake +quaker +quakers +quakes +quaking +qual +qualcomm +qualification +qualifications +qualified +qualifier +qualifiers +qualifies +qualify +qualifying +qualitative +qualitatively +qualities +quality +qualms +quam +quan +quandaries +quandary +quang +quant +quanta +quantico +quantifiable +quantification +quantified +quantifies +quantify +quantifying +quantitative +quantitatively +quantities +quantity +quantum +quarantine +quarantined +quarantines +quark +quarks +quarrel +quarreling +quarrelling +quarrels +quarrelsome +quarries +quarry +quarrying +quart +quarter +quarterback +quarterbacks +quarterdeck +quartered +quartering +quarterly +quartermaster +quarters +quartet +quartets +quartile +quarto +quarts +quartz +quasar +quasars +quash +quashed +quashing +quasi +quasimodo +quaternary +quatre +quattro +quay +quayle +que +queasy +quebec +quebrada +queen +queenie +queens +queensberry +queensland +queensryche +queensway +queer +queers +quell +quelled +quelling +quells +quench +quencher +quenching +quentin +queried +queries +querulous +query +querying +ques +quest +quested +quester +questing +question +questionable +questioned +questioner +questioners +questioning +questionings +questionnaire +questionnaires +questions +questor +quests +quetzal +queue +queued +queues +queuing +quevedo +qui +quibble +quibbles +quibbling +quiche +quick +quicken +quickened +quickening +quickens +quicker +quickest +quickie +quickly +quickness +quicksand +quicksands +quicksilver +quicktime +quid +quiescent +quiet +quieted +quieter +quietest +quieting +quietly +quietness +quiets +quigg +quigley +quill +quills +quilt +quilted +quilter +quilters +quilting +quilts +quin +quince +quincey +quincy +quine +quinine +quinlan +quinn +quinones +quint +quinta +quintal +quintana +quintanilla +quintero +quintessence +quintessential +quintessentially +quintet +quintile +quintiles +quintin +quinto +quinton +quintuple +quintupled +quintuplets +quintus +quip +quipped +quips +quire +quirk +quirke +quirks +quirky +quiroga +quiroz +quis +quisling +quist +quit +quite +quito +quits +quitter +quitters +quitting +quiver +quivering +quixote +quixotic +quiz +quizzed +quizzes +quizzical +quizzically +quizzing +quo +quoc +quorum +quota +quotable +quotas +quotation +quotations +quote +quoted +quotes +quoth +quotient +quoting +ra +raab +rab +rabat +rabbani +rabbi +rabbinic +rabbinical +rabbis +rabbit +rabbits +rabbitt +rabble +rabi +rabid +rabidly +rabies +rabin +rabkin +rabobank +raby +raccoon +raccoons +race +racecourse +raced +racehorse +racehorses +racer +racers +races +racetrack +racetracks +raceway +rach +rachel +rachmaninoff +racial +racialism +racialist +racially +racicot +racine +racing +racism +racist +racists +rack +racked +racket +racketeer +racketeering +racketeers +rackets +racking +racks +raconteur +racoon +racquet +racquetball +racquets +racy +rad +rada +radar +radars +radcliff +radcliffe +rader +radford +radial +radially +radials +radian +radiance +radiant +radiate +radiated +radiates +radiating +radiation +radiator +radiators +radical +radicalism +radicalization +radicalize +radicalized +radically +radicals +radin +radio +radioactive +radioactivity +radioed +radiography +radioisotope +radiological +radiologist +radiologists +radiology +radios +radish +radishes +radisson +radium +radius +radke +radley +radnor +radon +rae +raeburn +rael +rafael +rafale +raff +rafferty +raffi +raffish +raffle +raffles +rafik +rafsanjani +raft +rafted +rafter +rafters +rafting +rafts +rag +ragan +rage +raged +rages +ragged +raggedy +raging +ragland +rags +ragtag +ragtime +ragu +ragweed +rah +rahim +rahman +rahul +raid +raided +raider +raiders +raiding +raids +raikes +rail +railcar +railcars +railed +railing +railings +railroad +railroaded +railroaders +railroading +railroads +rails +railway +railways +raimondi +rain +rainbow +rainbows +raincoat +raincoats +raindance +raindrop +raindrops +raine +rained +rainer +raines +rainey +rainfall +rainfalls +rainforest +rainforests +rainier +rainiest +raining +rainmaker +rainman +rainout +rains +rainstorm +rainstorms +rainwater +rainy +rais +raisa +raise +raised +raiser +raisers +raises +raisin +raising +raisins +raison +raisonne +raitt +raj +raja +rajah +rajesh +rajiv +rajneesh +raju +rak +rake +raked +raker +rakes +raking +rakish +rakoff +raleigh +rales +ralf +rall +rallied +rallies +rally +rallying +ralph +ralphs +ralston +ram +rama +ramada +ramadan +ramage +ramakrishna +ramallah +raman +ramanathan +ramat +ramble +rambled +rambler +ramblers +rambling +ramblings +rambo +rambunctious +ramesh +ramey +rami +ramification +ramifications +ramirez +ramiro +rammed +rammer +ramming +ramo +ramon +ramona +ramos +ramp +rampage +rampaged +rampages +rampaging +rampant +rampart +ramparts +ramped +ramping +ramps +ramrod +rams +ramsay +ramses +ramsey +ramshackle +ran +rana +rance +ranch +rancher +ranchero +ranchers +ranches +ranching +rancho +rancid +rancor +rancorous +rand +randa +randal +randall +randell +randi +randle +randolph +random +randomized +randomly +randomness +rands +randy +rang +range +ranged +rangel +ranger +rangers +ranges +ranging +rangoon +rangy +rani +rank +ranked +ranker +rankin +ranking +rankings +rankle +rankled +rankles +ranks +rann +ransack +ransacked +ransacking +ransom +ransome +ransoms +rant +ranted +ranting +rantings +rants +rao +raoul +rap +rapacious +rapacity +rape +raped +raper +rapes +rapeseed +raphael +rapid +rapidity +rapidly +rapids +rapier +raping +rapist +rapists +rapoport +rapp +rappaport +rapped +rappel +rappelling +rapper +rappers +rapping +rapport +rapprochement +raps +rapt +raptly +raptor +raptors +rapture +rapturous +raquel +rare +rarefied +rarely +rareness +rarer +rarest +raring +rarities +rarity +ras +rasa +rascal +rascals +rasch +rash +rasheed +rashers +rashes +rashid +rashly +rasmussen +rasp +raspberries +raspberry +rasps +raspy +rast +raster +rat +rata +ratatouille +ratchet +ratcheted +ratcheting +ratchets +ratcliff +ratcliffe +rate +rated +ratepayer +rater +raters +rates +rath +rathbone +rather +rathole +ratification +ratified +ratifies +ratify +ratifying +rating +ratings +ratio +ration +rational +rationale +rationales +rationalisation +rationalism +rationalist +rationality +rationalization +rationalizations +rationalize +rationalized +rationalizing +rationally +rationed +rationing +rations +ratios +ratliff +ratner +rato +raton +rats +ratt +rattan +ratti +ratting +rattle +rattled +rattler +rattlers +rattles +rattlesnake +rattlesnakes +rattling +ratty +rau +rauch +raucous +raul +raunchy +ravage +ravaged +ravages +ravaging +rave +raved +ravel +raveling +raven +ravening +ravenous +ravens +ravenswood +raver +raves +ravi +ravin +ravine +ravines +raving +ravings +ravioli +ravishing +raw +rawalpindi +rawhide +rawlings +rawlins +rawlinson +rawls +rawness +rawson +ray +raya +rayburn +raychem +rayed +rayford +raymond +rayne +rayner +raynor +rayon +rays +raytheon +raza +raze +razed +razing +razor +razorback +razors +razzle +razzmatazz +rb +rbis +rd +re +rea +reabsorb +reabsorbed +reach +reachable +reached +reaches +reaching +reacquire +reacquired +reacquisition +react +reacted +reacting +reaction +reactionaries +reactionary +reactions +reactivate +reactivated +reactivates +reactivating +reactivation +reactive +reactivity +reactor +reactors +reacts +read +readability +readable +reade +reader +readers +readership +readied +readier +readies +readily +readiness +reading +readings +readjust +readjusted +readjusting +readjustment +readjustments +readmitted +readout +readouts +reads +ready +readying +readymade +reaffirm +reaffirmation +reaffirmed +reaffirming +reaffirms +reagan +reaganomics +reagent +reagents +real +realign +realigned +realigning +realignment +realignments +realise +realised +realising +realism +realist +realistic +realistically +realists +realities +reality +realizable +realization +realizations +realize +realized +realizes +realizing +reallocate +reallocated +reallocating +reallocation +really +realm +realms +realpolitik +reals +realtime +realtor +realtors +realty +realy +ream +reamer +reams +reanimate +reap +reaped +reaper +reaping +reappear +reappearance +reappeared +reappears +reapply +reappoint +reappointed +reappointment +reapportionment +reappraisal +reappraised +reaps +rear +reardon +reared +rearguard +rearing +rearm +rearmament +rearming +rearrange +rearranged +rearrangement +rearrangements +rearranging +rearrested +rears +rearview +reasearch +reason +reasonable +reasonableness +reasonably +reasoned +reasoner +reasoning +reasons +reassemble +reassembled +reassembling +reassembly +reassert +reasserted +reasserting +reassertion +reasserts +reassess +reassessed +reassesses +reassessing +reassessment +reassessments +reassign +reassigned +reassigning +reassignment +reassignments +reassume +reassurance +reassurances +reassure +reassured +reassures +reassuring +reassuringly +reattach +reattached +reauthorization +reauthorize +reauthorizing +reaves +reawaken +reawakened +reawakening +reb +reba +reback +rebalance +rebalancing +rebar +rebate +rebates +rebbe +rebecca +rebekah +rebel +rebelled +rebelling +rebellion +rebellions +rebellious +rebelliousness +rebels +rebid +rebirth +rebirths +reboot +rebooted +rebooting +reborn +rebound +rebounded +rebounding +rebounds +rebroadcast +rebuff +rebuffed +rebuffing +rebuffs +rebuild +rebuilding +rebuilds +rebuilt +rebuke +rebuked +rebukes +rebuking +rebus +rebut +rebuts +rebuttable +rebuttal +rebuttals +rebutted +rebutting +rec +recalcitrance +recalcitrant +recalculate +recalculated +recalculating +recalculation +recalibrating +recalibration +recall +recalled +recalling +recalls +recant +recantation +recanted +recanting +recap +recapitalization +recapitalize +recapitulate +recapitulates +recapitulation +recapped +recapping +recaps +recapture +recaptured +recapturing +recast +recasting +recasts +recede +receded +recedes +receding +receipt +receipts +receivable +receivables +receive +received +receiver +receivers +receivership +receives +receiving +recent +recently +receptacle +receptacles +reception +receptionist +receptionists +receptions +receptive +receptiveness +receptivity +receptor +receptors +recertification +recertified +recess +recessed +recesses +recessing +recession +recessionary +recessions +recessive +recharge +rechargeable +recharged +recharger +recharging +recheck +rechecked +rechecking +recherche +rechristened +recht +recidivism +recidivist +recidivists +recieve +recieved +recipe +recipes +recipient +recipients +reciprocal +reciprocally +reciprocate +reciprocated +reciprocating +reciprocation +reciprocity +recirculate +recirculated +recirculation +recision +recital +recitals +recitation +recitations +recite +recited +recites +reciting +reck +reckless +recklessly +recklessness +reckon +reckoned +reckoning +reckonings +reckons +reclaim +reclaimed +reclaiming +reclaims +reclamation +reclassification +reclassified +reclassify +reclassifying +recline +recliner +reclining +recluse +reclusive +reco +recoding +recognise +recognising +recognition +recognitions +recognizable +recognizably +recognizance +recognize +recognized +recognizes +recognizing +recoil +recoiled +recoiling +recoils +recollect +recollected +recollecting +recollection +recollections +recollects +recombinant +recombine +recombining +recommend +recommendation +recommendations +recommended +recommending +recommends +recommissioned +recommit +recommitment +recompense +recomputed +recon +reconcile +reconciled +reconciles +reconciliation +reconciliations +reconciling +recondite +recondition +reconditioned +reconditioning +reconfiguration +reconfigure +reconfigured +reconfiguring +reconfirm +reconfirmed +reconnaissance +reconnect +reconnected +reconnecting +reconnection +reconnects +reconquered +reconsider +reconsideration +reconsidered +reconsidering +reconsiders +reconstitute +reconstituted +reconstituting +reconstitution +reconstruct +reconstructed +reconstructing +reconstruction +reconstructions +reconstructive +reconvene +reconvened +reconvenes +record +recordable +recorded +recorder +recorders +recording +recordings +recordkeeping +records +recount +recounted +recounting +recounts +recoup +recouped +recouping +recoups +recourse +recover +recoverable +recovered +recoveries +recovering +recovers +recovery +recreate +recreated +recreates +recreating +recreation +recreational +recrimination +recriminations +recruit +recruited +recruiter +recruiters +recruiting +recruitment +recruits +rectal +rectangle +rectangles +rectangular +rectification +rectified +rectifier +rectify +rectifying +rectitude +rector +rectory +rectum +recuperate +recuperating +recuperation +recuperative +recur +recurred +recurrence +recurrences +recurrent +recurring +recurs +recusal +recuse +recused +recyclable +recycle +recycled +recyclers +recycles +recycling +red +redacted +redbook +redcoats +redd +redden +redder +redding +reddish +reddy +redecorate +redecorated +redecorating +redecoration +rededication +redeem +redeemable +redeemed +redeemer +redeeming +redeems +redefine +redefined +redefines +redefining +redefinition +redemption +redemptions +redemptive +redeploy +redeployed +redeploying +redeployment +redesign +redesignated +redesignation +redesigned +redesigning +redesigns +redevelop +redeveloped +redevelopment +redeye +redfern +redfield +redfish +redford +redgrave +redhead +redheaded +redheads +redial +redid +reding +redington +redirect +redirected +redirecting +redirection +redirects +rediscover +rediscovered +rediscovering +rediscovery +redistribute +redistributed +redistributing +redistribution +redistributive +redistricting +redline +redlining +redman +redmond +redneck +rednecks +redness +redo +redoing +redondo +redone +redouble +redoubled +redoubling +redoubt +redoubtable +redound +redpath +redraft +redrafted +redrafting +redraw +redrawing +redrawn +redraws +redress +redressed +redressing +reds +redskin +redskins +redstone +reduce +reduced +reducer +reduces +reducible +reducing +reductio +reduction +reductions +redundancies +redundancy +redundant +redux +redwood +redwoods +ree +reebok +reece +reed +reeder +reeds +reedy +reef +reefer +reefs +reek +reeked +reeking +reeks +reel +reelected +reelection +reeled +reeling +reels +reemerge +reemerged +reemergence +reemphasize +reemployment +reenact +reenacted +reenacting +reenactment +reenactments +reengineering +reenter +reentered +reentering +reentry +rees +reese +reestablish +reestablished +reestablishing +reevaluate +reevaluated +reevaluating +reevaluation +reeve +reeves +reexamination +reexamine +reexamined +reexamining +ref +refashion +refer +refered +referee +refereed +refereeing +referees +reference +referenced +references +referencing +referenda +referendum +referendums +referent +referential +refering +referral +referrals +referred +referring +refers +refiled +refill +refilled +refilling +refills +refinance +refinanced +refinancing +refine +refined +refinement +refinements +refiner +refineries +refiners +refinery +refines +refining +refinish +refinished +refinishing +refit +refitted +refitting +reflect +reflected +reflecting +reflection +reflections +reflective +reflector +reflectors +reflects +reflex +reflexes +reflexive +reflexively +reflexivity +reflux +refocus +refocused +refocuses +refocusing +reforestation +reform +reforma +reformat +reformation +reformatory +reformatted +reformatting +reformed +reformer +reformers +reforming +reformist +reformists +reforms +reformulate +reformulated +reformulation +refraction +refractive +refractories +refractory +refrain +refrained +refraining +refrains +reframe +reframed +refresh +refreshed +refresher +refreshers +refreshes +refreshing +refreshingly +refreshment +refreshments +refried +refrigerant +refrigerants +refrigerate +refrigerated +refrigeration +refrigerator +refrigerators +refs +refuel +refueling +refuge +refugee +refugees +refuges +refund +refundable +refunded +refunding +refunds +refurbish +refurbished +refurbishing +refurbishment +refusal +refusals +refuse +refused +refuses +refusing +refutation +refutations +refute +refuted +refutes +refuting +reg +regain +regained +regaining +regains +regal +regalado +regale +regaled +regalia +regaling +regally +regals +regan +regard +regarded +regarding +regardless +regards +regatta +regattas +regency +regenerate +regenerated +regenerates +regenerating +regeneration +regenerative +regent +regents +reggae +reggie +reggio +regie +regime +regimen +regimens +regiment +regimental +regimentation +regimented +regiments +regimes +regina +reginald +region +regional +regionalism +regionalization +regionalized +regionally +regionals +regions +regis +register +registered +registering +registers +registrant +registrants +registrar +registrars +registration +registrations +registries +registry +rego +regrading +regress +regressed +regresses +regressing +regression +regressions +regressive +regret +regretful +regretfully +regrets +regrettable +regrettably +regretted +regretting +regroup +regrouped +regrouping +regs +regula +regular +regularities +regularity +regularly +regulars +regulate +regulated +regulates +regulating +regulation +regulations +regulator +regulators +regulatory +regurgitates +regurgitation +reh +rehab +rehabilitate +rehabilitated +rehabilitating +rehabilitation +rehabilitations +rehabilitative +rehash +rehashes +rehashing +rehear +rehearing +rehearsal +rehearsals +rehearse +rehearsed +rehearses +rehearsing +reheat +reheating +rehire +rehired +rehiring +rehm +rehnquist +rehydrate +rehydration +rei +reich +reichardt +reichel +reichelt +reicher +reichman +reichstag +reid +reif +reiff +reign +reigned +reigning +reignite +reignited +reigns +reiki +reilley +reilly +reimbursable +reimburse +reimbursed +reimbursement +reimbursements +reimburses +reimbursing +reimer +reimpose +reimposed +reimposition +rein +reina +reinaldo +reincarnate +reincarnated +reincarnation +reindeer +reine +reined +reiner +reinforce +reinforced +reinforcement +reinforcements +reinforces +reinforcing +reinhard +reinhardt +reinhart +reinhold +reining +reins +reinsert +reinserted +reinstall +reinstalled +reinstalling +reinstate +reinstated +reinstatement +reinstates +reinstating +reinstitute +reinsurance +reinsured +reinsurer +reintegrate +reintegrated +reintegrating +reintegration +reinterpret +reinterpretation +reinterpreted +reinterpreting +reintroduce +reintroduced +reintroduces +reintroducing +reintroduction +reinvent +reinvented +reinventing +reinvention +reinvents +reinvest +reinvested +reinvesting +reinvestment +reinvigorate +reinvigorated +reinvigorating +reinvigoration +reis +reiss +reissue +reissued +reissues +reissuing +reit +reiter +reiterate +reiterated +reiterates +reiterating +reiteration +reitman +reits +reject +rejected +rejecting +rejection +rejections +rejects +rejoice +rejoiced +rejoices +rejoicing +rejoin +rejoinder +rejoined +rejoining +rejoins +rejuvenate +rejuvenated +rejuvenating +rejuvenation +rekindle +rekindled +rekindling +rel +relabel +relais +relapse +relapsed +relapses +relapsing +relate +related +relatedness +relates +relating +relation +relational +relations +relationship +relationships +relative +relatively +relatives +relativism +relativist +relativistic +relativity +relaunch +relaunched +relaunches +relaunching +relax +relaxant +relaxants +relaxation +relaxed +relaxes +relaxing +relay +relayed +relaying +relays +relearn +relearned +relearning +releasable +release +released +releases +releasing +relegate +relegated +relegates +relegating +relegation +relent +relented +relenting +relentless +relentlessly +relentlessness +relevance +relevancy +relevant +relevent +reliability +reliable +reliably +reliance +reliant +relic +relics +relied +relief +reliefs +relies +relieve +relieved +reliever +relievers +relieves +relieving +religion +religions +religiosity +religious +religiously +religous +relinquish +relinquished +relinquishes +relinquishing +relinquishment +relish +relished +relishes +relishing +relive +relived +relives +reliving +reload +reloaded +reloading +reloads +relocate +relocated +relocates +relocating +relocation +relocations +reluctance +reluctant +reluctantly +rely +relying +rem +remade +remain +remainder +remained +remaining +remains +remake +remakes +remaking +remand +remanded +remanufacture +remanufactured +remanufacturing +remapping +remark +remarkable +remarkably +remarked +remarking +remarks +remarque +remarriage +remarried +remarry +rematch +rembrandt +remedial +remediate +remediation +remedied +remedies +remedy +remedying +remember +remembered +remembering +remembers +remembrance +remembrances +remind +reminded +reminder +reminders +reminding +reminds +remington +reminisce +reminisced +reminiscence +reminiscences +reminiscent +reminisces +reminiscing +remiss +remission +remissions +remit +remittance +remittances +remitted +remix +remixed +remixes +remnant +remnants +remo +remodel +remodeled +remodeling +remodelling +remorse +remorseful +remorseless +remote +remotely +remoteness +remotest +removable +removal +removals +remove +removed +remover +removes +removing +remuneration +remunerative +remus +remy +ren +rena +renaissance +renal +rename +renamed +renames +renaming +renard +renata +renate +renato +renaud +renault +rend +renda +rendell +render +rendered +renderers +rendering +renderings +renders +rendezvous +rendition +renditions +rene +renee +renegade +renegades +renege +reneged +reneges +reneging +renegotiate +renegotiated +renegotiating +renegotiation +renegotiations +renew +renewable +renewal +renewals +renewed +renewing +renews +renfrew +renier +renminbi +renne +renner +rennet +rennie +reno +renoir +renominated +renomination +renounce +renounced +renounces +renouncing +renovate +renovated +renovating +renovation +renovations +renovators +renown +renowned +rensselaer +rent +rentable +rental +rentals +rented +renter +renters +renting +renton +rents +renumbered +renunciation +renwick +reo +reoccupied +reoccupy +reoccur +reoccurrence +reopen +reopened +reopening +reopens +reorder +reordered +reordering +reorganization +reorganizations +reorganize +reorganized +reorganizes +reorganizing +reorient +reorientation +rep +repack +repackage +repackaged +repackages +repackaging +repacked +repacking +repaid +repaint +repainted +repainting +repair +repairable +repaired +repairer +repairing +repairman +repairmen +repairs +reparation +reparations +repartee +repast +repatriate +repatriated +repatriation +repaved +repay +repayable +repaying +repayment +repayments +repays +repeal +repealed +repealing +repeals +repeat +repeatable +repeated +repeatedly +repeater +repeaters +repeating +repeats +repel +repellant +repelled +repellent +repellents +repelling +repels +repent +repentance +repentant +repented +repenting +repents +repercussion +repercussions +repertoire +repertoires +repertory +repetition +repetitions +repetitious +repetitive +repetitively +repetto +rephrase +rephrased +rephrasing +replace +replaceable +replaced +replacement +replacements +replaces +replacing +replant +replanted +replanting +replay +replayed +replaying +replays +replenish +replenished +replenishing +replenishment +replete +replica +replicable +replicas +replicate +replicated +replicates +replicating +replication +replications +replied +replies +reply +replying +repo +reponse +reponses +repopulate +repopulation +report +reportable +reportage +reported +reportedly +reporter +reporters +reporting +reports +repos +repose +reposition +repositioned +repositioning +repositories +repository +repossessed +repossession +repossessions +repp +reprehensible +represent +representation +representational +representations +representative +representatives +represented +representing +represents +repress +repressed +repressing +repression +repressions +repressive +reprice +repricing +reprieve +reprimand +reprimanded +reprimands +reprint +reprinted +reprinting +reprints +reprisal +reprisals +reprise +reprises +reprising +reproach +reprobate +reprocess +reprocessed +reprocessing +reproduce +reproduced +reproduces +reproducibility +reproducible +reproducing +reproduction +reproductions +reproductive +reprogram +reprogrammed +reprogramming +reproof +reps +repsol +reptile +reptiles +republic +republica +republican +republicanism +republicans +republics +republique +republish +republished +republishing +repudiate +repudiated +repudiates +repudiating +repudiation +repugnance +repugnant +repulse +repulsed +repulsing +repulsive +repurchase +repurchased +repurchases +repurchasing +reputable +reputation +reputations +repute +reputed +reputedly +request +requested +requester +requesting +requests +requiem +require +required +requirement +requirements +requires +requiring +requisite +requisites +requisition +requisitioned +requisitions +reran +reread +rereading +reroute +rerouted +rerouting +rerun +rerunning +reruns +res +resale +resales +reschedule +rescheduled +rescheduling +rescind +rescinded +rescinding +rescission +rescue +rescued +rescuer +rescuers +rescues +rescuing +reseach +reseal +resealable +resealed +research +researched +researcher +researchers +researches +researching +resection +reseda +resell +reseller +resellers +reselling +resells +resemblance +resemblances +resemble +resembled +resembles +resembling +resend +resent +resented +resentful +resenting +resentment +resentments +resents +reservation +reservations +reserve +reserved +reserves +reserving +reservist +reservists +reservoir +reservoirs +reset +resets +resetting +resettle +resettled +resettlement +reshape +reshaped +reshapes +reshaping +reshuffle +reshuffled +reshuffles +reshuffling +reside +resided +residence +residences +residencies +residency +resident +residential +residents +resides +residing +residual +residuals +residue +residues +resign +resignation +resignations +resigned +resigning +resigns +resilience +resiliency +resilient +resin +resins +resist +resistance +resistances +resistant +resisted +resistence +resisters +resisting +resistive +resistors +resists +resize +resized +resizing +resnick +reso +resold +resolute +resolutely +resolution +resolutions +resolve +resolved +resolver +resolves +resolving +resonable +resonance +resonant +resonate +resonated +resonates +resonating +resort +resorted +resorting +resorts +resound +resounded +resounding +resoundingly +resounds +resource +resourceful +resourcefulness +resources +respect +respectability +respectable +respectably +respected +respectful +respectfully +respecting +respective +respectively +respects +respirable +respiration +respirator +respirators +respiratory +respite +resplendent +respond +responded +respondent +respondents +responder +responders +responding +responds +response +responses +responsibilities +responsibility +responsible +responsiblity +responsibly +responsive +responsiveness +rest +restart +restarted +restarting +restate +restated +restatement +restatements +restates +restating +restaurant +restaurants +restaurateur +restaurateurs +rested +restful +resting +restitution +restive +restless +restlessly +restlessness +restock +restocking +reston +restoration +restorations +restorative +restore +restored +restorer +restores +restoring +restrain +restrained +restraining +restrains +restraint +restraints +restrict +restricted +restricting +restriction +restrictions +restrictive +restricts +restroom +restructure +restructured +restructures +restructuring +restructurings +rests +restyle +restyling +resubmit +resubmitted +resubmitting +result +resultant +resulted +resulting +results +resume +resumed +resumes +resuming +resumption +resupplied +resupply +resurface +resurfaced +resurfaces +resurfacing +resurgence +resurgent +resurrect +resurrected +resurrecting +resurrection +resurrects +resuscitate +resuscitated +resuscitating +resuscitation +ret +retail +retailed +retailer +retailers +retailing +retails +retain +retained +retainer +retainers +retaining +retains +retake +retaken +retakes +retaking +retaliate +retaliated +retaliates +retaliating +retaliation +retaliatory +retard +retardant +retardation +retarded +retarding +retards +retch +retching +rete +retell +retelling +retells +retention +retentive +retest +retested +retesting +rethink +rethinking +rethinks +rethought +reticence +reticent +retief +retina +retinal +retinitis +retinoblastoma +retinol +retinue +retire +retired +retiree +retirees +retirement +retirements +retires +retiring +retitled +retold +retook +retool +retooled +retooling +retort +retorted +retorts +retouched +retouching +retrace +retraced +retraces +retracing +retract +retractable +retracted +retracting +retraction +retractions +retracts +retrain +retrained +retraining +retransmission +retransmitted +retread +retreads +retreat +retreated +retreating +retreats +retrench +retrenched +retrenching +retrenchment +retrenchments +retrial +retribution +retried +retrievable +retrieval +retrieve +retrieved +retriever +retrievers +retrieves +retrieving +retro +retroactive +retroactively +retroactivity +retrofit +retrofits +retrofitted +retrofitting +retrograde +retrospect +retrospective +retrospectively +retry +retrying +return +returnable +returned +returnees +returner +returning +returns +retype +retyped +retyping +reuben +reunification +reunified +reunify +reunion +reunions +reunite +reunited +reunites +reuniting +reusable +reuse +reuseable +reused +reusing +reuss +reuter +reuters +rev +revalidation +revaluation +revaluations +revalue +revalued +revaluing +revamp +revamped +revamping +revamps +revco +reveal +revealed +revealing +reveals +reveille +revel +revelation +revelations +revelatory +revelers +revell +revelry +revels +revenge +revenue +revenues +reverb +reverberate +reverberated +reverberates +reverberating +reverberations +revere +revered +reverence +reverend +reverent +reverential +reverently +reverie +reveries +reversal +reversals +reverse +reversed +reverser +reversers +reverses +reversibility +reversible +reversing +reversion +reversions +revert +reverted +reverting +reverts +review +reviewable +reviewed +reviewer +reviewers +reviewing +reviews +revile +reviled +revise +revised +revises +revising +revision +revisionism +revisionist +revisionists +revisions +revisit +revisited +revisiting +revisits +revitalization +revitalize +revitalized +revitalizing +revival +revivalist +revivals +revive +revived +revives +reviving +revlon +revocable +revocation +revoir +revoke +revoked +revokes +revoking +revolt +revolted +revolting +revolts +revolution +revolutionaries +revolutionary +revolutionist +revolutionists +revolutionize +revolutionized +revolutionizing +revolutions +revolve +revolved +revolver +revolvers +revolves +revolving +revs +revue +revues +revulsion +revved +revving +reward +rewarded +rewarding +rewards +rewind +rewinding +rewire +rewired +rewiring +reword +reworded +rewording +rework +reworked +reworking +rewritable +rewrite +rewrites +rewriting +rewritten +rewrote +rex +rey +reyes +reykjavik +reynaldo +reynard +reynold +reynolds +reza +rezoning +rg +rh +rha +rhapsodic +rhapsodizes +rhapsody +rhea +rhein +rheingold +rhesus +rhetoric +rhetorical +rhetorically +rhetorician +rhetoricians +rhett +rheumatic +rheumatism +rheumatoid +rheumatologist +rheumatology +rhine +rhinestone +rhinestones +rhinitis +rhino +rhinoceros +rhinos +rhizome +rhizomes +rho +rhoads +rhoda +rhode +rhodes +rhodesia +rhodesian +rhodium +rhododendron +rhonda +rhone +rhubarb +rhyme +rhymed +rhymer +rhymes +rhyming +rhythm +rhythmic +rhythmically +rhythms +ria +riad +rial +rials +rialto +rib +ribald +ribbed +ribbing +ribble +ribbon +ribbons +ribeiro +ribera +ribs +ric +rica +rican +ricans +ricard +ricardo +riccardi +riccardo +ricci +ricciardi +riccio +rice +rich +richard +richards +richardson +riche +richelieu +richer +riches +richest +richey +richfield +richie +richland +richly +richman +richmond +richness +richter +rick +rickert +rickets +ricketts +rickettsial +rickety +rickey +ricki +rickman +ricks +rickshaw +rickshaws +ricky +rico +ricochet +ricocheted +ricocheting +ricoh +ricotta +rid +riddance +riddell +ridden +ridder +ridding +riddle +riddled +riddler +riddles +ride +rideau +rider +riders +ridership +rides +ridge +ridged +ridgefield +ridges +ridgeway +ridgewood +ridgway +ridicule +ridiculed +ridicules +ridiculing +ridiculous +ridiculously +ridiculousness +riding +ridings +ridley +rids +riedel +rieder +rieger +riel +riemann +ries +riesling +rife +riff +riffle +riffraff +riffs +rifkin +rifkind +rifle +rifled +rifleman +rifles +rifling +rift +rifts +rig +riga +rigatoni +rigby +rigel +rigg +rigged +rigging +riggins +riggs +right +righted +righteous +righteously +righteousness +righter +rightful +rightfully +righthand +righthanded +righting +rightist +rightists +rightly +rightness +rights +rightward +rightwing +righty +rigid +rigidities +rigidity +rigidly +rigney +rigoletto +rigor +rigorous +rigorously +rigors +rigs +rigsby +rigueur +rijn +rike +riker +rikki +rile +riled +riles +riley +rill +rim +rima +rime +rimer +rimes +rimmed +rimmer +rims +rina +rincon +rind +rinds +rine +rinehart +ring +ringe +ringed +ringer +ringers +ringgit +ringgold +ringing +ringleader +ringleaders +ringlets +ringling +ringmaster +ringo +rings +ringside +ringwald +rink +rinks +rinse +rinsed +rinses +rinsing +rio +riopelle +riordan +rios +riot +rioted +rioters +rioting +riotous +riots +rip +ripa +riparian +ripe +ripen +ripened +ripeness +ripening +ripest +ripken +ripley +ripoff +ripoffs +riposte +ripped +ripper +ripping +ripple +rippled +ripples +rippling +rippon +rips +riptide +risa +risc +rise +risen +riser +risers +rises +rish +rising +risk +risked +riskier +riskiest +riskiness +risking +riskless +risks +risky +riso +risotto +risque +rist +ristorante +rit +rita +ritalin +ritchey +ritchie +rite +rites +ritter +ritts +ritual +ritualistic +ritualistically +ritualized +rituals +ritz +ritzy +riva +rival +rivaling +rivalries +rivalry +rivals +rivas +rive +riven +river +rivera +riverbank +riverbed +riverboat +riverdale +riverfront +riverhead +rivero +rivers +riverside +riverway +rives +rivet +riveted +riveter +riveting +rivets +riviera +rivieres +rivlin +rix +riyadh +riyals +rizzo +rizzoli +rm +rn +ro +roach +roaches +road +roadbed +roadblock +roadblocks +roadhouse +roadie +roadkill +roadless +roadmap +roadrunner +roadrunners +roads +roadshow +roadside +roadsides +roadster +roadsters +roadway +roadways +roadwork +roald +roam +roamed +roaming +roams +roan +roanoke +roar +roared +roaring +roars +roast +roasted +roaster +roasters +roasting +roasts +rob +robb +robbed +robber +robberies +robbers +robbery +robbie +robbin +robbing +robbins +robby +robe +robed +rober +roberson +robert +roberta +roberto +roberts +robertson +robes +robeson +robey +robin +robinette +robins +robinson +robison +roble +robles +robocop +robot +robotic +robotics +robots +robs +robson +robust +robustly +robustness +roby +robyn +roc +roca +rocca +rocco +roch +rocha +roche +rochelle +rocher +rochester +rock +rockabilly +rockaway +rocked +rockefeller +rockefellers +rocker +rockers +rocket +rocketed +rocketeer +rocketing +rocketry +rockets +rockett +rockettes +rockfish +rockford +rockies +rocking +rockingham +rockland +rockman +rockne +rocks +rockville +rockwell +rockwood +rocky +rococo +rod +roda +rodd +roddenberry +roddick +roddy +rode +rodent +rodents +rodeo +rodeos +roderick +rodger +rodgers +rodham +rodin +rodman +rodney +rodolfo +rodrick +rodrigo +rodrigues +rodriguez +rodriquez +rods +roe +roebuck +roeder +roehm +roelofs +roemer +roes +rog +rogan +rogelio +roger +rogers +rogoff +rogue +rogues +roguish +rohan +rohm +rohmer +rohr +rohrer +roi +roid +roil +roiled +roiling +roils +rojas +rojo +roland +rolando +role +roles +rolex +rolf +rolfe +roll +rolla +rollback +rolle +rolled +roller +rollerblade +rollercoaster +rollers +rollicking +rollie +rollin +rolling +rollins +rollout +rollover +rollovers +rolls +rolltop +rolly +rolodex +rom +roma +romain +romaine +roman +romana +romance +romances +romancing +romanesque +romania +romanian +romano +romanov +romans +romantic +romantically +romanticism +romanticize +romanticized +romanticizing +romantics +romany +rome +romeo +romer +romero +rominger +rommel +romney +romo +romp +romping +romps +roms +romulus +ron +rona +ronald +ronan +ronco +rond +ronda +ronde +rondeau +rone +roney +rong +rongji +roni +ronin +ronnie +ronning +ronny +ronson +roo +rood +roof +roofed +roofer +roofers +roofing +roofs +rooftop +rooftops +rook +rooker +rookie +rookies +rooks +room +roomful +roomier +roominess +rooming +roommate +roommates +rooms +roomy +rooney +roos +roose +roosevelt +roost +roosted +rooster +roosters +root +rooted +rooting +rootless +roots +rope +roped +roper +ropes +roping +roque +roquefort +roquette +rorschach +rory +ros +rosa +rosales +rosalia +rosalie +rosalind +rosalyn +rosamond +rosanna +rosaries +rosario +rosary +rosas +rosati +roscoe +rose +roseanne +rosebud +rosebush +roseland +rosella +rosemarie +rosemary +rosemont +rosen +rosenbaum +rosenberg +rosenblatt +rosenblum +rosenfeld +rosenfield +rosengarten +rosenman +rosenquist +rosenthal +rosenwald +rosenzweig +roser +roses +rosetta +rosette +rosettes +roseville +rosewater +rosewood +rosey +rosh +rosie +rosier +rosin +rosina +rosing +rosita +roslyn +ross +rosser +rossetti +rossi +rossini +rosslyn +rossman +rossmann +rosso +rost +rostenkowski +roster +rosters +rostov +rostrum +roswell +rosy +rot +rota +rotary +rotate +rotated +rotates +rotating +rotation +rotational +rotations +rotator +rote +rotenberg +roth +rothchild +rothenberg +rother +rothman +rothschild +rothstein +rotisserie +roto +rotor +rotors +rots +rotted +rotten +rottenberg +rotter +rotterdam +rotting +rottweiler +rotund +rotunda +rouen +rouge +rough +roughage +roughed +rougher +roughest +roughing +roughly +roughneck +roughnecks +roughness +roughshod +roughy +rouleau +roulette +round +roundabout +rounded +rounder +rounders +roundhouse +rounding +roundish +roundly +roundness +rounds +roundtable +roundtree +roundtrip +roundup +roundworm +rourke +rous +rouse +rouses +roush +rousing +rousseau +roussel +roustabout +rout +route +routed +router +routers +routes +routh +routine +routinely +routines +routing +routings +routs +roux +rove +roved +rover +rovers +roving +row +rowan +rowboat +rowdies +rowdiness +rowdy +rowe +rowed +rowell +rowen +rower +rowers +rowing +rowland +rowlands +rowley +rows +rox +roxanna +roxbury +roxie +roxy +roy +royal +royale +royalist +royally +royals +royalties +royalty +royce +royces +royer +royster +rozelle +rozen +rozier +rpm +rps +rs +rsvp +rt +rti +ru +rub +rubbed +rubber +rubberized +rubbermaid +rubbers +rubbery +rubbing +rubbish +rubble +rube +rubel +rubella +ruben +rubens +rubenstein +rubes +rubicam +rubicon +rubies +rubin +rubino +rubinstein +rubio +ruble +rubles +rubric +rubrics +rubs +ruby +ruch +ruck +rucker +ruckman +rucksack +ruckus +rud +rudd +rudder +rudderless +ruddock +ruddy +rude +rudel +rudely +rudeness +ruder +rudest +rudi +rudiment +rudimentary +rudiments +rudman +rudner +rudolf +rudolph +rudy +rudyard +rue +rued +ruediger +rueful +ruefully +ruel +rues +ruf +ruff +ruffian +ruffians +ruffin +ruffle +ruffled +ruffles +ruffling +rufus +rug +rugby +ruger +rugged +ruggedly +ruggedness +ruggiero +rugs +ruhollah +ruin +ruination +ruined +ruining +ruinous +ruins +ruiz +rule +rulebook +ruled +rulemaking +ruler +rulers +rules +ruling +rulings +rum +rumania +rumanian +rumba +rumble +rumbled +rumbles +rumbling +rumblings +ruminant +ruminate +ruminated +ruminating +rumination +ruminations +rummage +rummaging +rummy +rumney +rumor +rumored +rumors +rumour +rumours +rump +rumpled +rumpus +rums +run +runabout +runaround +runaway +runaways +rundle +rundown +rune +runes +rung +runge +rungs +runnels +runner +runners +running +runny +runoff +runs +runt +runtime +runway +runways +runyon +ruoff +rupe +rupee +rupees +rupert +rupiah +rupp +ruppert +ruprecht +rupture +ruptured +ruptures +rupturing +rural +rusch +ruse +ruses +rush +rushdie +rushed +rusher +rushers +rushes +rushing +rushmore +rusk +ruskin +russ +russe +russel +russell +russet +russia +russian +russians +russo +rust +rusted +rustic +rusting +rustle +rustlers +rustling +ruston +rusts +rusty +rut +rutgers +ruth +ruthenium +rutherford +ruthless +ruthlessly +ruthlessness +ruthven +rutland +rutledge +ruts +rutted +rutter +rutting +ruud +rwanda +rwandan +ryal +ryall +ryan +ryanair +ryde +ryden +ryder +rye +ryerson +ryland +ryman +rymer +ryutaro +sa +saab +saad +saal +saatchi +saavedra +sab +saba +sabah +sabato +sabbath +sabbatical +sabbaticals +sabe +sabella +sabena +saber +sabin +sabina +sabine +sabino +sabir +sable +sables +sabo +sabotage +sabotaged +sabotages +sabotaging +saboteur +saboteurs +sabra +sabre +sabres +sabrina +sac +saccharin +saccharine +sacco +sachs +sack +sackcloth +sacked +sackett +sacking +sackings +sacks +saco +sacra +sacrament +sacramento +sacraments +sacre +sacred +sacrifice +sacrificed +sacrifices +sacrificial +sacrificing +sacrilege +sacrilegious +sacrosanct +sacs +sad +sada +sadat +saddam +sadden +saddened +saddening +saddens +sadder +saddest +saddle +saddleback +saddlebag +saddlebags +saddled +saddler +saddles +saddling +sade +sadi +sadie +sadiq +sadism +sadist +sadistic +sadistically +sadler +sadly +sadness +sado +sadowski +saeed +saenz +safari +safaris +safe +safecracker +safeguard +safeguarded +safeguarding +safeguards +safekeeping +safely +safer +safes +safest +safeties +safety +safeway +saffer +safflower +saffron +safir +safire +safra +sag +saga +sagan +sagar +sagas +sage +sagebrush +sager +sages +sagged +sagging +saggy +saginaw +sagittarius +sago +sags +saguaro +saha +sahara +saharan +sai +saic +said +saif +saigon +sail +sailboat +sailboats +sailed +sailfish +sailing +sailings +sailor +sailors +sails +sain +sainsbury +saint +sainte +sainted +sainthood +saintly +saints +saipan +sais +saison +saito +sakaguchi +sakai +sakamoto +sake +sakes +sakhalin +saki +saks +sakura +sal +sala +salaam +salacious +salad +saladin +salads +salah +salam +salamander +salamanders +salameh +salami +salamis +salamon +salaried +salaries +salary +salas +salazar +sale +saleable +saleem +saleh +salem +salema +salerno +sales +salesman +salesmanship +salesmen +salespeople +salesperson +salespersons +saleswoman +salgado +salience +salient +salim +salina +salinas +saline +salinger +salinity +salisbury +salish +saliva +salivary +salivate +salivated +salivating +salk +sall +salle +sallee +salles +sallie +sallies +sally +salma +salman +salmi +salmon +salmonella +salmons +salome +salomon +salon +salons +saloon +saloons +salsa +salt +salted +salter +salters +saltier +saltiness +salting +salton +salts +saltwater +salty +saltzman +salubrious +salud +salus +salutary +salutation +salutations +salute +saluted +salutes +saluting +salvador +salvadoran +salvage +salvageable +salvaged +salvaging +salvation +salvatore +salve +salves +salvia +salvo +salvos +salzburg +salzman +sam +sama +samantha +samara +samaras +samaria +samaritan +samaritans +samba +sambo +sambuca +same +sameness +samet +sami +samir +samizdat +sammons +sammy +samoa +samoan +samora +samos +samper +sample +sampled +sampler +samplers +samples +sampling +samplings +sampo +sampras +sampson +sams +samson +samsonite +samsung +samuel +samuels +samuelson +samurai +san +sanborn +sanchez +sancho +sancta +sanctification +sanctified +sanctify +sanctifying +sanctimonious +sanctimony +sanction +sanctioned +sanctioning +sanctions +sanctity +sanctuaries +sanctuary +sanctum +sand +sandal +sandals +sandalwood +sandbag +sandbagged +sandbagging +sandbags +sandbar +sandberg +sandblasted +sandblasting +sandbox +sandburg +sanded +sandel +sander +sanders +sanderson +sandhill +sandi +sandia +sanding +sandinista +sandino +sandler +sandlot +sandman +sandor +sandoval +sandpaper +sandpiper +sandra +sandro +sands +sandstone +sandstorm +sandstorms +sandstrom +sandusky +sandwich +sandwiched +sandwiches +sandy +sane +saner +sanford +sang +sanger +sangh +sangiovese +sangster +sanguine +sanguinetti +sanhedrin +sani +sanitary +sanitation +sanitize +sanitized +sanitizing +sanity +sanjay +sanjeev +sanjiv +sank +sankey +sanna +sano +sans +sanskrit +sant +santa +santana +santander +santas +santayana +sante +santer +santiago +santini +santo +santoro +santos +sanz +sao +sap +sapiens +sapient +sapling +saplings +sapped +sapper +sapphic +sapphire +sapphires +sapping +sapporo +sappy +saps +sar +sara +saracens +sarah +sarajevo +saran +sarandon +sarasota +saratoga +sarawak +sarbanes +sarcasm +sarcastic +sarcastically +sarcoidosis +sarcoma +sarcophagus +sardine +sardines +sardinia +sardinian +sardonic +sardonically +sare +sargasso +sarge +sargeant +sargent +sari +sarin +saris +sark +sarkar +sarmiento +sarong +sarongs +sars +sartain +sartorial +sartre +sarum +sas +sash +sasha +sashes +sashimi +saskatchewan +saskatoon +sass +sassafras +sasser +sasson +sassy +sat +satan +satanic +satanism +satanist +satanists +satchel +satchels +sate +sated +sateen +satellite +satellites +sates +sather +satiate +satiated +satin +satire +satires +satiric +satirical +satirist +satirists +satirize +satirizes +satirizing +satisfaction +satisfactions +satisfactorily +satisfactory +satisfied +satisfies +satisfy +satisfying +sativa +sato +sats +satterthwaite +sattler +saturate +saturated +saturating +saturation +saturday +saturdays +saturn +saturnalia +satya +sau +sauce +sauced +saucepan +saucer +saucers +sauces +saucier +saucy +saud +saudi +saudia +saudis +sauer +sauerkraut +saul +sauls +sault +saumur +sauna +saunas +saunders +saunter +saur +sausage +sausages +sausalito +saute +sauteed +sauter +sauternes +sauve +sauvignon +sav +sava +savage +savaged +savagely +savagery +savages +savaging +savanna +savannah +savant +savants +save +saved +saver +savers +saves +saville +savin +saving +savings +savior +saviors +saviour +savoie +savor +savoring +savors +savory +savoy +savvy +saw +sawdust +sawed +sawing +sawmill +sawmills +sawn +saws +sawtooth +sawyer +sawyers +sax +saxby +saxe +saxena +saxon +saxons +saxony +saxophone +saxophones +saxophonist +saxton +say +saybrook +saye +sayed +sayegh +sayer +sayers +saying +sayings +sayles +sayonara +sayre +says +sayyid +sc +scab +scabs +scads +scaffold +scaffolding +scaffolds +scaife +scala +scalable +scalar +scald +scalded +scalding +scalds +scale +scaled +scales +scalia +scaling +scallions +scallop +scalloped +scallops +scalp +scalped +scalpel +scalpels +scalper +scalpers +scalping +scalps +scaly +scam +scamp +scamper +scampered +scampering +scams +scan +scandal +scandalize +scandalized +scandalous +scandals +scandia +scandinavia +scandinavian +scandinavians +scanjet +scanlan +scanlon +scanned +scannell +scanner +scanners +scanning +scans +scant +scantily +scanty +scape +scapegoat +scapegoating +scapegoats +scar +scarab +scarborough +scarce +scarcely +scarcer +scarcity +scare +scarecrow +scared +scares +scarey +scarf +scarface +scarfed +scarfs +scarier +scariest +scaring +scarlet +scarlett +scarp +scarpa +scarred +scarring +scars +scarves +scary +scat +scathing +scatological +scatter +scattered +scattering +scattershot +scavenge +scavenger +scavengers +scavenging +scenario +scenarios +scene +scenery +scenes +scenic +scent +scented +scenting +scents +sceptical +scepticism +sceptre +sch +schacht +schachter +schadenfreude +schaefer +schaeffer +schafer +schaffer +schalk +schall +schank +schantz +schapiro +scharf +schatz +schauer +schaumburg +schechter +schedule +scheduled +scheduler +schedulers +schedules +scheduling +scheer +scheffer +scheidt +schein +schell +schelling +schema +schematic +schematics +scheme +schemed +schemer +schemers +schemes +scheming +schenectady +schenk +scherer +schering +scherrer +scheuer +schiavone +schick +schiff +schiffer +schiller +schilling +schillings +schindler +schism +schismatic +schisms +schizophrenia +schizophrenic +schlafly +schlatter +schlegel +schleicher +schlemmer +schlepping +schlesinger +schlitz +schlock +schloss +schlosser +schlumberger +schluter +schmaltz +schmaltzy +schmelzer +schmid +schmidt +schmitt +schmitz +schmooze +schmoozing +schmuck +schnabel +schnapps +schnauzer +schneck +schneider +schnell +schnitzel +schoen +schoenberg +schoenfeld +scholar +scholarly +scholars +scholarship +scholarships +scholastic +scholes +scholl +schon +schonfeld +school +schoolbooks +schoolboy +schoolboys +schoolchild +schoolchildren +schooled +schooler +schoolers +schoolgirl +schoolgirls +schoolhouse +schooling +schoolmaster +schoolmasters +schoolmate +schoolmates +schoolroom +schools +schoolteacher +schoolteachers +schoolwork +schoolyard +schooner +schooners +schott +schrader +schrag +schrager +schram +schrank +schreiber +schreiner +schrock +schroder +schroeder +schtick +schubert +schuessler +schuh +schuler +schuller +schulman +schultz +schultze +schulz +schulze +schumacher +schuman +schumer +schundler +schuster +schutz +schuyler +schwab +schwabe +schwan +schwartz +schwartzman +schwarz +schwarzenegger +schwarzkopf +schweiger +schweitzer +schweizer +schweppes +schwimmer +sci +sciatica +science +sciences +scientific +scientifically +scientifics +scientist +scientists +scientology +scimitar +scintilla +scintillation +scion +scirocco +scissor +scissors +scitex +scleroderma +sclerosis +scoff +scoffed +scoffing +scofflaws +scoffs +scofield +scoggins +scold +scolded +scolding +scolds +scoliosis +sconces +scone +scones +scoop +scooped +scooper +scooping +scoops +scoot +scooter +scooters +scoots +scope +scoped +scopes +scoping +scopolamine +scorch +scorched +scorcher +scorching +score +scoreboard +scorecard +scorecards +scored +scorekeeper +scoreless +scorer +scorers +scores +scoring +scorn +scorned +scornful +scorns +scorpio +scorpion +scorpions +scot +scotch +scotched +scotches +scotia +scotland +scots +scotsman +scott +scottie +scottish +scotts +scottsdale +scotty +scoundrel +scoundrels +scour +scoured +scourge +scourged +scourges +scourging +scouring +scours +scout +scouted +scouting +scoutmaster +scouts +scow +scowcroft +scowl +scowled +scowling +scowls +scr +scrabble +scraggly +scramble +scrambled +scrambler +scrambles +scrambling +scranton +scrap +scrapbook +scrapbooks +scrape +scraped +scraper +scrapers +scrapes +scrapheap +scraping +scrapings +scrapped +scrapping +scrappy +scraps +scratch +scratched +scratches +scratching +scratchy +scrawl +scrawled +scrawny +scream +screamed +screamer +screamers +screaming +screamingly +screams +screech +screeched +screeches +screeching +screed +screeds +screen +screened +screener +screeners +screening +screenings +screenplay +screenplays +screens +screenwriter +screw +screwball +screwdriver +screwdrivers +screwed +screwing +screws +screwy +scribble +scribbled +scribbler +scribblers +scribbles +scribbling +scribe +scribed +scribes +scribner +scrimmage +scrimp +scrimshaw +scrip +scripps +script +scripted +scripting +scripts +scriptural +scripture +scriptures +scriptwriter +scriptwriting +scriven +scrivener +scroll +scrolled +scrolling +scrolls +scrooge +scrotum +scrounge +scrounged +scrounging +scrub +scrubbed +scrubber +scrubbers +scrubbing +scrubby +scrubs +scruffy +scrumptious +scrunch +scrunched +scruple +scruples +scrupulous +scrupulously +scrutinize +scrutinized +scrutinizes +scrutinizing +scrutiny +scruton +scsi +sct +scuba +scud +scudder +scuds +scuff +scuffed +scuffle +scuffled +scuffles +scull +sculley +scully +sculpt +sculpted +sculpting +sculptor +sculptors +sculptural +sculpture +sculptured +sculptures +scum +scumbag +scupper +scuppered +scurried +scurrilous +scurry +scurrying +scuttle +scuttlebutt +scuttled +scuttles +scuttling +scythian +se +sea +seabed +seaboard +seaborne +seabrook +seacoast +seafarer +seafarers +seafloor +seafood +seafoods +seafront +seagal +seagate +seagoing +seagram +seagull +seagulls +seahawk +seahawks +seal +sealant +sealants +seale +sealed +sealer +sealey +sealing +seals +sealy +seam +seaman +seamed +seamen +seamless +seamlessly +seams +seamstress +seamstresses +seamus +seamy +sean +seance +seaplane +seaport +seaports +sear +search +searched +searcher +searchers +searches +searching +searchlight +searchlights +seared +searing +searle +sears +seas +seascape +seascapes +seashell +seashells +seashore +seasick +seaside +season +seasonable +seasonably +seasonal +seasonality +seasonally +seasoned +seasoning +seasonings +seasons +seat +seatbelt +seatbelts +seated +seater +seating +seatings +seaton +seats +seattle +seaver +seawall +seaward +seawater +seaway +seaweed +seaweeds +seawolf +seaworthy +sebaceous +sebastian +sebring +sec +seca +secede +seceded +seceding +secession +secessionist +secessionists +secluded +seclusion +seco +second +secondaries +secondarily +secondary +seconded +secondhand +secondly +seconds +secor +secrecy +secrest +secret +secretarial +secretariat +secretaries +secretary +secrete +secreted +secretion +secretions +secretive +secretiveness +secretly +secretory +secrets +secs +sect +sectarian +sectarianism +section +sectional +sectioned +sectioning +sections +sector +sectoral +sectors +sects +secular +secularism +secularist +secularists +secularization +secularized +secunda +secure +secured +securely +secures +securing +securities +security +sed +sedan +sedans +sedate +sedated +sedating +sedation +sedative +sedentary +seder +sedge +sedgewick +sedgwick +sediment +sedimentary +sedimentation +sediments +sedition +seditious +sedona +seduce +seduced +seducer +seducing +seduction +seductions +seductive +seductively +see +seed +seeded +seeding +seedling +seedlings +seeds +seedy +seeger +seeing +seek +seeker +seekers +seeking +seeks +seel +seeley +seelig +seely +seelye +seem +seema +seemed +seeming +seemingly +seemly +seems +seen +seep +seepage +seeped +seeping +seeps +seer +seers +sees +seesaw +seesawed +seethe +seethed +seething +sefton +sega +segal +seger +segment +segmentation +segmented +segmenting +segments +segovia +segregate +segregated +segregates +segregating +segregation +segregationist +segregationists +segue +seguin +segundo +segura +sei +seibert +seidel +seidman +seif +seige +seigel +seigniorage +seiji +seiko +seiler +seim +sein +seine +seinfeld +seis +seismic +seismological +seismologists +seismology +seitz +seize +seized +seizes +seizing +seizure +seizures +sel +sela +selby +selden +seldom +sele +select +selectable +selected +selecting +selection +selections +selective +selectively +selectivity +selector +selectors +selects +selena +selene +selenium +self +selfish +selfishly +selfishness +selfless +selflessly +selflessness +selig +seligman +selim +selkirk +sell +sella +sellars +selle +seller +sellers +selling +sellout +sellouts +sells +selma +selmer +seltzer +selva +selvage +selves +selwyn +sem +semantic +semantics +semaphore +semblance +semel +semen +semester +semesters +semi +semiannual +semiannually +semiautomatic +semicircular +semicolon +semicolons +semiconducting +semiconductor +semiconductors +semifinal +semifinalist +semifinals +seminal +seminar +seminarian +seminarians +seminaries +seminario +seminars +seminary +seminole +seminoles +semiotic +semiotics +semipermanent +semiprecious +semis +semite +semites +semitic +semitism +semper +semple +semtex +sen +sena +senate +senator +senatorial +senators +sence +send +sender +senders +sending +sends +seneca +senegal +senescence +seng +senile +senility +senior +seniority +seniors +senna +sennett +senor +senora +senorita +sens +sensation +sensational +sensationalism +sensationalist +sensationalize +sensationalized +sensationalizing +sensations +sense +sensed +senseless +sensenbrenner +senses +sensibilities +sensibility +sensible +sensibly +sensing +sensitive +sensitively +sensitivities +sensitivity +sensitize +sensitized +sensitizing +sensor +sensors +sensory +sensual +sensuality +sensuous +sent +sentance +sentence +sentenced +sentences +sentencing +sentient +sentiment +sentimental +sentimentality +sentimentally +sentiments +sentinel +sentinels +sentra +sentries +sentry +seoul +sep +separate +separated +separately +separateness +separates +separating +separation +separations +separatism +separatist +separatists +separator +separators +seperate +seperated +seperately +sephardic +sepia +sepp +sepsis +sept +septa +septal +september +septic +septuagenarian +septum +sepulchre +sepulveda +sequel +sequelae +sequels +sequence +sequenced +sequencer +sequences +sequencing +sequent +sequential +sequentially +sequester +sequestered +sequestering +sequestration +sequin +sequined +sequins +sequitur +sequiturs +sequoia +sequoias +ser +sera +serafin +seraphim +serb +serbia +serbian +serbians +serbs +sere +serena +serenade +serenaded +serenades +serenading +serendipitous +serendipitously +serendipity +serene +serenely +serengeti +serenity +serf +serfs +serge +sergeant +sergeants +sergei +sergey +sergio +seri +serial +serialization +serialize +serialized +serials +serie +series +serif +serio +serious +seriously +seriousness +sermon +sermonizing +sermons +serological +serology +serono +serota +serotonin +serous +serpa +serpent +serpentine +serpents +serpico +serra +serrano +serrated +serum +serv +servant +servants +serve +served +server +servers +serves +service +serviceable +serviced +serviceman +servicemen +servicer +services +servicing +servile +serving +servings +servitude +servo +sesame +sess +session +sessions +set +setback +setbacks +seth +seti +seton +sets +settee +setter +setters +setting +settings +settle +settled +settlement +settlements +settler +settlers +settles +settling +setup +setups +setzer +seurat +seuss +sevan +seve +seven +sevenfold +sevens +seventeen +seventeenth +seventh +seventies +seventy +sever +several +severance +severe +severed +severely +severest +severing +severino +severity +severn +severs +sevier +sevigny +seville +sevres +sew +sewage +seward +sewed +sewell +sewer +sewerage +sewers +sewing +sewn +sex +sexed +sexes +sexier +sexiest +sexily +sexism +sexist +sexless +sexology +sextant +sextet +sexton +sexual +sexuality +sexually +sexy +seybold +seymour +sf +sforza +sg +sh +sha +shaanxi +shabby +shack +shackle +shackled +shackles +shackleton +shacks +shad +shade +shaded +shades +shading +shadings +shadow +shadowed +shadowing +shadows +shadowy +shady +shaeffer +shafer +shaffer +shaft +shafted +shafts +shag +shagged +shagging +shaggy +shah +shaheen +shahi +shaikh +shaka +shake +shakedown +shaken +shakeout +shaker +shakers +shakes +shakespeare +shakespearean +shakeup +shakier +shakiness +shaking +shakti +shakur +shaky +shalala +shale +shales +shall +shallot +shallots +shallow +shallower +shallowness +shalom +shalt +sham +shama +shaman +shamans +shambles +shame +shamed +shameful +shamefully +shameless +shamelessly +shamelessness +shames +shaming +shamir +shampoo +shampooed +shampoos +shamrock +shams +shamu +shamus +shan +shana +shanahan +shand +shandling +shandong +shandy +shane +shanghai +shangri +shank +shanked +shanker +shanks +shanley +shanna +shannon +shant +shanti +shanties +shanty +shantytown +shao +shape +shaped +shapeless +shapely +shaper +shapers +shapes +shaping +shapira +shapiro +shar +sharan +sharansky +shard +shards +share +sharecropper +sharecroppers +shared +shareef +shareholder +shareholders +shareholding +shareowner +sharer +sharers +shares +shareware +shari +sharia +sharif +sharing +sharjah +shark +sharkey +sharks +sharma +sharman +sharon +sharp +sharpe +sharpen +sharpened +sharpener +sharpeners +sharpening +sharpens +sharper +sharpest +sharpie +sharples +sharply +sharpness +sharps +sharpshooter +sharpshooters +sharpton +sharron +shasta +shastri +shatner +shatter +shattered +shattering +shatters +shattuck +shatz +shaughnessy +shaul +shaun +shauna +shave +shaved +shaven +shaver +shavers +shaves +shaving +shavings +shaw +shawl +shawls +shawn +shawnee +shay +shayne +shays +she +shea +sheaf +sheaffer +sheahan +shean +shear +sheared +shearer +shearing +shearling +shearman +shears +shearson +shearwater +sheath +sheathed +sheathing +sheaths +sheaves +sheba +shebang +shed +shedd +shedding +sheds +sheedy +sheehan +sheehy +sheen +sheena +sheep +sheepish +sheepishly +sheepskin +sheer +sheers +sheet +sheeting +sheets +sheffield +sheik +sheikh +sheikhs +sheiks +sheila +shek +shekel +shekels +shelburne +shelby +sheldon +shelf +shell +shelled +sheller +shelley +shellfish +shelling +shells +shelly +shelter +sheltered +sheltering +shelters +shelton +shelve +shelved +shelves +shelving +shen +shenandoah +shenanigan +shenanigans +sheng +shenk +shenzhen +shep +shepard +shephard +shepheard +shepherd +shepherded +shepherding +shepherds +sheppard +shepperd +sher +sheraton +sherbert +sherbet +shere +sheri +sheridan +sheriff +sheriffs +sherlock +sherman +sherpa +sherri +sherrie +sherries +sherriff +sherrill +sherrod +sherron +sherry +sherwin +sherwood +sheryl +sheth +shetland +shetlands +shevardnadze +shew +shh +shi +shia +shiatsu +shibboleth +shibboleths +shick +shied +shield +shielded +shielding +shields +shier +shies +shiff +shift +shifted +shifter +shifters +shifting +shiftless +shifts +shifty +shigeki +shigella +shigeru +shih +shiite +shill +shiller +shilling +shillings +shills +shim +shimbun +shimizu +shimmer +shimmered +shimmering +shimmers +shimmy +shimon +shin +shindig +shindler +shine +shined +shiner +shiners +shines +shing +shingle +shingler +shingles +shiniest +shining +shinjuku +shinn +shins +shinto +shintoism +shiny +ship +shipboard +shipbuilder +shipbuilders +shipbuilding +shipley +shipload +shipman +shipmate +shipmates +shipment +shipments +shipowner +shipowners +shipp +shipped +shipper +shippers +shipping +ships +shipwreck +shipwrights +shipyard +shipyards +shira +shiraz +shire +shires +shirey +shirk +shirked +shirking +shirley +shirt +shirtless +shirts +shish +shit +shite +shiv +shiva +shive +shiver +shivered +shivering +shivers +shlomo +shmuel +sho +shoal +shoals +shock +shocked +shocker +shockers +shocking +shockingly +shockley +shocks +shockwave +shockwaves +shod +shoddily +shoddy +shoe +shoebox +shoehorn +shoehorned +shoeing +shoelace +shoelaces +shoemaker +shoemakers +shoes +shoeshine +shoestring +shogun +shogunate +shoguns +shoji +shoko +shold +shomron +shon +shone +shoo +shooed +shook +shoot +shooter +shooters +shooting +shootings +shootout +shootouts +shoots +shop +shope +shopkeeper +shopkeepers +shoplift +shoplifter +shoplifters +shoplifting +shoppe +shopped +shopper +shoppers +shopping +shops +shopworn +shor +shore +shored +shoreham +shoreline +shorelines +shores +shoring +shorn +short +shortage +shortages +shortbread +shortcake +shortchange +shortchanged +shortchanging +shortcoming +shortcomings +shortcut +shortcuts +shorted +shorten +shortened +shortening +shortens +shorter +shortest +shortfall +shortfalls +shorthand +shorthanded +shortie +shorties +shorting +shortly +shortness +shortridge +shorts +shortsighted +shortsightedness +shortstop +shortwave +shorty +shoshone +shot +shotgun +shotguns +shots +shou +shoud +should +shoulda +shoulder +shouldered +shouldering +shoulders +shoup +shouse +shout +shouted +shouting +shouts +shoval +shove +shoved +shovel +shoveled +shovels +shoves +shoving +show +showalter +showbiz +showboat +showboating +showcase +showcased +showcases +showcasing +showdown +showdowns +showed +shower +showered +showerhead +showering +showers +showgirl +showgirls +showing +showings +showman +showmanship +shown +showoff +showpiece +showplace +showroom +showrooms +shows +showstopper +showtime +showy +shr +shrank +shrapnel +shred +shredded +shredder +shredders +shredding +shreds +shree +shreve +shreveport +shrewd +shrewdest +shrewdly +shrewdness +shrewsbury +shri +shriek +shrieked +shrieking +shrieks +shrift +shrike +shrill +shrilly +shrimp +shrimpers +shrimps +shrine +shrines +shrink +shrinkage +shrinking +shrinks +shrivel +shriveling +shriver +shropshire +shroud +shrouded +shrouding +shrouds +shrub +shrubbery +shrubs +shrug +shrugged +shrugging +shrugs +shrunk +shrunken +shtick +shu +shuck +shucked +shucking +shucks +shudder +shuddered +shuddering +shudders +shue +shuffle +shuffleboard +shuffled +shuffles +shuffling +shugart +shui +shul +shulman +shultz +shum +shun +shunned +shunning +shuns +shunt +shunted +shunting +shunts +shure +shush +shuster +shut +shutdown +shutdowns +shute +shutoff +shutout +shutouts +shuts +shutt +shutter +shuttered +shuttering +shutters +shutting +shuttle +shuttled +shuttles +shuttling +shy +shying +shylock +shyly +shyness +shyster +shysters +si +siam +siamese +sib +siberia +siberian +siberians +sibley +sibling +siblings +sibyl +sic +sica +sich +sichel +sichuan +sicilian +siciliano +sicily +sick +sicken +sickened +sickening +sickens +sicker +sickest +sicking +sickle +sickles +sickly +sickness +sicknesses +sid +sida +siddhartha +side +sidearm +sidearms +sidebar +sideburns +sidecar +sided +sidekick +sidekicks +sidelight +sideline +sidelined +sidelines +sideman +sider +siders +sides +sideshow +sideshows +sidestep +sidestepped +sidestepping +sidesteps +sideswipe +sidetrack +sidetracked +sidewalk +sidewalks +sidewall +sideways +sidewinder +sidhu +sidi +siding +sidle +sidler +sidling +sidney +sidon +sie +siecle +sieg +siegal +siege +siegel +sieges +siegfried +siemens +siena +sienna +sierra +sierras +siesta +sieve +sieves +siew +siewert +sift +sifted +sifting +sifts +sig +sigh +sighed +sighing +sighs +sight +sighted +sighting +sightings +sights +sightsee +sightseeing +sightseers +sigler +sigma +sigman +sigmund +sign +signa +signage +signal +signaled +signaling +signalled +signalling +signals +signatories +signatory +signature +signatures +signed +signee +signer +signers +signet +significance +significant +significantly +signified +signifies +signify +signifying +signing +signings +signoff +signor +signori +signpost +signposts +signs +sigourney +sigrid +sigur +sigurd +sikes +sikh +sikhs +sikorsky +sil +silas +silber +silberman +silence +silenced +silencer +silencers +silences +silencing +silent +silently +silesia +silex +silhouette +silhouetted +silhouettes +silica +silicate +silicon +silicone +silicones +silk +silks +silkwood +silkworm +silkworms +silky +sill +sillier +silliest +silliman +silliness +sills +silly +silo +silos +silt +silva +silveira +silver +silvera +silverado +silverberg +silverman +silvers +silversmith +silverstein +silverstone +silverthorne +silverton +silverware +silvery +silvester +silvestri +silvia +silvio +sim +sima +simba +sime +simeon +simi +simian +similar +similarities +similarity +similarly +simile +similiar +simkin +simkins +simm +simmer +simmered +simmering +simmers +simmon +simmonds +simmons +simms +simon +simone +simons +simonson +simonton +simpatico +simper +simpering +simple +simpleminded +simpler +simplest +simpleton +simplex +simplicity +simplification +simplifications +simplified +simplifies +simplify +simplifying +simplistic +simplistically +simply +simpson +simpsons +sims +simson +simulate +simulated +simulates +simulating +simulation +simulations +simulator +simulators +simulcast +simulcasts +simultaneous +simultaneously +sin +sina +sinai +sinan +sinatra +sinbad +since +sincere +sincerely +sincerest +sincerity +sinclair +sind +sine +sinead +sinecure +sinewy +sinfonia +sinful +sinfully +sinfulness +sing +singapore +singaporean +singe +singed +singer +singers +singh +singing +single +singled +singlehandedly +singles +singlet +singleton +singling +singly +sings +singsong +singular +singularity +singularly +sinha +sinhalese +sinister +sink +sinker +sinkers +sinkhole +sinking +sinks +sinn +sinned +sinner +sinners +sinning +sino +sins +sinton +sinuous +sinus +sinuses +sinusitis +siobhan +sion +sioux +sip +sipe +siphon +siphoned +siphoning +siphons +sipped +sipping +sipple +sips +sir +sire +siren +sirens +sires +sirhan +siri +sirius +sirloin +sirocco +sirs +sis +sisal +siskin +sissy +sister +sisterhood +sisters +sistine +sisyphus +sit +sitar +sitcom +sitcoms +site +sited +sites +siting +sitka +sits +sitter +sitters +sitting +sittings +situ +situate +situated +situation +situational +situations +situs +six +sixers +sixes +sixfold +sixteen +sixteenth +sixth +sixths +sixties +sixtieth +sixty +sizable +size +sizeable +sized +sizer +sizes +sizing +sizzle +sizzled +sizzler +sizzles +sizzling +sjogren +skadden +skaff +skaggs +skandia +skanska +skate +skateboard +skateboarding +skateboards +skated +skater +skaters +skates +skating +skeen +skeet +skeletal +skeleton +skeletons +skelly +skelter +skelton +skene +skeptic +skeptical +skepticism +skeptics +sketch +sketchbook +sketched +sketches +sketchily +sketching +sketchy +skew +skewed +skewer +skewered +skewering +skewers +skewing +skews +ski +skid +skidded +skidding +skidmore +skidoo +skids +skied +skier +skiers +skies +skiffs +skiing +skill +skilled +skillet +skillets +skillful +skillfully +skilling +skills +skim +skimmed +skimmer +skimmers +skimming +skimp +skimped +skimping +skimpy +skims +skin +skinhead +skinheads +skinless +skinned +skinner +skinnier +skinning +skinny +skins +skintight +skip +skipjack +skipped +skipper +skippers +skipping +skippy +skips +skirmish +skirmishes +skirmishing +skirt +skirted +skirting +skirts +skis +skit +skits +skitter +skittish +skittles +sklar +skoal +skoda +skolnick +skulk +skull +skullduggery +skulls +skunk +skunks +sky +skydive +skydivers +skydiving +skydome +skyhawk +skylab +skylark +skylight +skylights +skyline +skylines +skyrocket +skyrocketed +skyrocketing +skyrockets +skyscraper +skyscrapers +skyward +skyway +skyways +skywriting +sl +slab +slabs +slack +slacked +slacken +slackened +slackening +slackens +slacker +slackers +slacks +slade +slag +slain +slalom +slam +slammed +slammer +slamming +slams +slander +slandered +slandering +slanderous +slanders +slane +slang +slant +slanted +slanting +slants +slap +slapdash +slapped +slapper +slapping +slaps +slapstick +slash +slashed +slasher +slashes +slashing +slat +slate +slated +slater +slates +slather +slatkin +slats +slattery +slaughter +slaughtered +slaughterhouse +slaughterhouses +slaughtering +slaughters +slav +slave +slavery +slaves +slavic +slavin +slaving +slavish +slavishly +slavonic +slavs +slay +slayer +slayers +slaying +slayings +slays +sleaze +sleaziest +sleazy +sled +sledding +sledge +sledgehammer +sleds +sleek +sleekly +sleep +sleeper +sleepers +sleepily +sleepiness +sleeping +sleepless +sleeps +sleepwalk +sleepwalker +sleepwalkers +sleepwalking +sleepwear +sleepy +sleet +sleeve +sleeved +sleeveless +sleeves +sleigh +sleighs +sleight +sleights +slender +slept +sleuth +sleuthing +sleuths +slew +slice +sliced +slicer +slices +slicing +slick +slicked +slicker +slickers +slickest +slickly +slicks +slid +slide +slider +sliders +slides +slideshow +sliding +slight +slighted +slightest +slighting +slightly +slights +slighty +slim +slime +slimmed +slimmer +slimmest +slimming +slims +slimy +sling +slinger +slingers +slinging +slings +slingshot +slingshots +slink +slinking +slinky +slip +slippage +slippages +slipped +slipper +slipperiness +slippers +slippery +slipping +slips +slipshod +slit +slither +slithering +slits +slitting +sliver +slivers +sloan +sloane +sloat +slob +slobber +slobbering +slobodan +slobs +slocum +sloe +slog +slogan +slogans +slogged +slogging +sloman +slone +sloop +slop +slope +sloped +sloper +slopes +sloping +sloppier +sloppily +sloppiness +sloppy +slosh +sloshing +slot +sloth +slotnick +slots +slotted +slotting +slouch +slouched +slouches +slouching +slough +sloughed +sloughs +slovak +slovakia +slovene +slovenia +slovenian +slovenliness +slovenly +slow +slowdown +slowdowns +slowed +slower +slowest +slowing +slowly +slowness +slows +sludge +sludges +slug +slugfest +slugged +slugger +sluggers +slugging +sluggish +sluggishly +sluggishness +slugs +sluice +sluicing +slum +slumber +slumbering +slumming +slump +slumped +slumping +slumps +slums +slung +slunk +slur +slurp +slurping +slurred +slurring +slurry +slurs +slush +slut +sluts +slutsky +slutty +sly +slyly +slyness +sm +sma +smack +smacked +smacking +smacks +small +smaller +smallest +smalley +smalling +smallish +smallness +smallpox +smalls +smalltalk +smarmy +smart +smarten +smarter +smartest +smarting +smartly +smarts +smash +smashed +smashes +smashing +smattering +smeal +smear +smeared +smearing +smears +smedley +smee +smell +smelled +smelling +smells +smelly +smelt +smelter +smelters +smelting +smidge +smidgen +smidgeon +smile +smiled +smiles +smiley +smiling +smirk +smirking +smirks +smirnoff +smirnov +smit +smith +smithereens +smithers +smithfield +smithkline +smiths +smithson +smithsonian +smits +smitten +smoak +smock +smocks +smog +smoggy +smoke +smoked +smokehouse +smokeless +smoker +smokers +smokes +smokescreen +smokestack +smokestacks +smokey +smokies +smoking +smoky +smolder +smoldering +smolinski +smooch +smoot +smooth +smoothed +smoother +smoothest +smoothie +smoothies +smoothing +smoothly +smoothness +smooths +smorgasbord +smote +smother +smothered +smothering +smothers +smudge +smudged +smudges +smug +smuggle +smuggled +smuggler +smugglers +smuggling +smugly +smugness +smurf +smurfs +smut +smyrna +smyth +smythe +sn +snack +snacking +snacks +snafu +snafus +snag +snagged +snagging +snags +snail +snails +snake +snakebite +snakelike +snakes +snakeskin +snaking +snap +snapdragons +snape +snapped +snapper +snappier +snapping +snappy +snaps +snapshot +snapshots +snare +snared +snares +snaring +snarl +snarled +snarling +snarls +snatch +snatched +snatcher +snatchers +snatches +snatching +snazzy +snead +sneak +sneaked +sneaker +sneakers +sneaking +sneaks +sneaky +sneer +sneered +sneering +sneeringly +sneers +sneeze +sneezed +sneezes +sneezing +sneezy +snell +snelson +snicker +snickered +snickering +snickers +snide +snider +sniff +sniffed +sniffer +sniffing +sniffles +sniffs +snip +snipe +sniped +sniper +snipers +snipes +sniping +snipped +snippet +snippets +snipping +snips +snit +snitch +snitched +snitching +snob +snobbery +snobbish +snobby +snobs +snoddy +snodgrass +snook +snooker +snookered +snoop +snooping +snoops +snoopy +snoot +snooty +snooze +snoozing +snore +snores +snoring +snorkel +snorkeling +snorkels +snort +snorted +snorting +snorts +snot +snout +snow +snowball +snowballed +snowballing +snowballs +snowbird +snowbirds +snowboarder +snowboarders +snowbound +snowcapped +snowden +snowdon +snowdrift +snowe +snowed +snowfall +snowfalls +snowflake +snowflakes +snowing +snowman +snowmen +snowmobile +snowmobiles +snowpack +snows +snowshoe +snowstorm +snowstorms +snowy +snub +snubbed +snubbing +snubs +snuck +snuff +snuffed +snuffing +snuffs +snug +snuggle +snuggled +snuggling +snugly +snyder +so +soak +soaked +soaking +soaks +soap +soapbox +soaps +soapy +soar +soared +soares +soaring +soars +soave +sob +sobbed +sobbing +sobek +sobel +sober +sobered +sobering +soberly +sobieski +sobre +sobriety +sobriquet +sobs +soc +soccer +sociable +social +socialism +socialist +socialistic +socialists +socialite +socialites +socialization +socialize +socialized +socializing +socially +socials +societal +societe +societies +society +socio +socioeconomic +sociological +sociologist +sociologists +sociology +sociopath +sociopathic +sociopaths +sock +socked +socket +sockets +sockeye +socking +socks +socorro +socrates +socratic +sod +soda +sodas +sodden +sodding +soderstrom +sodium +sodom +sodomized +sodomy +soe +sofa +sofas +sofia +sofie +soft +softball +softballs +soften +softened +softener +softening +softens +softer +softest +softie +softly +softness +software +softwares +softwood +softy +sogang +soggy +sogo +soh +sohn +soho +soil +soiled +soiling +soils +soir +soiree +sojourn +sojourned +sojourner +sojourns +sok +sokol +sol +sola +solace +solan +solana +solano +solar +solari +solaris +solarium +solberg +sold +solder +soldered +soldering +soldier +soldiered +soldiering +soldiers +soldiery +sole +soled +soledad +soleil +solely +solemn +solemnity +solemnly +solenoid +soler +soles +soley +soli +solicit +solicitation +solicitations +solicited +soliciting +solicitor +solicitors +solicitous +solicits +solicitude +solid +solidarity +soliders +solidification +solidified +solidifies +solidify +solidifying +solidity +solidly +solids +soliloquies +soliloquy +soliman +solipsism +solis +solitaire +solitary +solitude +soll +solly +solo +soloist +soloists +soloman +solomon +solon +solos +solow +solstice +solstices +solubility +soluble +solute +solution +solutions +solvable +solvay +solve +solved +solvency +solvent +solvents +solver +solvers +solves +solving +solzhenitsyn +som +soma +somali +somalia +somaliland +somalis +somatic +somber +somberly +sombrero +some +somebody +someday +somedays +somehow +someone +someones +someplace +somers +somersault +somersaulting +somersaults +somerset +somerville +somethin +something +somethings +sometime +sometimes +someting +somewhat +somewhere +somma +sommelier +sommer +sommers +somnolence +somnolent +somone +somthing +son +sonar +sonata +sonatas +sondheim +sondra +sone +song +songbird +songbirds +songbook +songs +songwriter +songwriters +songwriting +sonia +sonic +sonics +sonja +sonnenberg +sonnenfeld +sonnenschein +sonnet +sonnets +sonnett +sonny +sonogram +sonoma +sonora +sonorous +sons +sontag +sony +soo +soon +sooner +sooners +soonest +soong +sooo +soooo +soot +sooth +soothe +soothed +soother +soothes +soothing +soothingly +soothsayer +soothsayers +sooty +sop +sope +soper +sophia +sophie +sophisticate +sophisticated +sophisticates +sophistication +sophistry +sophocles +sophomore +sophomores +sopping +soppy +soprano +sopranos +sops +sor +sorbet +sorbonne +sorcerer +sorcerers +sorceress +sorcery +sordid +sore +sorel +sorely +soren +soreness +sorensen +sorenson +sores +sorghum +sori +soria +soriano +sororities +sorority +soros +sorrel +sorrels +sorrento +sorrow +sorrowful +sorrows +sorry +sort +sorta +sorted +sorter +sorters +sortie +sorties +sorting +sorts +sos +sosa +soss +soto +sotomayor +sou +souffle +sought +souk +soul +soule +soulful +soulless +souls +sound +soundbite +sounded +sounder +sounders +sounding +soundings +soundly +soundness +sounds +soundtrack +soundtracks +soup +souped +soups +soupy +sour +source +sourcebook +sourced +sources +sourcing +soured +souring +sours +sous +sousa +souter +south +southampton +southard +southbound +southeast +southeasterly +southeastern +souther +southerly +southern +southerner +southerners +southernmost +southland +southpaw +southside +southward +southwest +southwesterly +southwestern +southwood +southworth +souvenir +souvenirs +souvlaki +souza +sovereign +sovereigns +sovereignties +sovereignty +soviet +soviets +sow +sowed +sowers +sowing +sown +sows +sox +soy +soya +soybean +soybeans +soyuz +sp +spa +spac +space +spacecraft +spaced +spaceflight +spacek +spaceman +spacer +spacers +spaces +spaceship +spaceships +spacesuit +spacesuits +spacewalk +spacewalks +spacey +spacing +spacious +spaciousness +spade +spader +spades +spadework +spaghetti +spagnoli +spago +spahn +spain +spake +spalding +spall +spam +span +spandex +spang +spangled +spaniard +spaniards +spaniel +spaniels +spanish +spank +spanked +spanking +spanky +spann +spanned +spanner +spanning +spano +spans +spar +sparc +spare +spared +spares +sparing +sparingly +spark +sparked +sparking +sparkle +sparkled +sparkler +sparklers +sparkles +sparkling +sparkly +sparkman +sparks +sparky +sparling +sparred +sparring +sparrow +sparrows +spars +sparse +sparsely +sparta +spartan +spartans +spas +spasm +spasmodic +spasms +spastic +spat +spate +spates +spath +spatial +spats +spatter +spattered +spattering +spatula +spatz +spawn +spawned +spawning +spawns +spay +spaying +spca +speach +speak +speakeasy +speaker +speakerphone +speakers +speakership +speaking +speaks +spear +spearhead +spearheaded +spearheading +spearheads +spearing +spearman +spearmint +spears +spec +specht +special +specialised +specialist +specialists +specialities +speciality +specialization +specialize +specialized +specializes +specializing +specially +specials +specialties +specialty +specie +species +specific +specifically +specification +specifications +specificially +specificity +specifics +specified +specifies +specify +specifying +specimen +specimens +specious +speck +speckle +speckled +specks +specs +spect +spectacle +spectacles +spectacular +spectacularly +spectator +spectators +specter +spector +spectra +spectral +spectre +spectrograph +spectrometer +spectrometers +spectrometry +spectroscopic +spectroscopy +spectrum +spectrums +speculate +speculated +speculates +speculating +speculation +speculations +speculative +speculator +speculators +speculum +sped +speech +speeches +speechless +speechwriter +speechwriters +speed +speedboat +speedboats +speeded +speeder +speeders +speedier +speedily +speeding +speedo +speedometer +speeds +speedster +speedup +speedway +speedy +speer +speight +spell +spellbinding +spellbound +spelled +speller +spellers +spelling +spellings +spellman +spells +spelman +spelt +spence +spencer +spend +spendable +spender +spenders +spending +spends +spendthrift +spengler +spenser +spent +speranza +sperling +sperm +sperry +spew +spewed +spewing +spews +speyer +sphere +spheres +spherical +spheroid +sphincter +sphinx +spic +spice +spiced +spicer +spices +spicey +spicing +spicy +spider +spiderman +spiders +spidery +spied +spiegel +spiegelman +spiel +spielberg +spieler +spier +spiers +spies +spiess +spiff +spiffed +spiffing +spiffy +spigot +spigots +spike +spiked +spikes +spiking +spiky +spill +spillage +spilled +spiller +spillers +spilling +spillover +spills +spillway +spillways +spilt +spin +spina +spinach +spinal +spindle +spindler +spindly +spine +spineless +spinelli +spines +spinnaker +spinner +spinners +spinney +spinning +spinoff +spinoffs +spinoza +spins +spinster +spiny +spiral +spiraling +spiralling +spirals +spire +spires +spirit +spirited +spiritedness +spirits +spiritual +spiritualism +spiritualist +spiritualists +spirituality +spiritualized +spiritually +spirituals +spiro +spirochete +spit +spite +spiteful +spitfire +spits +spitting +spittle +spitz +spitzer +spivak +spivey +spl +splash +splashed +splashes +splashing +splashy +splat +splatter +splattered +splattering +splay +spleen +splendid +splendidly +splendor +splendors +splendour +splice +spliced +splices +splicing +spline +splint +splinter +splintered +splintering +splinters +splints +split +splits +splitter +splitters +splitting +splotch +splotches +splurge +splurged +splurging +spock +spoil +spoilage +spoiled +spoiler +spoilers +spoiling +spoils +spoilt +spokane +spoke +spoked +spoken +spokes +spokesman +spokesmen +spokespeople +spokesperson +spokespersons +spokeswoman +spokeswomen +spoleto +spong +sponge +sponged +sponges +spongy +sponsor +sponsored +sponsoring +sponsors +sponsorship +sponsorships +spontaneity +spontaneous +spontaneously +spoof +spoofed +spoofing +spoofs +spook +spooked +spooking +spooks +spooky +spool +spools +spoon +spooner +spoonful +spoonfuls +spooning +spoons +sporadic +sporadically +spore +spores +sport +sported +sportier +sporting +sports +sportscaster +sportscasters +sportsman +sportsmanlike +sportsmanship +sportsmen +sportster +sportswear +sportswriter +sportswriters +sporty +spot +spotless +spotlight +spotlighted +spotlighting +spotlights +spots +spotted +spotter +spotters +spotting +spotty +spousal +spouse +spouses +spout +spouted +spouting +spouts +spp +sprague +sprain +sprained +sprains +sprang +spratt +sprawl +sprawled +sprawling +sprawls +spray +sprayed +sprayer +spraying +sprays +spread +spreader +spreaders +spreading +spreads +spreadsheet +spreadsheets +sprecher +spree +sprees +sprightly +sprigs +spring +springboard +springer +springfield +springing +springs +springsteen +springtime +sprinkle +sprinkled +sprinkler +sprinklers +sprinkles +sprinkling +sprint +sprinted +sprinter +sprinters +sprinting +sprints +sprit +sprite +sprites +spritz +sproat +sprocket +sprockets +sprott +sproul +sprout +sprouted +sprouting +sprouts +spruce +spruced +sprucing +sprung +spry +spss +spt +spud +spuds +spun +spunk +spunky +spur +spurge +spurgeon +spurious +spurling +spurn +spurned +spurning +spurns +spurr +spurred +spurrier +spurring +spurs +spurt +spurted +spurting +spurts +sputnik +sputter +sputtered +sputtering +sputters +sputum +spy +spyglass +spying +sq +squabble +squabbled +squabbles +squabbling +squad +squadron +squadrons +squads +squalid +squall +squalls +squalor +squander +squandered +squandering +square +squared +squarely +squares +squaring +squash +squashed +squashing +squat +squats +squatted +squatter +squatters +squatting +squaw +squawk +squawking +squawks +squeak +squeaked +squeaker +squeakers +squeaking +squeaks +squeaky +squeal +squealed +squealing +squeals +squeamish +squeamishness +squeegee +squeeze +squeezed +squeezes +squeezing +squelch +squelched +squelching +squibb +squibs +squid +squids +squier +squiggle +squiggles +squint +squinting +squire +squires +squirm +squirming +squirms +squirrel +squirreled +squirrels +squirt +squirted +squirting +squirts +squish +squished +squishing +squishy +sr +sram +srebrenica +sri +srinivasan +srivastava +ss +st +sta +staab +staat +staats +stab +stabbed +stabbing +stabbings +stabile +stability +stabilization +stabilize +stabilized +stabilizer +stabilizers +stabilizes +stabilizing +stable +stabler +stables +stabs +staccato +stacey +stack +stacked +stacker +stackhouse +stacking +stacks +stacy +stade +stadia +stadium +stadiums +stadler +stadt +staff +staffed +staffer +staffers +staffing +stafford +staffordshire +staffs +stag +stage +stagecoach +stagecraft +staged +stagehand +stagehands +stages +stagflation +stagger +staggered +staggering +staggers +staging +stagnant +stagnate +stagnated +stagnating +stagnation +stahl +staid +stain +stained +staining +stainless +stains +stair +staircase +staircases +stairs +stairway +stairways +stairwell +stairwells +stake +staked +stakeholder +stakeholders +stakeout +stakes +staking +stale +stalemate +stalemated +staley +stalin +stalingrad +stalinism +stalinist +stalinists +stalk +stalked +stalker +stalkers +stalking +stalks +stall +stalled +staller +stalling +stallings +stallion +stallions +stallone +stalls +stalwart +stalwarts +stam +stamford +stamina +stammer +stamos +stamp +stamped +stampede +stampeded +stampedes +stampeding +stamper +stamping +stamps +stan +stance +stances +stanch +stanchions +stand +standalone +standard +standardization +standardize +standardized +standardizing +standards +standby +standbys +standing +standings +standish +standoff +standoffs +standout +standouts +standpoint +standpoints +stands +standstill +standup +stanfield +stanford +stang +stanger +stanhope +stanislas +stanislav +stanislaw +stank +stanley +stanly +stansfield +stanton +stanza +stanzas +staph +staple +stapled +stapler +staplers +staples +stapleton +stapling +stapp +star +starboard +starbuck +starch +starched +starches +starchy +starck +stardom +stardust +stare +stared +stares +starfighter +starfish +stargazer +stargazers +staring +stark +starker +starkest +starkey +starkly +starkness +starkweather +starlet +starlets +starlight +starling +starlings +starlit +starnes +starr +starred +starring +starry +stars +starship +starstruck +start +started +starter +starters +starting +startle +startled +startles +startling +startlingly +starts +startup +startups +starvation +starve +starved +starves +starving +stash +stashed +stashes +stashing +stasi +stassen +stat +state +statecraft +stated +statehood +statehouse +stateless +stately +statement +statements +staten +stater +stateroom +staterooms +staters +states +stateside +statesman +statesmanlike +statesmanship +statesmen +statewide +static +statically +stating +station +stationary +stationed +stationer +stationers +stationery +stationing +stations +statism +statist +statistic +statistical +statistically +statistician +statisticians +statistics +statists +statoil +stator +stats +statuary +statue +statues +statuesque +statuette +statuettes +stature +status +statuses +statute +statutes +statutorily +statutory +staub +staunch +staunchest +staunchly +staunton +stave +staved +staves +staving +stay +stayed +staying +stayner +stays +std +stds +stead +steadfast +steadfastly +steadfastness +steadier +steadily +steadiness +steadman +steady +steagall +steak +steakhouse +steaks +steal +stealer +stealing +steals +stealth +stealthy +steam +steamboat +steamboats +steamed +steamer +steamers +steaming +steamroller +steams +steamship +steamy +stearns +stedman +steed +steeg +steel +steele +steeler +steelers +steelhead +steeling +steelmaker +steelmakers +steelmaking +steelman +steels +steelworker +steelworkers +steelworks +steely +steen +steep +steeped +steepened +steepening +steeper +steepest +steeple +steeplechase +steeply +steepness +steer +steerable +steerage +steered +steering +steers +steet +stefan +stefani +stefano +steffi +stegman +steichen +steiger +stein +steinbeck +steinberg +steinbrenner +steinem +steiner +steinhauser +steinhoff +steinman +steins +steinway +stejskal +stell +stella +stellar +stellenbosch +steller +stem +stemmed +stemming +stempel +stemple +stems +sten +stena +stench +stencil +stenciled +stenciling +stengel +stenger +stenholm +stenhouse +steno +stenographer +stenographers +stenosis +stent +stentor +steny +step +stepbrother +stepchild +stepchildren +stepdaughter +stepfather +stepfathers +steph +stephan +stephane +stephanie +stephanopoulos +stephen +stephens +stephenson +stepladder +stepladders +stepmother +stepney +steppe +stepped +stepper +steppers +steppes +stepping +steps +stepsister +stepsisters +stepson +steptoe +ster +stereo +stereos +stereoscopic +stereotype +stereotyped +stereotypes +stereotypical +stereotyping +sterile +sterility +sterilization +sterilizations +sterilize +sterilized +sterilizing +sterling +stern +sternberg +sterner +sternest +sternly +sternness +sterns +sternum +steroid +steroidal +steroids +stet +stethoscope +stethoscopes +stetson +steuben +steve +stevedore +steven +stevens +stevenson +steves +stevie +stew +steward +stewardess +stewardesses +stewards +stewardship +stewart +stewed +stewing +stews +stich +stick +stickball +sticker +stickers +stickiest +stickiness +sticking +stickle +stickler +sticklers +sticks +sticky +stiff +stiffed +stiffen +stiffened +stiffening +stiffens +stiffer +stiffest +stiffler +stiffly +stiffness +stiffs +stifle +stifled +stifles +stifling +stig +stiglitz +stigma +stigmatize +stigmatized +stigmatizing +stil +stile +stiles +stiletto +stilettos +still +stillborn +stilled +stiller +stillman +stillness +stills +stillwater +stillwell +stilt +stilted +stilton +stilts +stilwell +stimpson +stimpy +stimson +stimulant +stimulants +stimulate +stimulated +stimulates +stimulating +stimulation +stimulative +stimulator +stimulators +stimuli +stimulus +stine +sting +stinger +stingers +stinginess +stinging +stingray +stingrays +stings +stingy +stink +stinker +stinkers +stinking +stinks +stinky +stinson +stint +stints +stipe +stipend +stipends +stipulate +stipulated +stipulates +stipulating +stipulation +stipulations +stir +stirling +stirred +stirring +stirrings +stirrup +stirrups +stirs +stitch +stitched +stitches +stitching +stk +stm +stochastic +stock +stockbridge +stockbroker +stockbrokers +stockbroking +stockdale +stocked +stocker +stockholder +stockholders +stockholding +stockholdings +stockholm +stocking +stockings +stockman +stockpile +stockpiled +stockpiles +stockpiling +stockroom +stocks +stockton +stocky +stockyards +stoddard +stodgy +stoffel +stoiber +stoic +stoically +stoicism +stoke +stoked +stoker +stokes +stoking +stolberg +stole +stolen +stolichnaya +stolid +stoll +stolle +stollen +stoller +stoltz +stomach +stomachs +stomp +stomped +stomping +stomps +stone +stonecutter +stoned +stonehenge +stonehill +stoneman +stoner +stones +stonewall +stonewalled +stonewalling +stonewalls +stoneware +stoney +stong +stoning +stony +stood +stooge +stooges +stool +stools +stoop +stooped +stooping +stoops +stop +stopgap +stoplight +stoplights +stopover +stopovers +stoppage +stoppages +stopped +stopper +stoppers +stopping +stops +stopwatch +stor +storage +storages +store +stored +storefront +storefronts +storehouse +storehouses +storekeeper +storekeepers +storer +storeroom +stores +storey +storied +stories +storing +stork +storm +stormed +storming +storms +stormy +story +storyboard +storybook +storybooks +storyline +storylines +storyteller +storytellers +storytelling +stott +stouffer +stout +stoutly +stovall +stove +stovepipe +stover +stoves +stow +stowaway +stowe +stowed +stowell +str +stra +strachan +strack +strada +straddle +straddled +straddles +straddling +stradivarius +strafe +strafing +straggle +straggled +stragglers +straggling +strahan +straight +straightaway +straightedge +straighten +straightened +straightening +straightens +straighter +straightforward +straightforwardly +straightjacket +straights +strain +strained +strainer +strainers +straining +strains +strait +straitjacket +straits +strake +strand +stranded +stranding +strands +strang +strange +strangelove +strangely +strangeness +stranger +strangers +strangest +strangle +strangled +stranglehold +strangler +strangles +strangling +strangulation +strap +strapless +strapped +strapping +straps +strasbourg +strasburg +strasser +strassman +strata +stratagems +stratas +strategem +strategic +strategically +strategies +strategist +strategists +strategize +strategy +stratford +stratified +stratosphere +stratospheric +stratton +stratum +stratus +straub +straus +strauss +stravinsky +straw +strawberries +strawberry +straws +stray +strayed +strayhorn +straying +strays +streak +streaked +streaker +streaking +streaks +streaky +stream +streamed +streamer +streamers +streaming +streamline +streamlined +streamlines +streamlining +streams +streamside +streep +street +streetcar +streeter +streeters +streets +streetwise +streisand +streit +strength +strengthen +strengthened +strengthening +strengthens +strengths +strenuous +strenuously +strep +streptococcus +streptokinase +streptomycin +stress +stressed +stresses +stressful +stressing +stressor +stressors +stretch +stretched +stretcher +stretchers +stretches +stretching +stretchy +strewing +strewn +stribling +strick +stricken +stricker +strickland +strickler +strict +stricter +strictest +strictly +strictness +stricture +strictures +stride +stridency +strident +stridently +strider +strides +striding +strife +strike +strikebreakers +strikeout +strikeouts +striker +strikers +strikes +striking +strikingly +strine +string +stringed +stringency +stringent +stringently +stringer +stringers +stringfellow +stringing +strings +stringy +strip +stripe +striped +striper +stripes +stripling +stripped +stripper +strippers +stripping +strips +striptease +strive +striven +strives +striving +strivings +strobe +strobes +strode +stroganoff +stroh +stroke +stroked +strokes +stroking +stroll +strolled +stroller +strollers +strolling +strolls +strom +stronach +strong +stronger +strongest +stronghold +strongholds +strongly +strongman +strongpoint +strontium +stroock +strop +strother +stroud +stroup +strove +struble +struck +structural +structurally +structure +structured +structures +structuring +struggle +struggled +struggles +struggling +strum +strummer +strumming +strums +strung +strunk +strut +struthers +struts +strutting +strychnine +stryker +stu +stuart +stub +stubbed +stubble +stubborn +stubbornly +stubbornness +stubbs +stubby +stubs +stucco +stuck +stuckey +stud +studded +studebaker +student +students +studied +studies +studio +studios +studious +studiously +studley +studs +study +studying +stuff +stuffed +stuffer +stuffers +stuffing +stuffs +stuffy +stuka +stull +stultifying +stumble +stumbled +stumbles +stumbling +stump +stumpage +stumped +stumpf +stumping +stumps +stumpy +stun +stung +stunk +stunned +stunner +stunning +stunningly +stuns +stunt +stunted +stunting +stunts +stupa +stupefied +stupendous +stupid +stupider +stupidest +stupidity +stupidly +stupor +sturdier +sturdiness +sturdy +sturgeon +sturgeons +sturges +sturgis +sturm +sturtevant +stutter +stuttered +stuttering +stutters +stuttgart +sty +style +styled +styles +styling +stylings +stylish +stylishly +stylishness +stylist +stylistic +stylistically +stylists +stylized +stylus +stymie +stymied +stymies +styne +styrene +styrofoam +styx +su +suarez +suasion +suave +sub +subacute +subaru +subassemblies +subassembly +subatomic +subbed +subbing +subcategories +subchapter +subclass +subclasses +subcommittee +subcommittees +subcompact +subcomponents +subconscious +subconsciously +subcontinent +subcontinental +subcontract +subcontracted +subcontracting +subcontractor +subcontractors +subcontracts +subculture +subcultures +subcutaneous +subdirectories +subdirectory +subdivide +subdivided +subdividing +subdivision +subdivisions +subdue +subdued +subduing +suber +subfamily +subgroup +subgroups +subheadings +subhuman +subic +subject +subjected +subjecting +subjection +subjective +subjectively +subjectivity +subjects +subjugate +subjugated +subjugation +subjunctive +sublease +subleasing +sublet +sublimate +sublimated +sublime +subliminal +subliminally +subluxation +submachine +submarine +submariner +submariners +submarines +submerge +submerged +submersible +submersibles +submersion +submission +submissions +submissive +submissively +submit +submits +submitted +submitting +suboptimal +suborbital +subordinate +subordinated +subordinates +subordinating +subordination +subplot +subplots +subpoena +subpoenaed +subpoenaing +subpoenas +subpopulation +subregions +subrogation +subs +subscribe +subscribed +subscriber +subscribers +subscribes +subscribing +subscription +subscriptions +subsection +subsections +subsequent +subsequently +subservience +subservient +subset +subsets +subside +subsided +subsidence +subsides +subsidiaries +subsidiary +subsidies +subsiding +subsidization +subsidize +subsidized +subsidizes +subsidizing +subsidy +subsist +subsistence +subsisting +subsoil +subspecies +substance +substances +substandard +substantial +substantially +substantiate +substantiated +substantiates +substantiating +substantiation +substantive +substantively +substation +substitute +substituted +substitutes +substituting +substitution +substitutions +substrate +substrates +subsume +subsurface +subsystem +subsystems +subterfuge +subterranean +subtext +subtitle +subtitled +subtitles +subtle +subtler +subtlest +subtleties +subtlety +subtly +subtotal +subtract +subtracted +subtracting +subtraction +subtractions +subtracts +subtropical +subtype +suburb +suburban +suburbanites +suburbanization +suburbans +suburbia +suburbs +subversion +subversive +subversives +subvert +subverted +subverting +subverts +subway +subways +subzero +succeed +succeeded +succeeding +succeeds +succesful +succesfully +success +successes +successful +successfully +succession +successive +successively +successor +successors +succinct +succinctly +succulent +succulents +succumb +succumbed +succumbing +succumbs +sucess +sucessful +such +suchlike +suck +sucka +sucked +sucker +suckered +suckers +sucking +suckle +suckling +sucks +sucre +sucrose +suction +sudan +sudanese +sudbury +sudden +suddenly +suddenness +suds +sue +sued +suede +sues +suess +suey +suez +suffer +suffered +sufferer +sufferers +suffering +sufferings +suffers +suffice +sufficed +suffices +sufficiency +sufficient +sufficiently +suffield +suffix +suffixes +suffocate +suffocated +suffocating +suffocation +suffolk +suffrage +suffragette +suffragettes +suffragist +suffragists +suffuse +suffused +sufi +sugar +sugarcane +sugared +sugarman +sugars +sugary +sugg +suggest +suggested +suggestible +suggesting +suggestion +suggestions +suggestive +suggestively +suggestiveness +suggests +suharto +suhr +sui +suicidal +suicide +suicides +suing +suis +suisse +suit +suitability +suitable +suitably +suitcase +suitcases +suite +suited +suites +suitor +suitors +suits +suk +sula +suleiman +suleyman +sulfa +sulfate +sulfide +sulfites +sulfur +sulfuric +sulk +sulked +sulking +sulky +sullen +sullied +sullivan +sullivans +sully +sulphate +sulphide +sulphur +sultan +sultanate +sultans +sultry +sulzberger +sulzer +sum +suma +sumac +sumatra +sumatran +sumitomo +sumlin +summa +summaries +summarily +summarise +summarised +summarize +summarized +summarizes +summarizing +summary +summation +summations +summed +summer +summered +summerland +summers +summertime +summery +summing +summit +summits +summon +summoned +summoning +summons +summonses +sumner +sumo +sump +sumptuous +sums +sumter +sun +sunbathe +sunbathing +sunbeam +sunbelt +sunbird +sunbirds +sunburn +sunburned +sunburns +sunburst +sundae +sundaes +sundance +sunday +sundays +sunde +sunder +sundered +sundial +sundials +sundin +sundown +sundry +sundstrom +sunfish +sunflower +sunflowers +sung +sungard +sunglass +sunglasses +sunil +sunk +sunken +sunland +sunless +sunlight +sunlit +sunni +sunnier +sunniest +sunning +sunny +sunnyside +sunnyvale +sunrise +sunrises +sunroof +sunroom +suns +sunscreen +sunset +sunsets +sunshade +sunshine +sunspot +sunspots +suntan +sununu +sunup +sunward +suny +sup +supe +super +superabundance +superannuation +superb +superbly +superbowl +supercede +superceded +supercedes +supercenter +supercharge +supercharged +supercilious +supercomputer +supercomputers +supercomputing +superconducting +superconductivity +superconductor +superconductors +supercool +supercooled +supercritical +superdome +superego +superfamily +superficial +superficially +superfluous +superfund +superheated +superhero +superheroes +superhighway +superhighways +superhuman +superieure +superimposed +superimposing +superintendant +superintendent +superintendents +superior +superiority +superiors +superlative +superlatives +superman +supermarket +supermarkets +supermen +supermodel +supermodels +supernatural +supernaturalism +supernaturally +supernova +supernovas +superoxide +superpower +superpowers +supers +supersede +superseded +supersedes +superseding +supersonic +superstar +superstars +superstition +superstitions +superstitious +superstore +superstores +superstructure +supertanker +supervise +supervised +supervises +supervising +supervision +supervisor +supervisors +supervisory +superwoman +supine +supp +supped +supper +suppers +supplant +supplanted +supplanting +supplants +supple +supplement +supplemental +supplementary +supplementation +supplemented +supplementing +supplements +supplicant +supplication +supplied +supplier +suppliers +supplies +supply +supplying +support +supportable +supported +supporter +supporters +supporting +supportive +supports +suppose +supposed +supposedly +supposes +supposing +supposition +suppositions +suppository +suppress +suppressant +suppressants +suppressed +suppresses +suppressing +suppression +suppressive +suppressor +suppressors +supra +supranational +supremacist +supremacists +supremacy +supreme +supremely +supremes +supremo +suprise +suprised +suprising +suprisingly +sur +sura +surat +surcharge +surcharges +sure +surely +surer +sures +suresh +surest +surety +surf +surface +surfaced +surfaces +surfacing +surfactant +surfboard +surfboards +surfed +surfeit +surfer +surfers +surfing +surfs +surge +surged +surgeon +surgeons +surgeries +surgery +surges +surgical +surgically +surging +surinam +suriname +surly +surmise +surmised +surmises +surmising +surmount +surmounted +surmounting +surmounts +surname +surnames +surpass +surpassed +surpasses +surpassing +surplus +surpluses +surprise +surprised +surprises +surprising +surprisingly +surprize +surreal +surrealism +surrealist +surrealistic +surrealists +surrender +surrendered +surrendering +surrenders +surreptitious +surreptitiously +surrey +surrogate +surrogates +surround +surrounded +surrounding +surroundings +surrounds +surtax +surveillance +survey +surveyed +surveying +surveyor +surveyors +surveys +survivability +survivable +survival +survivalist +survivalists +survive +survived +survives +surviving +survivor +survivors +survivorship +surya +sus +susan +susana +susanna +susannah +susanne +susceptibility +susceptible +sushi +susie +susman +suspect +suspected +suspecting +suspects +suspend +suspended +suspender +suspenders +suspending +suspends +suspense +suspenseful +suspension +suspensions +suspicion +suspicions +suspicious +suspiciously +susquehanna +sussex +sussman +sustain +sustainability +sustainable +sustained +sustaining +sustains +sustenance +susy +sutcliffe +suter +sutherland +sutter +sutton +suture +sutures +suu +suzanna +suzanne +suzette +suzhou +suzie +suzuki +suzy +sv +svc +svelte +sven +svenska +svensson +svetlana +svoboda +sw +swa +swab +swabs +swaddled +swaddling +swag +swaggart +swagger +swaggering +swags +swahili +swain +swaine +swale +swales +swallow +swallowed +swallowing +swallows +swam +swami +swaminathan +swamp +swamped +swamping +swampland +swamps +swampy +swan +swanee +swank +swanky +swann +swans +swanson +swanton +swap +swapped +swapping +swaps +sward +swarm +swarmed +swarming +swarms +swart +swarthmore +swarthy +swartz +swashbuckling +swastika +swastikas +swat +swatch +swatches +swath +swathe +swathed +swaths +swatted +swatting +sway +swayed +swaying +swayne +sways +swayze +swaziland +swear +swearing +swears +sweat +sweated +sweater +sweaters +sweating +sweats +sweatshirt +sweatshirts +sweatshop +sweatshops +sweaty +swed +sweda +swede +sweden +swedes +swedish +swee +sweeney +sweeny +sweep +sweeper +sweepers +sweeping +sweeps +sweepstake +sweepstakes +sweet +sweeten +sweetened +sweetener +sweeteners +sweetening +sweeter +sweetest +sweetheart +sweethearts +sweetie +sweeties +sweeting +sweetly +sweetness +sweets +sweetwater +sweety +swell +swelled +swelling +swells +swelter +sweltering +swenson +swept +swerve +swerved +swerves +swerving +swick +swift +swifter +swiftest +swiftly +swiftness +swifts +swig +swigs +swill +swilling +swim +swimmer +swimmers +swimming +swims +swimsuit +swimsuits +swimwear +swindell +swindle +swindled +swindler +swindlers +swindles +swindling +swindon +swine +swing +swinger +swingers +swinging +swingle +swings +swinney +swinton +swipe +swiped +swipes +swiping +swire +swirl +swirled +swirling +swirls +swish +swished +swisher +swishes +swiss +swissair +switch +switchback +switchblade +switchboard +switchboards +switched +switcher +switchers +switches +switching +switchover +switzer +switzerland +swivel +swivels +swollen +swoon +swooned +swooning +swoop +swooped +swooping +swoops +swoosh +swope +sword +swordfish +swordplay +swords +swore +sworn +swum +swung +sy +sybaritic +sybase +sybil +sybille +sycamore +sycophant +sycophantic +sycophants +syd +sydney +sydow +syed +sykes +sykora +syllable +syllables +syllabus +syllogism +sylva +sylvain +sylvan +sylvania +sylvester +sylvia +sylvie +symantec +symbiosis +symbiotic +symbol +symbolic +symbolically +symbolism +symbolize +symbolized +symbolizes +symbolizing +symbols +symington +symmetric +symmetrical +symmetrically +symmetry +symms +symonds +symons +sympathetic +sympathetically +sympathies +sympathize +sympathized +sympathizer +sympathizers +sympathizes +sympathizing +sympathy +symphonic +symphonies +symphony +symposia +symposium +symposiums +symptom +symptomatic +symptoms +syn +synagogue +synagogues +synapse +synapses +synaptic +sync +synch +synched +synchro +synchronicity +synchronization +synchronize +synchronized +synchronizes +synchronizing +synchronous +synchronously +synchrony +syncing +syncopated +syncopation +syncretic +syncretism +syncs +syndicate +syndicated +syndicates +syndication +syndications +syndicator +syndrome +syndromes +synergies +synergism +synergistic +synergistically +synergy +synod +synonym +synonymous +synonyms +synopses +synopsis +syntax +synth +synthesis +synthesize +synthesized +synthesizer +synthesizers +synthesizes +synthesizing +synthetic +synthetically +synthetics +syphilis +syr +syracuse +syria +syrian +syrians +syringe +syringes +syrup +syrups +syst +system +systematic +systematically +systematics +systemic +systemically +systems +systemwide +sytem +szabo +szeto +ta +tab +tabasco +tabb +tabby +taber +tabernacle +tabitha +table +tableau +tableaux +tablecloth +tablecloths +tabled +tables +tablespoon +tablespoons +tablet +tabletop +tablets +tableware +tabling +tabloid +tabloids +taboo +taboos +tabor +tabs +tabular +tabulate +tabulated +tabulates +tabulating +tabulation +tabulations +tac +tach +tachometer +tacit +tacitly +taciturn +tacitus +tack +tacked +tacking +tackle +tackled +tackles +tackling +tacks +tacky +taco +tacoma +tacos +tact +tactful +tactfully +tactic +tactical +tactically +tactician +tacticians +tactics +tactile +tad +tadpole +tadpoles +tae +taekwondo +taff +taffeta +taffy +taft +tag +tagalog +taggart +tagged +tagging +tagline +tags +taha +taher +tahir +tahiti +tahitian +tahoe +tai +tail +tailback +tailed +tailgate +tailgates +tailgating +tailhook +tailing +tailings +taillights +tailor +tailored +tailoring +tailors +tailpipe +tails +tailspin +tailwind +tain +taint +tainted +tainting +taints +taipei +tait +taiwan +taiwanese +taj +tajik +tajikistan +tak +taka +takagi +takahashi +takashi +takashimaya +take +takeaway +takeda +taken +takeo +takeoff +takeoffs +takeout +takeover +takeovers +taker +takers +takes +takeshi +taketh +takin +taking +takings +tal +talal +talbot +talbots +talbott +talc +talcum +tale +talent +talented +talents +tales +tali +taligent +talisman +talk +talkative +talked +talker +talkers +talkie +talkies +talkin +talking +talks +tall +talladega +tallahassee +tallent +taller +tallest +talley +tallied +tallies +tallow +tally +tallying +talmadge +talmud +talon +talons +tam +tama +tamale +tamales +tamar +tamara +tamarin +tamarind +tamarins +tamarisk +tamas +tamblyn +tambourine +tame +tamed +tamer +tames +tamil +taming +tammany +tammie +tammy +tamoxifen +tamp +tampa +tampax +tamped +tamper +tampered +tampering +tamping +tampon +tampons +tan +tanaka +tandem +tandems +tandon +tandy +tang +tangent +tangential +tangentially +tangents +tangerine +tangerines +tangible +tangibles +tangier +tangle +tangled +tangles +tanglewood +tangling +tango +tangos +tangs +tangy +tania +taniguchi +tank +tankage +tankard +tankards +tanked +tanker +tankers +tanking +tanks +tanned +tannenbaum +tanner +tannic +tannin +tanning +tannins +tans +tansey +tansu +tant +tantalize +tantalized +tantalizes +tantalizing +tantalizingly +tantalum +tantalus +tantamount +tanto +tantra +tantric +tantrum +tantrums +tanya +tanzania +tanzanian +tao +taoism +taoist +taormina +taos +tap +tapa +tapas +tape +taped +taper +tapered +tapering +tapers +tapes +tapestries +tapestry +tapia +taping +tapings +tapioca +tapir +tapp +tappan +tapped +tapper +tapping +taproot +taps +tapscott +tar +tara +taranto +tarantula +tarde +tardiness +tardy +target +targeted +targeting +targets +targetted +tarheel +tariff +tariffs +tariq +tarleton +tarmac +tarn +tarnish +tarnished +tarnishes +tarnishing +taro +tarot +tarp +tarpon +tarps +tarr +tarragon +tarragona +tarrant +tarred +tarring +tarry +tars +tarsus +tart +tartaglia +tartan +tartar +tartars +tarte +tarted +tarter +tartness +tarts +taryn +tarzan +tas +taser +tasha +tashkent +task +tasked +tasker +taskforce +tasking +taskmaster +tasks +tasman +tasmania +tasmanian +tass +tassel +tassels +tassie +tasso +taste +tasted +tasteful +tastefully +tasteless +taster +tasters +tastes +tastier +tasting +tastings +tasty +tat +tata +tatar +tatars +tate +tater +tates +tatiana +tatoo +tats +tatter +tattered +tatters +tattersall +tattle +tattling +tattoo +tattooed +tattooing +tattoos +tatty +tatu +tatum +tatyana +tau +taub +taube +taubman +taught +taunt +taunted +taunting +taunton +taunts +taupe +taurine +taurus +taut +tautological +tautology +tavares +tavern +taverna +taverns +tawana +tawdry +tawil +tawney +tawny +tax +taxable +taxation +taxed +taxes +taxi +taxicab +taxicabs +taxidermist +taxied +taxiing +taxing +taxis +taxman +taxonomists +taxonomy +taxpayer +taxpayers +taxpaying +tay +taylor +taylors +tb +tbilisi +tch +tchaikovsky +te +tea +teac +teach +teachable +teacher +teachers +teaches +teaching +teachings +teacup +teacups +teagle +teague +teahouse +teak +teal +team +teamed +teaming +teammate +teammates +teams +teamster +teamsters +teamwork +teapot +tear +tearful +tearfully +teargas +tearing +tearoom +tears +teary +teas +teasdale +tease +teased +teaser +teasers +teases +teasing +teaspoon +teaspoons +teat +teatro +teats +tec +tech +techie +techies +techne +technical +technicalities +technicality +technically +technician +technicians +technicolor +technics +technique +techniques +techno +technocracy +technocrat +technocratic +technocrats +technologic +technological +technologically +technologies +technologist +technologists +technology +techs +teck +tectonic +tectonics +ted +teddy +tedeschi +tedesco +tedious +tediously +tedium +teds +tee +teed +teeing +teel +teem +teeming +teems +teen +teena +teenage +teenaged +teenager +teenagers +teenie +teens +teensy +teeny +teer +tees +teeter +teetered +teetering +teeters +teeth +teething +teflon +teheran +tehran +teixeira +tejada +tejano +tejas +tejon +tek +tektronix +tel +telco +tele +telecast +telecasting +telecasts +telecom +telecomm +telecommunication +telecommunications +telecommuting +telecoms +teleconference +teleconferencing +teledyne +telefon +telefonica +telefonos +telegenic +teleglobe +telegram +telegrams +telegraph +telegraphed +telegraphic +telegraphs +telekom +telemachus +telemarketers +telemarketing +telematic +telematics +telemetry +teleological +telepath +telepathic +telepathy +telephone +telephoned +telephones +telephonic +telephoning +telephony +telephoto +teleport +teleprompter +telescope +telescoped +telescopes +telescopic +telescoping +telesis +teletext +telethon +teletype +televangelist +televisa +televise +televised +televising +television +televisions +televison +telex +telfer +telford +tell +tellabs +teller +tellers +telling +tellingly +tells +telltale +telluride +tellus +telly +telnet +telos +telstar +telugu +tem +temblor +temecula +temerity +temin +temp +tempe +temper +tempera +temperament +temperamental +temperamentally +temperaments +temperance +temperate +temperature +temperatures +tempered +tempering +tempers +tempest +tempestuous +templar +templars +template +templates +temple +temples +templeton +tempo +temporal +temporaries +temporarily +temporary +tempore +tempos +temps +tempt +temptation +temptations +tempted +tempter +tempting +temptress +tempts +ten +tena +tenable +tenacious +tenaciously +tenacity +tenancy +tenant +tenants +tench +tend +tended +tendencies +tendency +tendentious +tender +tendered +tendering +tenderloin +tenderly +tenderness +tenders +tending +tendinitis +tendon +tendonitis +tendons +tendrils +tends +tenement +tenements +tenet +tenets +tenfold +teng +tenn +tennant +tenneco +tenner +tennesse +tennessean +tennesseans +tennessee +tenney +tennis +tennison +tennyson +tenor +tenors +tens +tense +tensely +tenses +tensile +tension +tensions +tensor +tent +tentacle +tentacles +tentative +tentatively +tented +tenth +tenths +tents +tenuous +tenure +tenured +tenures +tepee +tepid +tepper +tequila +tequilas +ter +tera +teradata +teradyne +tercel +terence +teresa +teresita +teri +teriyaki +terje +terkel +term +terman +termed +terminal +terminally +terminals +terminate +terminated +terminates +terminating +termination +terminations +terminator +terminators +termine +terming +termini +terminology +terminus +termite +termites +terms +tern +terns +terpenes +terra +terrace +terraced +terraces +terrain +terrains +terral +terrance +terrapin +terrapins +terre +terrell +terrence +terrestrial +terri +terrible +terribly +terrie +terrier +terriers +terrific +terrifically +terrified +terrifies +terrify +terrifying +terrifyingly +territorial +territoriality +territorially +territories +territory +terror +terrorism +terrorist +terroristic +terrorists +terrorize +terrorized +terrorizes +terrorizing +terrors +terry +terse +tersely +tertiary +terzian +tesco +tesla +tesoro +tess +tessa +tesseract +tessie +test +testa +testable +testament +testamentary +testaments +testbed +tested +tester +testers +testes +testicle +testicles +testicular +testified +testifies +testify +testifying +testily +testimonial +testimonials +testimonies +testimony +testing +testosterone +tests +testy +tet +tetanus +tete +tether +tethered +tethers +teton +tetra +tetrachloride +tetracycline +tetrahedron +tetris +tew +tewksbury +tews +tex +texaco +texan +texans +texarkana +texas +text +textbook +textbooks +textile +textiles +textron +texts +textual +textually +texture +textured +textures +tg +th +tha +thabo +thacker +thackeray +thad +thaddeus +thai +thailand +thain +thais +thakur +thal +thaler +thalia +thalidomide +tham +thames +than +thane +thang +thanh +thank +thanked +thankful +thankfully +thankfulness +thanking +thankless +thanks +thanksgiving +thanksgivings +thankyou +thar +tharp +that +thatch +thatched +thatcher +thats +thaw +thawed +thawing +thaws +thay +thayer +the +thea +theater +theaters +theatre +theatres +theatrical +theatricality +theatrically +theatrics +thebes +thee +theft +thefts +thein +their +theirs +theis +theism +thelma +them +thema +thematic +thematically +theme +themed +themes +themself +themselves +then +thence +thenceforth +theo +theobald +theocracy +theocratic +theodor +theodora +theodore +theologian +theologians +theological +theologically +theologies +theology +theophylline +theorem +theorems +theoretic +theoretical +theoretically +theoretician +theoreticians +theories +theorist +theorists +theorize +theorized +theorizes +theorizing +theory +ther +therapeutic +therapeutically +therapeutics +therapies +therapist +therapists +therapy +theravada +there +thereabouts +thereafter +thereby +therefor +therefore +therefrom +therein +thereof +thereon +theres +theresa +therese +thereto +thereupon +therm +thermal +thermally +thermals +thermo +thermocouple +thermodynamic +thermodynamics +thermography +thermometer +thermometers +thermonuclear +thermoplastic +thermoplastics +thermos +thermostat +thermostats +theron +theroux +thesaurus +these +theses +theseus +thesis +thespian +thespians +thessalonians +theta +thew +they +theyre +thi +thiamin +thibault +thick +thicken +thickened +thickener +thickening +thickens +thicker +thickest +thicket +thickets +thickly +thickness +thief +thiel +thier +thierry +thiessen +thievery +thieves +thieving +thigh +thighs +thigpen +thimble +thin +thine +thing +things +thingy +think +thinkable +thinker +thinkers +thinking +thinkpad +thinks +thinly +thinned +thinner +thinners +thinness +thinnest +thinning +thins +thiokol +thir +third +thirdly +thirds +thirst +thirsting +thirsty +thirteen +thirteenth +thirties +thirtieth +thirty +thirtysomething +this +thissen +thistle +thistles +tho +tholen +thom +thoman +thomas +thomases +thomasina +thomason +thomasson +thome +thompson +thomsen +thomson +thon +thone +thong +thongs +thor +thoracic +thorax +thoreau +thorium +thorn +thornberry +thornburgh +thorne +thorniest +thorns +thornton +thorny +thorough +thoroughbred +thoroughbreds +thoroughfare +thoroughfares +thoroughly +thoroughness +thorp +thorpe +thorson +thorsten +thorton +thorugh +thos +those +thoth +thou +though +thought +thoughtful +thoughtfully +thoughtfulness +thoughtless +thoughtlessness +thoughts +thousand +thousands +thousandth +thrace +thrall +thrash +thrashed +thrasher +thrashers +thrashes +thrashing +thread +threadbare +threaded +threadgill +threading +threads +threat +threaten +threatened +threatening +threateningly +threatens +threats +three +threefold +threes +threesome +threesomes +thresher +threshing +threshold +thresholds +threw +thrice +thrift +thrifts +thrifty +thrill +thrilled +thriller +thrillers +thrilling +thrills +thrips +thrive +thrived +thrives +thriving +throat +throats +throb +throbbing +throbs +throes +thrombolytic +thrombosis +throne +throng +thronged +throngs +throop +throttle +throttled +throttles +throttling +through +throughly +throughout +throughput +throughs +throught +throw +throwaway +throwback +throwdown +thrower +throwers +throwing +thrown +throws +thru +thrush +thrushes +thrust +thruster +thrusters +thrusting +thrusts +thruway +ths +thucydides +thud +thug +thuggery +thuggish +thugs +thule +thumb +thumbed +thumbing +thumbnail +thumbprint +thumbs +thump +thumped +thumper +thumpers +thumping +thumps +thun +thunder +thunderball +thunderbird +thunderbirds +thunderbolt +thundered +thundering +thunderous +thunders +thunderstorm +thunderstorms +thunderstruck +thunk +thurber +thurgood +thurman +thurmond +thurow +thurs +thursday +thursdays +thurston +thus +thusly +thwart +thwarted +thwarting +thwarts +thy +thye +thyme +thymidine +thyroid +thyself +thyssen +ti +tia +tian +tiananmen +tianjin +tiara +tiaras +tibbs +tiber +tibet +tibetan +tibetans +tibia +tibor +tiburon +tic +tice +tick +ticked +ticker +tickers +ticket +ticketed +ticketing +ticketless +ticketmaster +tickets +ticking +tickle +tickled +tickler +tickles +tickling +ticklish +ticks +ticonderoga +tics +tidal +tidbit +tidbits +tiddlywinks +tide +tides +tidewater +tidings +tidwell +tidy +tie +tiebreaker +tied +tiegs +tieing +tiempo +tien +tier +tiered +tierney +tierra +tiers +ties +tiff +tiffany +tiffin +tig +tiger +tigers +tigger +tighe +tight +tighten +tightened +tightening +tightens +tighter +tightest +tightly +tightness +tightrope +tights +tightwad +tigress +tigris +tijuana +tikes +tiki +tikka +til +tilbury +tilda +tilde +tilden +tile +tiled +tiles +tilghman +tiling +till +tiller +tilley +tilling +tillman +tilly +tilney +tilson +tilt +tilted +tilting +tilton +tilts +tim +timber +timberlake +timberland +timberlands +timberline +timbers +timbre +timbuktu +time +timed +timeframe +timekeeper +timekeepers +timekeeping +timeless +timelessness +timeline +timelines +timeliness +timely +timeout +timeouts +timepiece +timer +timers +times +timesaving +timeshare +timetable +timetables +timewise +timex +timid +timidity +timidly +timing +timings +timken +timmer +timmerman +timmons +timmy +timo +timon +timor +timorese +timorous +timothy +timpani +tims +tin +tina +tincture +tindall +tinder +tinderbox +tine +tines +ting +tinge +tinged +tingle +tingles +tingling +tingly +tings +tinier +tiniest +tink +tinker +tinkered +tinkerer +tinkerers +tinkering +tinkers +tinkle +tinkler +tinkling +tinny +tino +tins +tinsel +tinseltown +tinsley +tint +tinted +tinting +tinto +tints +tiny +tion +tions +tip +tipo +tipoff +tipp +tipped +tipper +tipperary +tippers +tippet +tippett +tipping +tipple +tippy +tips +tipster +tipsy +tiptoe +tiptoed +tiptoeing +tiptoes +tipton +tirade +tirades +tiramisu +tirana +tire +tired +tiredness +tireless +tirelessly +tires +tiresome +tiring +tis +tisch +tish +tissue +tissues +tit +tita +titan +titania +titanic +titanium +titans +tite +tithe +tithing +titian +titillate +titillated +titillating +titillation +title +titled +titleholder +titles +titling +tito +titter +tittle +titular +titus +titusville +tivoli +tizzy +tk +tlingit +tm +tn +to +toa +toad +toads +toal +toast +toasted +toaster +toasters +toasting +toastmaster +toasts +toasty +tobacco +tobaccos +tobago +tobe +tobey +tobias +tobin +toboggan +toboggans +toby +tock +tocqueville +tod +toda +today +todays +todd +toddle +toddler +toddlers +toddling +toddy +toe +toed +toehold +toeing +toenail +toenails +toes +toffee +toffler +toft +tofu +toga +together +togetherness +togethers +toggle +toggled +togo +togs +toho +toil +toiled +toilet +toiletries +toiletry +toilets +toilette +toiling +toils +token +tokenism +tokens +tokyo +tol +tolan +tolbert +told +tole +toledo +tolentino +tolerable +tolerance +tolerances +tolerant +tolerate +tolerated +tolerates +tolerating +toleration +toll +tollbooth +tolled +toller +tolley +tolling +tolls +tollway +tolman +tolstoy +toluene +tom +toma +tomahawk +tomahawks +toman +tomas +tomasi +tomato +tomatoes +tomb +tomboy +tombs +tombstone +tombstones +tomcat +tomcats +tome +tomei +tomes +tomkins +tomlin +tomlinson +tommie +tommorrow +tommy +tomography +tomorrow +tomorrows +tompkins +toms +ton +tonal +tonality +tone +toned +toner +tones +toney +tong +tonga +tongs +tongue +tongued +tongues +toni +tonic +tonics +tonight +toning +tonite +tonk +tonka +tonkin +tonks +tonn +tonnage +tonnages +tonne +tonner +tonnes +tons +tonsil +tonsillectomy +tonsils +tony +tonya +tonys +too +toobin +took +tooke +tool +toolbox +toole +tooled +tooling +toolkit +toolmakers +tools +toomer +toomey +toon +toons +toot +tooth +toothbrush +toothbrushes +toothed +toothless +toothpaste +toothpastes +toothpick +toothpicks +toothy +tooting +tootle +toots +tootsie +top +topanga +topaz +tope +topeka +topiary +topic +topical +topics +topknot +topless +topnotch +topographic +topographical +topography +topol +topology +topped +topper +toppers +topping +toppings +topple +toppled +topples +toppling +topps +tops +topsoil +topspin +topsy +toque +tor +tora +torah +torch +torchbearer +torched +torches +torching +torchlight +tore +toren +tori +tories +torino +torment +tormented +tormenting +tormentor +tormentors +torments +torn +tornado +tornadoes +tornados +torney +toro +toronto +toros +torpedo +torpedoed +torpedoes +torpedoing +torpedos +torpid +torpor +torque +torrance +torre +torrence +torrent +torrential +torrents +torres +torrey +torricelli +torrid +torsional +torso +torsos +torsten +tort +torte +tortellini +tortilla +tortillas +tortoise +tortoises +torts +tortuous +torture +tortured +torturers +tortures +torturing +torturous +toru +torus +tory +tos +tosca +tosh +toshiba +toss +tossed +tosses +tossing +tot +total +totaled +totaling +totalitarian +totalitarianism +totalitarians +totality +totalled +totalling +totally +totals +tote +toted +totem +totemic +totems +totes +toth +toting +toto +tots +totten +totter +tottered +tottering +totters +totty +touch +touchdown +touchdowns +touche +touched +touches +touching +touchingly +touchstone +touchy +tough +toughen +toughened +toughening +toughens +tougher +toughest +toughness +toughs +toulouse +toupee +tour +toured +tourette +touring +tourism +tourist +tourists +touristy +tourmaline +tournament +tournaments +tourney +tourneys +tourniquet +tours +toussaint +tout +touted +touting +touts +tow +toward +towards +towbin +towed +towel +towels +tower +towered +towering +towers +towing +town +towne +towner +towners +townes +townhouse +townhouses +townie +townies +towns +townsend +townsfolk +townshend +township +townships +townsman +townspeople +tows +towson +toxic +toxicity +toxicological +toxicologist +toxicologists +toxicology +toxics +toxin +toxins +toy +toya +toyed +toying +toyland +toymaker +toynbee +toyo +toyota +toyotas +toys +tozer +tp +tpm +tr +tra +trac +trace +traceable +traced +tracer +tracers +traces +tracey +trachea +tracheal +trachtenberg +tracie +tracing +track +trackball +tracked +tracker +trackers +tracking +tracks +tract +tractable +traction +tractor +tractors +tracts +tracy +trad +tradable +trade +tradeable +tradecraft +traded +trademark +trademarked +trademarks +tradename +tradeoff +tradeoffs +trader +traders +trades +tradesmen +trading +tradition +traditional +traditionalism +traditionalist +traditionalists +traditionally +traditions +trafalgar +traffic +trafficker +traffickers +trafficking +traficant +tragedies +tragedy +tragic +tragically +tragicomic +trail +trailblazer +trailblazers +trailed +trailer +trailers +trailhead +trailing +trails +train +trainable +trained +trainee +trainees +trainer +trainers +training +trainor +trains +traipse +traipsing +trait +traitor +traitorous +traitors +traits +trajan +trajectories +trajectory +tram +trammel +tramp +tramping +trample +trampled +tramples +trampling +trampoline +trams +tran +trance +tranche +trane +trang +trani +tranquil +tranquility +tranquilizer +tranquilizers +tranquillity +trans +transact +transacted +transacting +transaction +transactional +transactions +transamerica +transatlantic +transceiver +transceivers +transcend +transcended +transcendence +transcendent +transcendental +transcending +transcends +transcontinental +transcribe +transcribed +transcriber +transcribers +transcribes +transcribing +transcript +transcriptase +transcription +transcriptions +transcripts +transducer +transducers +transduction +transfer +transferability +transferable +transfered +transference +transfering +transferor +transferrable +transferred +transferring +transfers +transfiguration +transfixed +transform +transformation +transformational +transformations +transformative +transformed +transformer +transformers +transforming +transforms +transfuse +transfused +transfusion +transfusions +transgenic +transgress +transgressed +transgresses +transgression +transgressions +transgressor +transgressors +transience +transient +transients +transistor +transistors +transit +transited +transiting +transition +transitional +transitioned +transitioning +transitions +transitive +transitory +transits +translate +translated +translates +translating +translation +translations +translator +translators +translucent +transmission +transmissions +transmit +transmits +transmittable +transmittal +transmitted +transmitter +transmitters +transmitting +transmutation +transmute +transmuted +transnational +transocean +transoceanic +transom +transoms +transparencies +transparency +transparent +transparently +transpire +transpired +transpires +transpiring +transplant +transplantation +transplanted +transplanting +transplants +transponder +transponders +transport +transportable +transportation +transported +transporter +transporters +transporting +transports +transpose +transposed +transposing +transposition +transpositions +transsexual +transsexualism +transsexuals +transshipment +transverse +transvestism +transvestite +transvestites +transworld +transylvania +trap +trapdoor +trapdoors +trapeze +trapezoid +trapped +trapper +trappers +trapping +trappings +trappist +traps +trash +trashed +trashes +trashing +trashy +trattoria +traub +trauma +traumas +traumatic +traumatized +trav +travail +travails +travel +traveled +traveler +travelers +traveling +travelled +traveller +travellers +travelling +travelodge +travelogue +travels +travers +traversal +traverse +traversed +traverses +traversing +travesties +travesty +travis +travolta +trawl +trawler +trawlers +tray +traylor +traynor +trays +treacherous +treachery +tread +treading +treadmill +treadmills +treads +treadway +treadwell +treason +treasonable +treasure +treasured +treasurer +treasurers +treasures +treasuries +treasury +treat +treatable +treated +treaties +treating +treatise +treatises +treatment +treatments +treats +treaty +treble +trebled +trebling +tree +treeless +trees +treetop +treetops +trejo +trek +trekked +trekker +trekkers +trekking +treks +trellis +tremaine +tremblay +tremble +trembled +trembling +tremendous +tremendously +tremens +tremor +tremors +tremulous +trench +trenchant +trenches +trenching +trend +trended +trendier +trendiest +trending +trendline +trends +trendsetter +trendy +trent +trenton +trepidation +treptow +tres +trespass +trespassers +trespasses +trespassing +tress +tresses +trestle +trevino +trevor +trey +tri +triad +triads +triage +trial +trials +triangle +triangles +triangular +triangulation +trianon +triathlon +trib +tribal +tribalism +tribble +tribbles +tribe +tribeca +tribes +tribesman +tribesmen +tribulation +tribulations +tribunal +tribunals +tribune +tributaries +tributary +tribute +tributes +trice +triceps +triceratops +trichet +tricia +trick +tricked +trickery +trickier +trickiest +tricking +trickle +trickled +trickles +trickling +tricks +trickster +tricksters +tricky +trico +tricolor +tricon +tricycle +tricycles +tricyclic +trident +tried +triennial +trier +tries +trieste +trifle +trifled +trifles +trifling +trig +trigg +trigger +triggered +triggering +triggers +triglyceride +triglycerides +trigonometry +trilateral +trill +trilling +trillion +trillions +trillium +trillo +trilogy +trim +trimble +trimester +trimesters +trimmed +trimmer +trimmers +trimming +trimmings +trims +trina +tring +trinh +trinidad +trinity +trinket +trinkets +trio +triomphe +trios +trip +tripartite +tripe +triple +tripled +triples +triplet +triplets +triplex +triplicate +tripling +triply +tripod +tripods +tripoli +tripp +tripped +tripper +trippers +tripping +tripple +trips +triptych +tripwire +trish +trisha +trisomy +tristan +tristar +tristate +triste +tristram +trite +tritium +triton +tritt +triumph +triumphal +triumphant +triumphantly +triumphed +triumphs +triumvirate +trivia +trivial +triviality +trivialize +trivialized +trivializing +trivially +troche +trod +troika +trois +trojan +trojans +troll +trolled +trolley +trolleys +trolling +trolls +trombone +trombones +trombonist +tromp +tron +tronic +troop +trooped +trooper +troopers +troops +trophies +trophy +tropic +tropical +tropicana +tropics +trost +trot +trotman +trots +trotsky +trott +trotted +trotter +trotting +troubadour +trouble +troubled +troublemaker +troublemakers +troublemaking +troubles +troubleshoot +troubleshooter +troubleshooting +troublesome +troubling +trough +troughs +trounce +trounced +trounces +trouncing +troup +troupe +troupes +trouser +trousers +trout +troutman +trovatore +trove +troves +trow +trowbridge +trowel +troy +truancy +truant +truce +truck +trucked +trucker +truckers +trucking +truckload +truckloads +trucks +truculent +trudeau +trudge +trudged +trudges +trudging +trudie +trudy +true +trueblood +truelove +truely +trueman +truer +truest +truffaut +truffle +truffles +truism +trujillo +truly +truman +trumbull +trump +trumped +trumpet +trumpeted +trumpeter +trumpeters +trumpeting +trumpets +trumping +trumps +truncate +truncated +truncates +truncating +truncation +truncheon +truncheons +trundle +trundled +trunk +trunks +truong +truro +truss +trussed +trusses +trust +trusted +trustee +trustees +trusteeship +trusting +trusts +trustworthiness +trustworthy +trusty +truth +truthful +truthfully +truthfulness +truths +try +trying +tryon +tryout +tryouts +tryptophan +tryst +ts +tsai +tsang +tsao +tsar +tse +tseng +tsetse +tsi +tsk +tso +tsp +tss +tsui +tsunami +tsunamis +tthe +tu +tuan +tub +tuba +tubal +tubbs +tubby +tube +tuber +tuberculosis +tuberous +tubers +tubes +tubing +tubman +tubs +tubular +tucci +tuchman +tuck +tucked +tucker +tuckered +tucking +tucks +tucson +tucuman +tudjman +tudor +tue +tuebingen +tues +tuesday +tuesdays +tufa +tuff +tuft +tufted +tufts +tug +tugboat +tugboats +tugged +tugging +tugs +tugwell +tuition +tuitions +tula +tulane +tulare +tulip +tulips +tulkarm +tull +tullio +tullius +tulloch +tully +tulsa +tumble +tumbled +tumbler +tumblers +tumbles +tumbleweed +tumbling +tummies +tummy +tumor +tumors +tums +tumult +tumultuous +tun +tuna +tunable +tundra +tune +tuned +tuneful +tuner +tunes +tuneup +tung +tungsten +tunic +tunica +tunics +tuning +tunis +tunisia +tunisian +tunku +tunnel +tunneling +tunnels +tunney +tunstall +tupac +tupelo +tupper +tupperware +tur +turban +turbans +turbidity +turbine +turbines +turbo +turbocharged +turbocharger +turbocharging +turbojet +turboprop +turboprops +turbos +turbot +turbulence +turbulent +turco +turcotte +turd +ture +turf +turgeon +turgid +turgut +turin +turing +turismo +turk +turkey +turkeys +turki +turkic +turkish +turkmenistan +turks +turley +turmeric +turmoil +turn +turnabout +turnaround +turnarounds +turnbull +turncoat +turndown +turned +turner +turney +turning +turnip +turnips +turnkey +turnoff +turnout +turnouts +turnover +turnovers +turnpike +turns +turnstile +turnstiles +turntable +turntables +turow +turpentine +turpin +turpitude +turquoise +turret +turrets +turtle +turtleneck +turtlenecks +turtles +turvy +tuscan +tuscany +tuscon +tush +tusk +tusks +tussle +tussled +tussles +tut +tutankhamen +tutelage +tutor +tutored +tutorial +tutorials +tutoring +tutors +tutsi +tutti +tuttle +tutto +tutu +tux +tuxedo +tuxedos +tv +twaddle +twain +twang +twangy +twas +tweak +tweaked +tweaking +tweaks +tweed +tweedle +tweedledee +tweedledum +tweedy +tween +tweet +tweeter +tweeters +tweezer +tweezers +twelfth +twelve +twenties +twentieth +twenty +twentysomethings +twi +twice +twiddle +twiddling +twig +twiggs +twiggy +twigs +twilight +twill +twin +twine +twined +twinge +twinges +twining +twinkie +twinkies +twinkle +twinkles +twinkling +twinning +twins +twirl +twirled +twirler +twirling +twirls +twist +twisted +twister +twisters +twisting +twists +twisty +twit +twitch +twitched +twitches +twitching +twits +twitty +twixt +two +twofold +twombly +twos +twosome +twyla +tx +txt +ty +tyco +tycoon +tycoons +tye +tying +tyke +tykes +tylenol +tyler +tynan +tyndall +tyne +tyner +tynes +type +typecast +typecasting +typed +typeface +typefaces +types +typescript +typeset +typesetters +typesetting +typewriter +typewriters +typewritten +typhoid +typhoon +typhoons +typhus +typical +typically +typified +typifies +typify +typing +typist +typists +typo +typographical +typography +typos +tyra +tyrannical +tyrannies +tyrannize +tyrannosaurus +tyranny +tyrant +tyrants +tyre +tyree +tyro +tyrolean +tyrone +tyrrell +tyrrhenian +tyson +tysons +uber +ubiquitous +ubiquity +udder +udo +ueberroth +ueda +uemura +uganda +ugandan +ugh +uglier +ugliest +ugliness +ugly +uh +uhh +uhuru +ui +ukraine +ukrainian +ukrainians +ukulele +ul +ulan +ulcer +ulcerative +ulcers +ulema +ulf +ullman +ulm +ulman +ulnar +ulrich +ulster +ulterior +ultima +ultimate +ultimately +ultimatum +ultimatums +ultra +ultraconservative +ultramodern +ultranationalist +ultras +ultrasonic +ultrasound +ultrasounds +ultraviolet +ulysses +um +umar +umberto +umbilical +umbra +umbrage +umbrella +umbrellas +umbrian +umlauts +umm +ump +umpire +umpires +umpiring +umpteen +umpteenth +un +una +unabashed +unabashedly +unabated +unable +unabridged +unacceptable +unacceptably +unaccepted +unaccompanied +unaccountable +unaccountably +unaccounted +unaccredited +unaccustomed +unaddressed +unadjusted +unadorned +unadulterated +unadventurous +unadvertised +unaffected +unaffiliated +unaffordable +unafraid +unaided +unalienable +unallocated +unalloyed +unalterably +unaltered +unambiguous +unambiguously +unamerican +unanimity +unanimous +unanimously +unannounced +unanswerable +unanswered +unanticipated +unapologetic +unapologetically +unappealing +unappetizing +unappreciated +unapproved +unarguably +unarmed +unashamed +unashamedly +unassailable +unassigned +unassisted +unassociated +unassuming +unattached +unattainable +unattended +unattractive +unattributed +unaudited +unauthorized +unavailability +unavailable +unavailing +unavoidable +unavoidably +unaware +unawares +unbalance +unbalanced +unbearable +unbearably +unbeatable +unbeaten +unbecoming +unbeknown +unbeknownst +unbelief +unbelievable +unbelievably +unbeliever +unbelievers +unbelieving +unbending +unbiased +unbidden +unbilled +unbleached +unblemished +unblocked +unbolted +unborn +unbound +unbounded +unbowed +unbranded +unbreakable +unbridled +unbroken +unbudgeted +unbuilt +unbundled +unbundling +unburden +unburdened +unburned +unbutton +unbuttoned +uncalled +uncannily +uncanny +uncapped +uncaring +unceasing +unceasingly +uncensored +unceremonious +unceremoniously +uncertain +uncertainly +uncertainties +uncertainty +unchallenged +unchangeable +unchanged +unchanging +uncharacteristic +uncharacteristically +uncharged +uncharitable +uncharted +unchartered +unchecked +uncivil +uncivilized +unclaimed +unclassified +uncle +unclean +uncleaned +unclear +uncles +uncluttered +uncoated +uncoiled +uncollected +uncollectible +uncomfortable +uncomfortably +uncommitted +uncommon +uncommonly +uncompensated +uncompetitive +uncompleted +uncomplicated +uncompressed +uncompromised +uncompromising +uncompromisingly +unconcealed +unconcern +unconcerned +unconditional +unconditionally +unconfined +unconfirmed +uncongested +unconnected +unconscionable +unconscionably +unconscious +unconsciously +unconsciousness +unconsolidated +unconstitutional +unconstitutionality +unconstitutionally +unconstrained +uncontained +uncontaminated +uncontested +uncontrollable +uncontrollably +uncontrolled +uncontroversial +unconventional +unconverted +unconvinced +unconvincing +uncooked +uncool +uncooperative +uncoordinated +uncork +uncorked +uncorks +uncorrected +uncorroborated +uncorrupted +uncounted +uncouple +uncoupled +uncoupling +uncouth +uncover +uncovered +uncovering +uncovers +uncritical +uncritically +uncrowded +unction +unctuous +uncultured +uncut +und +undamaged +undated +undaunted +unde +undecided +undecideds +undeclared +undefeated +undefinable +undefined +undeliverable +undelivered +undemanding +undemocratic +undeniable +undeniably +under +underachieved +underachiever +underachievers +underachieving +underage +underarm +underbelly +underbid +underbrush +undercarriage +underclass +underclassmen +undercoat +undercooked +undercover +undercurrent +undercurrents +undercut +undercuts +undercutting +underdeveloped +underdevelopment +underdog +underdogs +underdown +undereducated +underemployed +underemployment +underestimate +underestimated +underestimates +underestimating +underestimation +underfoot +underfunding +undergarment +undergarments +undergirding +undergo +undergoes +undergoing +undergone +undergrad +undergrads +undergraduate +undergraduates +underground +undergrowth +underhanded +underhill +underinsured +underlie +underlies +underline +underlined +underlines +underling +underlings +underlining +underlying +undermanned +undermine +undermined +undermines +undermining +underneath +undernourished +underpaid +underpants +underpass +underpaying +underpayment +underperform +underperformed +underperforming +underpin +underpinned +underpinning +underpinnings +underpins +underplay +underplayed +underpowered +underpriced +underpricing +underrate +underrated +underreporting +underrepresented +unders +underscore +underscored +underscores +underscoring +undersea +undersecretary +undersell +underselling +underserved +undershirt +underside +undersigned +undersized +undersold +understaffed +understaffing +understand +understandable +understandably +understanding +understandings +understands +understate +understated +understatement +understates +understating +understood +understudy +undersupply +undertake +undertaken +undertaker +undertakers +undertakes +undertaking +undertakings +undertone +undertones +undertook +undertow +underused +underutilized +undervalue +undervalued +undervalues +undervaluing +underwater +underway +underwear +underweight +underwent +underwhelming +underwood +underworld +underwrite +underwriter +underwriters +underwrites +underwriting +underwritten +underwrote +undeserved +undeserving +undesirability +undesirable +undesired +undetectable +undetected +undetermined +undeterred +undeveloped +undiagnosed +undid +undies +undifferentiated +undigested +undignified +undiluted +undiminished +undimmed +undine +undiplomatic +undisciplined +undisclosed +undiscovered +undiscriminating +undisguised +undisputable +undisputed +undistinguished +undistributed +undisturbed +undivided +undo +undocumented +undoing +undone +undoubtably +undoubted +undoubtedly +undreamed +undress +undressed +undressing +undue +undulating +undulations +unduly +undyed +undying +unearned +unearth +unearthed +unearthing +unearthly +unearths +unease +uneasily +uneasiness +uneasy +uneconomical +unedited +uneducated +unelectable +unelected +unemotional +unemployable +unemployed +unemployment +unencrypted +unencumbered +unending +unenforceable +unenlightened +unenthusiastic +unenviable +unequal +unequipped +unequivocal +unequivocally +unerring +unesco +unescorted +unethical +unethically +uneven +unevenly +unevenness +uneventful +unexcavated +unexceptional +unexciting +unexecuted +unexercised +unexpected +unexpectedly +unexpired +unexplainable +unexplained +unexploded +unexploited +unexplored +unfailing +unfailingly +unfair +unfairly +unfairness +unfaithful +unfamiliar +unfamiliarity +unfashionable +unfathomable +unfavorable +unfazed +unfeasible +unfeeling +unfenced +unfettered +unfiled +unfilled +unfiltered +unfinished +unfit +unfitness +unflagging +unflappable +unflattering +unflinching +unflinchingly +unfocused +unfold +unfolded +unfolding +unfolds +unforced +unforeseeable +unforeseen +unforgettable +unforgivable +unforgiven +unforgiving +unformed +unfortified +unfortunate +unfortunately +unfortunates +unfounded +unfree +unfreeze +unfriendly +unfrozen +unfulfilled +unfunded +unfurl +unfurled +unfurling +ung +ungainly +ungar +unger +unglamorous +unglued +ungodly +ungovernable +ungrateful +ungrounded +unguarded +unhampered +unhappily +unhappiness +unhappy +unharmed +unhealthful +unhealthy +unheard +unheated +unheeded +unhelpful +unheralded +unhesitatingly +unhindered +unhinge +unhinged +unhip +unholy +unhook +unhooked +unhurried +unhurt +uni +unicef +unicorn +unicorns +unicycle +unidentifiable +unidentified +unification +unified +unifier +uniform +uniformed +uniformity +uniformly +uniforms +unify +unifying +unilateral +unilateralism +unilaterally +unilever +unimaginable +unimaginably +unimaginative +unimagined +unimpaired +unimpeachable +unimpeded +unimportant +unimpressed +unimpressive +unimproved +unincorporated +uninfected +uninformative +uninformed +uninhabitable +uninhabited +uninhibited +uninitiated +uninjured +uninspired +uninspiring +uninsured +unintelligible +unintended +unintentional +unintentionally +uninterested +uninteresting +uninterrupted +uninterruptible +uninvited +uninviting +uninvolved +union +unionism +unionist +unionists +unionization +unionize +unionized +unionizing +unions +unique +uniquely +uniqueness +unisex +unison +unisys +unit +unitarian +unitarians +unitary +unite +united +unites +uniting +units +unity +univ +univeristy +universal +universalist +universality +universally +universe +universes +universite +universities +university +univision +unix +unjust +unjustifiable +unjustifiably +unjustified +unjustly +unkempt +unkind +unknowable +unknowing +unknowingly +unknown +unknowns +unlabeled +unlawful +unlawfully +unleaded +unlearn +unlearned +unleash +unleashed +unleashes +unleashing +unless +unlicensed +unlike +unlikeliest +unlikely +unlimited +unlined +unlisted +unlit +unlivable +unload +unloaded +unloading +unloads +unlock +unlocked +unlocking +unlocks +unloved +unloving +unlucky +unmade +unmanageable +unmanaged +unmanned +unmapped +unmarked +unmarried +unmask +unmasked +unmatched +unmeasured +unmentionable +unmentioned +unmet +unmistakable +unmistakably +unmitigated +unmixed +unmodified +unmolested +unmonitored +unmoved +unnamed +unnatural +unnaturally +unneccessary +unnecessarily +unnecessary +unneeded +unnerve +unnerved +unnerving +unnoticed +unobservable +unobstructed +unobtainable +unobtrusive +unocal +unoccupied +unofficial +unofficially +unopened +unopposed +unorganized +unorthodox +unpack +unpacked +unpacking +unpaid +unpainted +unpalatable +unparalleled +unpasteurized +unpatriotic +unpayable +unpersuaded +unpersuasive +unperturbed +unplanned +unplayable +unpleasant +unpleasantly +unpleasantness +unplug +unplugged +unplugging +unpopular +unpopularity +unpopulated +unprecedented +unprecedentedly +unpredictability +unpredictable +unpredictably +unprepared +unpreparedness +unpressurized +unpretentious +unprincipled +unprintable +unprivileged +unprocessed +unproduced +unproductive +unprofessional +unprofitable +unpromising +unprompted +unprotected +unprovable +unproved +unproven +unprovoked +unpublicized +unpublished +unpunished +unqualified +unquantified +unquestionable +unquestionably +unquestioned +unquestioning +unquestioningly +unquote +unrated +unravel +unraveling +unravelled +unravelling +unravels +unreachable +unread +unreadable +unreal +unrealistic +unrealistically +unreality +unrealizable +unrealized +unreasonable +unreasonably +unreasoning +unrecognizable +unrecognized +unreconciled +unreconstructed +unrecorded +unrecoverable +unrecovered +unredeemed +unrefined +unreformed +unrefuted +unregenerate +unregistered +unregulated +unrehearsed +unrelated +unreleased +unrelenting +unrelentingly +unreliability +unreliable +unrelieved +unremarkable +unremarked +unremitting +unrepentant +unreported +unrepresentative +unrepresented +unrequited +unreserved +unreservedly +unresolved +unresponsive +unresponsiveness +unrest +unrestrained +unrestricted +unreviewed +unrevised +unrivaled +unruffled +unruh +unruly +unsafe +unsaid +unsanctioned +unsanitary +unsatisfactory +unsatisfied +unsatisfying +unsaturated +unscathed +unscheduled +unschooled +unscientific +unscramble +unscrambled +unscrambling +unscreened +unscrew +unscrewed +unscripted +unscrupulous +unsealed +unseasonable +unseasonably +unseasoned +unseat +unseated +unseating +unsecure +unsecured +unseemly +unseen +unselfish +unselfishly +unselfishness +unsentimental +unserved +unserviceable +unsettle +unsettled +unsettling +unshackled +unshakable +unshaken +unshared +unshaven +unshielded +unsightly +unsigned +unsinkable +unskilled +unsmiling +unsold +unsolicited +unsolvable +unsolved +unsophisticated +unsound +unsparing +unspeakable +unspeakably +unspecific +unspecified +unspectacular +unspent +unspoiled +unspoken +unsportsmanlike +unstable +unstained +unstated +unsteady +unstinting +unstoppable +unstructured +unstuck +unsubscribe +unsubscribed +unsubsidized +unsubstantiated +unsubtle +unsuccessful +unsuccessfully +unsuitable +unsuited +unsullied +unsung +unsupervised +unsupportable +unsupported +unsure +unsurpassed +unsurprising +unsurprisingly +unsuspected +unsuspecting +unsustainable +unsustainably +unswayed +unsweetened +unswerving +unsympathetic +untainted +untalented +untamed +untangle +untangling +untapped +untarred +untaxed +untenable +untended +untermeyer +untested +untethered +unthinkable +unthinking +unthinkingly +untidy +untie +untied +until +untill +untimely +untiring +untitled +unto +untold +untouchability +untouchable +untouchables +untouched +untoward +untraceable +untrained +untrammeled +untreatable +untreated +untried +untroubled +untrue +untrustworthiness +untrustworthy +untruth +untruthful +untruths +untucked +unturned +untypical +unum +unusable +unused +unusual +unusually +unvaccinated +unvarnished +unveil +unveiled +unveiling +unveils +unverifiable +unverified +unwanted +unwarranted +unwary +unwashed +unwatched +unwavering +unwed +unweighted +unwelcome +unwelcomed +unwieldy +unwilling +unwillingly +unwillingness +unwin +unwind +unwinding +unwinds +unwinnable +unwired +unwise +unwisely +unwitting +unwittingly +unworkable +unworthy +unwound +unwrap +unwrapped +unwrapping +unwraps +unwritten +unyielding +unzip +unzipped +up +upbeat +upbraid +upbraided +upbringing +upcoming +update +updated +updates +updating +updike +updraft +updrafts +upending +upfront +upgrade +upgraded +upgrades +upgrading +upham +upheaval +upheavals +upheld +uphill +uphold +upholding +upholds +upholstered +upholstery +upjohn +upkeep +upland +uplands +uplift +uplifted +uplifting +uplink +upload +uploaded +uploading +upon +upp +upped +upper +uppercase +uppermost +uppers +upping +uppity +uppsala +upright +uprightness +uprights +uprising +uprisings +upriver +uproar +uproot +uprooted +uprooting +ups +upscale +upset +upsets +upsetting +upshaw +upshot +upside +upsides +upsilon +upstage +upstaged +upstaging +upstairs +upstanding +upstart +upstarts +upstate +upstream +upsurge +upswing +upswings +uptake +uptick +uptight +upto +upton +uptown +upturn +upward +upwardly +upwards +upwind +ur +ural +urals +uranium +uranus +urbain +urban +urbana +urbane +urbanism +urbanites +urbanization +urbanized +urbina +urchin +urchins +urdu +ure +urea +uremic +urethane +urethra +urge +urged +urgency +urgent +urgently +urges +urging +urgings +uri +uriarte +uribe +uric +uriel +urinal +urinals +urinalysis +urinary +urinate +urinated +urinating +urine +urn +urns +urokinase +urological +urologist +urologists +urology +urquhart +ursa +ursula +ursus +uruguay +uruguayan +us +usa +usability +usable +usaf +usage +usages +usaid +use +useable +useage +used +useful +usefully +usefulness +useless +usenet +user +users +uses +usgs +usher +ushered +ushering +ushers +using +usinor +ust +usted +ustinov +usu +usual +usually +usurp +usurpation +usurped +usurping +usurps +usury +ususally +ut +uta +utah +ute +utensil +utensils +uterine +utero +uterus +utica +util +utilitarian +utilities +utility +utilization +utilize +utilized +utilizes +utilizing +utils +utley +utmost +utopia +utopian +utopians +utopias +utrecht +uts +utter +utterance +utterances +uttered +uttering +utterly +utters +uva +uwe +uxbridge +uzbek +uzbekistan +uzbeks +uzi +uzis +va +vac +vaca +vacancies +vacancy +vacant +vacate +vacated +vacates +vacating +vacation +vacationed +vacationers +vacationing +vacations +vacaville +vaccinate +vaccinated +vaccinating +vaccination +vaccinations +vaccine +vaccines +vaccinia +vaccum +vaccuum +vachon +vacillate +vacillated +vacillating +vacillation +vaclav +vacuity +vacuous +vacuum +vacuuming +vacuums +vader +vadim +vadis +vagabond +vagabonds +vagaries +vagina +vaginal +vaginitis +vagrants +vague +vaguely +vagueness +vaguer +vaguest +vagus +vail +vain +vainly +val +valderrama +valdez +vale +valedictorian +valedictory +valence +valencia +valens +valent +valente +valenti +valentin +valentina +valentine +valentines +valentino +valenzuela +valeo +valeri +valerian +valerie +valerio +valero +valery +vales +valet +valhalla +valiant +valiantly +valid +validate +validated +validates +validating +validation +validity +validly +valin +valium +valk +valle +vallee +vallejo +valley +valleys +vallone +valmont +valois +valor +valorem +valparaiso +vals +valuable +valuables +valuation +valuations +value +valued +valueless +values +valuing +valve +valverde +valves +vamos +vamp +vamped +vamping +vampire +vampires +vamps +van +vanadium +vance +vancomycin +vancouver +vanda +vandal +vandalism +vandalize +vandalized +vandalizing +vandals +vandenberg +vander +vanderbilt +vandross +vandyke +vane +vanes +vanessa +vang +vanguard +vanier +vanilla +vanillin +vanish +vanished +vanishes +vanishing +vanishingly +vanities +vanity +vann +vanquish +vanquished +vans +vantage +vantages +vanuatu +vanunu +vanya +vapid +vapor +vaporization +vaporize +vaporized +vaporizer +vaporizes +vaporizing +vapors +vapour +var +vara +varco +varga +vargas +variability +variable +variables +varian +variance +variances +variant +variants +variation +variations +varicose +varied +varies +varietal +varietals +varieties +variety +various +variously +varley +varmint +varney +varnish +varnished +varnishes +vars +varsity +vary +varying +vas +vasa +vasco +vascular +vase +vasectomies +vasectomy +vaseline +vases +vasquez +vassal +vassals +vassar +vast +vastly +vastness +vat +vatican +vats +vaudeville +vaudevillian +vaughan +vaughn +vault +vaulted +vaulter +vaulting +vaults +vaunted +vaux +vauxhall +vax +vazquez +ve +veal +veale +vecchio +vector +vectored +vectors +vectra +veda +vedder +vedic +vee +veep +veer +veered +veering +veers +vega +vegan +vegans +vegas +vegetable +vegetables +vegetal +vegetarian +vegetarianism +vegetarians +vegetate +vegetating +vegetation +vegetative +veggie +veggies +vehemence +vehement +vehemently +vehicle +vehicles +vehicular +veil +veiled +veiling +veils +vein +veins +veitch +vela +velarde +velasco +velasquez +velcro +velez +velma +velo +velocities +velocity +velodrome +veloso +velour +velvet +velveteen +velvets +velvety +vena +venable +venal +venality +vender +vendetta +vending +vendor +vendors +veneer +venerable +venerate +venerated +veneration +venereal +venetian +venezia +venezuela +venezuelan +vengeance +vengeful +venial +venice +venise +venison +venom +venomous +venous +vent +ventana +vented +venter +ventilate +ventilated +ventilating +ventilation +ventilator +venting +vento +ventral +ventre +ventricular +ventriloquist +vents +ventura +venture +ventured +venturer +venturers +ventures +venturesome +venturi +venturing +venue +venues +venus +ver +vera +veracity +veranda +verandah +verandas +verb +verbal +verbalize +verbally +verbatim +verbena +verbiage +verbose +verbosity +verboten +verbs +verbum +verdant +verde +verdes +verdi +verdict +verdicts +verdugo +verdun +vergara +verge +verges +verging +verhoeven +verifiable +verification +verifications +verified +verifier +verifiers +verifies +verify +verifying +verily +verisimilitude +veritable +veritas +verities +verity +verlag +verma +vermeer +vermeil +vermilion +vermillion +vermin +vermont +vermonter +vermonters +vermouth +vern +vernacular +vernal +verne +verner +vernier +vernon +vernor +vero +veron +verona +veronica +veronique +veronis +verre +verry +vers +versa +versace +versailles +versatile +versatility +verse +versed +verses +version +versions +verso +versus +vert +vertebra +vertebrae +vertebral +vertebrate +vertebrates +vertex +vertical +vertically +verticals +vertiginous +vertigo +verts +verve +very +vesey +vespa +vesper +vespers +vessel +vessels +vessey +vest +vesta +vestal +vested +vester +vestibule +vestige +vestiges +vestigial +vesting +vestment +vestments +vests +vesuvius +vet +veta +vetch +veteran +veterans +veterinarian +veterinarians +veterinary +veto +vetoed +vetoes +vetoing +vets +vette +vetted +vetting +veuve +vex +vexation +vexatious +vexed +vexing +vey +vi +via +viability +viable +viacheslav +viacom +viaduct +vial +vials +viator +vibe +vibes +vibrance +vibrancy +vibrant +vibrantly +vibrate +vibrates +vibrating +vibration +vibrational +vibrations +vibrato +vibrator +vibrio +vic +vicar +vicarious +vicariously +vicars +vice +vicente +viceroy +vices +vichy +vici +vicinity +vicious +viciously +viciousness +vicissitudes +vick +vickers +vickery +vicki +vickie +vicks +vicksburg +vicky +vicomte +victim +victimhood +victimization +victimize +victimized +victimizing +victimless +victims +victor +victoria +victorian +victorians +victorias +victories +victorious +victors +victory +vicuna +vida +vidal +vidalia +vide +video +videocassette +videocassettes +videoconference +videoconferencing +videophone +videos +videotape +videotaped +videotapes +videotaping +vides +vidor +vie +vied +vieira +vienna +viennese +vier +vies +viet +vietcong +vietnam +vietnamese +view +viewable +viewed +viewer +viewers +viewership +viewfinder +viewing +viewings +viewpoint +viewpoints +views +vig +vigil +vigilance +vigilant +vigilante +vigilantes +vigilantism +vigilantly +vigils +vigna +vignette +vignettes +vigo +vigor +vigorous +vigorously +viii +vijay +vik +viking +vikings +viktor +vila +vilas +vile +vilest +vilification +vilified +vilify +vill +villa +village +villager +villagers +villages +villain +villainous +villains +villainy +villanova +villanueva +villar +villas +ville +villers +villette +villi +villiers +vilnius +vim +vin +vina +vinaigrette +vinal +vinay +vinca +vince +vincent +vincente +vincenzo +vinci +vindicate +vindicated +vindicates +vindication +vindictive +vindictiveness +vine +vinegar +vinegars +vineland +viner +vines +vineyard +vineyards +vinifera +vining +vinnie +vinny +vino +vinod +vinson +vint +vintage +vintages +vintner +vintners +vinyl +viola +violate +violated +violates +violating +violation +violations +violator +violators +violence +violent +violently +violet +violets +violette +violin +violinist +violinists +violins +violist +vip +viper +vipers +vips +virago +viral +virden +vires +virga +virgen +virgil +virgilio +virgin +virginal +virginia +virginian +virginians +virginity +virgins +virgo +virile +virility +virological +virologists +virology +virtual +virtually +virtue +virtues +virtuosi +virtuosity +virtuoso +virtuosos +virtuous +virtus +virulence +virulent +virulently +virus +viruses +vis +visa +visage +visas +visceral +viscerally +visconti +viscosity +viscount +viscous +vise +vishnu +visibility +visible +visibly +visigoths +vision +visionaries +visionary +visions +visit +visitation +visitations +visited +visiting +visitor +visitors +visits +visor +visors +visser +vista +vistas +visual +visualization +visualizations +visualize +visualized +visualizing +visually +visuals +vita +vitae +vital +vitale +vitali +vitality +vitally +vitals +vitaly +vitamin +vitamins +vitek +vitesse +viticultural +vitiligo +vito +vitra +vitreous +vitriol +vitriolic +vitro +vitry +vittles +vittoria +vittorio +vituperation +vituperative +viva +vivacious +vivant +vive +vivendi +vivian +vivid +vividly +vividness +vivien +vivienne +vivisection +vivo +vivre +vixen +viz +vizcaya +vl +vlad +vladimir +vladivostok +vo +voc +vocabularies +vocabulary +vocal +vocalist +vocalists +vocally +vocals +vocation +vocational +vocations +voce +vociferous +vociferously +vocoder +vocs +vodafone +vodka +vodkas +vogel +vogelstein +vogl +vogler +vogt +vogue +voice +voiced +voiceless +voicemail +voices +voicing +void +voidable +voided +voiding +voids +voight +voigt +voila +voile +voiles +voinovich +voir +vol +volante +volatile +volatility +volcanic +volcano +volcanoes +volcanos +volcker +voles +volga +volition +volitional +volk +volker +volkswagen +volkswagens +volley +volleyball +volleyed +volleys +volmer +volpe +vols +volt +volta +voltage +voltages +voltaire +volte +volts +volubility +voluble +volume +volumes +volumetric +voluminous +voluntarily +voluntariness +voluntarism +voluntary +volunteer +volunteered +volunteering +volunteerism +volunteers +voluptuous +volvo +volvos +volz +vomit +vomited +vomiting +von +vonnegut +vons +vontobel +voodoo +voorhees +voracious +voracity +vortex +vos +voss +votaries +vote +voted +voter +voters +votes +voting +vouch +vouched +voucher +vouchers +vouching +vous +vow +vowed +vowel +vowels +vowing +vows +vox +voyage +voyager +voyagers +voyages +voyeur +voyeurism +voyeuristic +vremya +vries +vroom +vs +vt +vu +vue +vuitton +vulcan +vulcans +vulgar +vulgarities +vulgarity +vulgate +vulnerabilities +vulnerability +vulnerable +vulture +vultures +vulva +vv +vyacheslav +vying +wa +wabash +wac +wachovia +wachs +wachtel +wachter +wack +wacked +wacker +wackier +wackiest +wackiness +wacko +wackos +wacks +wacky +waco +wad +wada +wadded +waddell +waddington +waddle +waddles +waddy +wade +waded +waders +wades +wadi +wading +wads +wadsworth +wafer +wafers +waffle +waffled +waffles +waffling +waft +wafted +wafting +wag +wage +waged +wagenaar +wagener +wager +wagered +wagering +wagers +wages +wagged +wagging +waggling +waggoner +waging +wagner +wagnerian +wagon +wagoner +wagons +wags +wagstaff +wah +wahhabi +wahid +wahlberg +wahoo +waif +waikiki +wail +wailed +wailing +wails +wain +wainwright +wais +waist +waistband +waistcoat +waistline +waists +wait +waite +waited +waiter +waiters +waiting +waitress +waitresses +waits +waive +waived +waiver +waivers +waives +waiving +wake +wakefield +wakefulness +wakeman +waken +wakes +wakeup +waking +wako +wal +walcott +wald +waldemar +walden +walder +waldman +waldo +waldorf +waldron +waldrop +wales +walesa +walford +wali +walid +walk +walked +walken +walker +walkers +walkie +walking +walkman +walkout +walkouts +walks +walkup +walkway +walkways +wall +walla +wallace +wallach +wallboard +walled +wallenberg +waller +wallerstein +wallet +wallets +walleye +wallflower +wallflowers +walling +wallingford +wallis +wallman +wallop +walloping +wallops +wallow +wallowed +wallowing +wallows +wallpaper +wallpapering +wallpapers +walls +wallsend +wally +walmart +walnut +walnuts +walpole +walrus +walruses +walsh +walston +walt +walter +walters +waltham +walther +walton +waltz +waltzer +waltzes +waltzing +walworth +walz +walzer +wamsley +wan +wanamaker +wand +wanda +wander +wandered +wanderer +wanderers +wandering +wanderings +wanderlust +wanders +wands +wane +waned +wanes +wang +wanger +wangle +wangled +waning +wank +wanker +wanna +wannabe +wannabes +wanner +want +wanted +wanting +wanton +wantonly +wants +wapping +war +warble +warbler +warblers +warbling +warburg +warburton +ward +warded +wardell +warden +wardens +warder +warding +wardrobe +wardrobes +wardrop +wards +ware +warehouse +warehoused +warehouses +warehousing +wares +warf +warfare +warfield +wargames +warhead +warheads +warhol +warhorse +warily +wariness +waring +wark +warlike +warlock +warlord +warlords +warm +warmed +warmer +warmers +warmest +warming +warmly +warmonger +warmongering +warmongers +warms +warmth +warmup +warn +warned +warner +warners +warning +warnings +warnock +warns +warp +warpath +warped +warping +warplane +warplanes +warps +warrant +warranted +warrantee +warranties +warranting +warrantless +warrants +warranty +warren +warrens +warrick +warriner +warring +warrington +warrior +warriors +wars +warsaw +warship +warships +wart +wartime +warts +warwick +wary +was +wasabi +wasatch +wash +washable +washboard +washburn +washcloth +washed +washer +washers +washes +washing +washington +washingtonian +washingtonians +washoe +washout +washouts +washroom +washtub +washy +wasn +wasnt +wasp +wasps +wass +wasserman +wasserstein +wast +waste +wastebasket +wasted +wasteful +wastefully +wastefulness +wasteland +wastelands +wastepaper +waster +wasters +wastes +wastewater +wasting +wastrel +wat +watanabe +watch +watchdog +watchdogs +watched +watcher +watchers +watches +watchful +watching +watchmaker +watchman +watchword +water +waterbed +waterborne +waterbury +watercolor +watercolors +watercourse +watercraft +watercress +watered +waterfall +waterfalls +waterford +waterfowl +waterfront +watergate +waterhouse +watering +waterings +waterlily +waterline +waterlogged +waterloo +waterman +watermark +watermelon +watermelons +waterproof +waterproofing +waters +watershed +watersheds +waterside +waterskiing +waterson +waterston +watertight +watertown +waterway +waterways +waterworks +watery +watford +wath +watkins +wats +watson +watsonville +watt +wattage +watteau +watters +wattle +wattles +watts +waugh +wave +waved +waveform +wavelength +wavelengths +waver +wavered +wavering +waverly +wavers +waves +waving +wavy +wax +waxed +waxes +waxing +waxman +waxworks +waxy +way +waylaid +wayland +waylay +waylon +wayman +wayne +waynesboro +ways +wayside +wayward +wazir +wc +wd +we +weak +weaken +weakened +weakening +weakens +weaker +weakest +weakling +weaklings +weakly +weakness +weaknesses +wealth +wealthier +wealthiest +wealthy +wean +weaned +weaning +weapon +weaponry +weapons +wear +wearable +wearer +wearers +wearied +wearily +weariness +wearing +wearisome +wears +weary +wearying +weasel +weasels +weather +weatherby +weathered +weatherford +weatherhead +weathering +weatherly +weatherman +weathermen +weatherproof +weathers +weatherspoon +weatherstripping +weave +weaved +weaver +weavers +weaves +weaving +web +webb +webbed +webber +webbing +weber +webs +webster +websters +wechsler +wed +wedded +wedding +weddings +wedel +wedge +wedged +wedges +wedgewood +wedgwood +wedlock +wednesday +wednesdays +weds +wee +weed +weeded +weeding +weeds +weedy +week +weekday +weekdays +weekend +weekender +weekends +weekley +weeklies +weeklong +weekly +weeknight +weeknights +weeks +weems +ween +weenie +weenies +weeny +weep +weeping +weeps +weepy +weet +weevil +weevils +wegner +wehner +wei +weidenfeld +weider +weigel +weigh +weighed +weighing +weighs +weight +weighted +weightier +weighting +weightings +weightless +weightlessness +weightlifter +weightlifting +weights +weighty +weil +weiler +weill +weimar +wein +weinberg +weinberger +weiner +weingarten +weinreich +weinstein +weinstock +weintraub +weir +weird +weirder +weirdest +weirdly +weirdness +weirdo +weirdos +weis +weisberg +weisbrod +weiser +weiss +weissberg +weissman +weitz +weitzman +weizmann +welby +welch +welcome +welcomed +welcomes +welcoming +weld +welded +welder +welders +welding +weldon +welds +welfare +welk +welker +well +wellbeing +wellcome +weller +welles +wellesley +wellfleet +wellhead +welling +wellington +wellman +wellness +wellpoint +wells +wellspring +wellstone +welsh +welt +weltanschauung +welte +welter +welterweight +welts +welty +wembley +wen +wenatchee +wenches +wend +wende +wendel +wendell +wending +wends +wendt +wendy +wenger +wennberg +wenner +went +wente +wentworth +wenzel +wept +were +werewolf +werner +wert +werth +wertheim +wertheimer +wertz +wes +wesley +wesleyan +wessel +wessels +wesson +west +westboro +westborough +westbound +westbrook +westbury +westchester +westcott +westdeutsche +wester +westergaard +westerly +western +westerner +westerners +westernization +westernized +westernmost +westerns +westfall +westfield +westford +westgate +westin +westinghouse +westlake +westland +westley +westminster +westmoreland +weston +westphal +westphalia +westport +westside +westward +westwards +westwood +wet +wether +wetland +wetlands +wetness +wets +wetsuit +wetter +wettest +wetting +wetzel +wexler +weyerhaeuser +weymouth +weyrich +wf +wh +wha +whack +whacked +whacker +whacking +whacko +whacks +whacky +whale +whalen +whaler +whalers +whales +whaley +whaling +wham +whammy +whan +whang +wharf +wharton +what +whatcha +whatever +whatnot +whats +whatsoever +wheat +wheaties +wheatley +wheaton +whee +wheel +wheelbarrow +wheelbarrows +wheelbase +wheelchair +wheelchairs +wheeled +wheeler +wheelers +wheelie +wheelies +wheeling +wheelock +wheels +wheelwright +wheeze +wheezes +wheezing +wheezy +whelan +when +whence +whenever +where +whereabouts +whereas +whereby +wherefore +wherefores +wherein +whereof +whereupon +wherever +wherewithal +wherry +whet +whether +whets +whetstone +whetted +whew +whey +which +whichever +whiff +whiffed +whig +whigs +while +whilst +whim +whimper +whimpering +whimpers +whims +whimsical +whimsy +whine +whined +whiner +whiners +whines +whiney +whining +whiny +whip +whiplash +whipped +whippersnapper +whipping +whipple +whippoorwill +whips +whir +whirl +whirled +whirling +whirlpool +whirlpools +whirlwind +whirlwinds +whirring +whisk +whisked +whisker +whiskers +whiskey +whiskeys +whisks +whisky +whisper +whispered +whispering +whispers +whistle +whistleblower +whistleblowers +whistled +whistler +whistlers +whistles +whistling +whit +whitaker +whitbread +whitcomb +white +whitecaps +whited +whitefish +whitehall +whitehead +whitehorn +whitehorse +whitehouse +whitehurst +whiteley +whitely +whiteman +whiten +whitener +whiteness +whitening +whiter +whites +whiteside +whitest +whitestone +whitewash +whitewashed +whitewashing +whitewater +whitey +whitfield +whitford +whither +whiting +whitish +whitley +whitlock +whitlow +whitman +whitmore +whitney +whittaker +whittemore +whitten +whittier +whittington +whittle +whittled +whittles +whittling +whitton +whitty +whitworth +whiz +whizzed +whizzes +whizzing +who +whoa +whodunit +whoever +whole +wholehearted +wholeheartedly +wholeness +wholes +wholesale +wholesaler +wholesalers +wholesaling +wholesome +wholesomeness +wholly +whom +whomever +whomsoever +whoo +whoop +whooped +whoopee +whoopi +whoopie +whooping +whoops +whoosh +whopper +whoppers +whopping +whore +whorehouse +whores +whorl +whorls +whose +whosoever +whup +why +whys +whyte +wi +wicca +wice +wich +wichita +wick +wicked +wickedest +wickedly +wickedness +wicker +wicket +wickets +wicking +wicks +wide +widebody +widely +widen +widened +widener +widening +widens +wider +wides +widespread +widest +widget +widgets +widmann +widow +widowed +widower +widowers +widowhood +widows +width +widths +wieck +wieder +wiedersehen +wiegand +wield +wielded +wielder +wielding +wields +wiemer +wien +wiener +wieners +wier +wierd +wiesbaden +wiesel +wiesenthal +wiest +wife +wig +wiggin +wiggins +wiggle +wiggles +wigglesworth +wiggling +wiggly +wiggs +wight +wigs +wigwam +wil +wilber +wilbur +wilco +wilcox +wild +wildcard +wildcat +wildcats +wildcatting +wilde +wildebeest +wilder +wilderness +wildest +wildfire +wildfires +wildflower +wildflowers +wilding +wildlife +wildly +wildman +wildness +wilds +wildwood +wile +wiles +wiley +wilford +wilfred +wilfrid +wilful +wilfully +wilhelm +wilhelmina +wiling +wilk +wilke +wilken +wilkerson +wilkes +wilkie +wilkin +wilkins +wilkinson +wilks +will +willa +willamette +willard +willed +willem +willes +willet +willett +willey +willful +willfully +willi +william +williams +williamsburg +williamson +williamstown +willie +willies +willig +willing +willinger +willingham +willingly +willingness +willis +williston +willmore +willner +willoughby +willow +willowbrook +willows +willowy +willpower +wills +willson +willy +wilma +wilmer +wilmington +wilmot +wilmott +wilshire +wilson +wilt +wilted +wilting +wilton +wilts +wiltshire +wily +wim +wimberley +wimbledon +wimmer +wimp +wimps +wimpy +win +winbush +wince +winced +winch +winchester +wincing +wind +windblown +windbreaker +windchill +winded +winder +windfall +windfalls +windham +windiest +winding +windjammer +windle +windmill +windmills +window +windowed +windowing +windowless +windowpane +windowpanes +windows +windowsill +winds +windshield +windshields +windsor +windstar +windstorm +windsurf +windswept +windup +windward +windy +wine +wined +wineglass +wineglasses +winemaker +winemaking +winer +wineries +winery +wines +winey +winfield +winfree +winfrey +wing +wingate +wingback +winged +winger +wingers +wingert +wingfield +winging +wingless +wingman +wings +wingspan +winifred +wining +wink +winked +winking +winkle +winkler +winks +winless +winn +winnable +winnebago +winner +winners +winnick +winnie +winning +winnings +winnipeg +winnow +winnowed +winnowing +winny +wino +winograd +winokur +winona +winos +wins +winship +winslow +winsome +winsor +winston +wint +winter +wintergreen +winterized +winters +winterthur +wintertime +winthrop +winton +wintry +winwood +wipe +wiped +wipeout +wiper +wipers +wipes +wiping +wir +wire +wired +wireless +wireline +wires +wiretap +wiretapped +wiretapping +wiretaps +wiring +wirth +wirtz +wiry +wis +wisc +wisconsin +wisdom +wise +wisecrack +wisecracking +wisecracks +wised +wiseguy +wisely +wiseman +wiser +wisest +wish +wishbone +wished +wisher +wishers +wishes +wishful +wishing +wishy +wisner +wisp +wisps +wispy +wisteria +wistful +wistfully +wistfulness +wit +witch +witchcraft +witcher +witches +witching +witchy +with +witham +withdraw +withdrawal +withdrawals +withdrawing +withdrawl +withdrawn +withdraws +withdrew +wither +withered +withering +withers +witherspoon +withheld +withhold +withholding +withholds +within +without +withstand +withstanding +withstands +withstood +witkin +witless +witness +witnessed +witnesses +witnessing +witney +wits +witt +witted +wittenberg +witter +wittgenstein +witticisms +wittiest +wittig +wittily +witting +wittingly +wittman +witty +wives +wiz +wizard +wizardry +wizards +wizened +wm +wo +wobble +wobbled +wobbles +wobbling +wobbly +woburn +woe +woebegone +woeful +woefully +woes +wofford +wojciech +wojcik +wok +woke +woken +woks +wold +wolf +wolfe +wolff +wolfgang +wolfing +wolfman +wolfram +wolfson +wolinsky +wolk +wollen +wollman +wolter +wolverine +wolverines +wolverton +wolves +womack +woman +womanhood +womanizer +womanizing +womb +wombat +wombats +womble +women +womens +won +wonder +wondered +wonderful +wonderfully +wonderfulness +wondering +wonderland +wonderment +wonderous +wonders +wondrous +wondrously +wong +wonk +wont +woo +wood +woodall +woodard +woodbridge +woodburn +woodbury +woodcarver +woodcarvers +woodchuck +woodcock +woodcut +wooded +wooden +woodford +woodhouse +woodhull +woodie +woodland +woodlands +woodley +woodman +woodpecker +woodpeckers +woodrow +woodruff +woods +woodshed +woodside +woodsman +woodsmen +woodstock +woodward +woodwind +woodwinds +woodwork +woodworker +woodworkers +woodworking +woodworth +woody +woodyard +wooed +woof +woofers +woogie +wooing +wool +woolard +wooldridge +woolen +wooley +woolf +woollen +woolley +woolly +woolridge +wools +woolsey +woolworth +wooly +woon +woos +wooster +wooten +wootton +woozy +wop +worcester +word +worded +worden +wordiness +wording +wordings +wordless +wordlessly +wordperfect +wordplay +words +wordsmith +wordstar +wordsworth +wordy +wore +worf +work +workability +workable +workaday +workaholic +workaholics +workaholism +workbench +workbook +workbooks +workday +workdays +worked +worker +workers +workforce +workgroup +workgroups +workhorse +workhorses +working +workings +workload +workloads +workman +workmanlike +workmanship +workmen +workout +workouts +workplace +workplaces +workroom +works +worksheet +worksheets +workshop +workshops +workspace +workstation +workstations +workup +workweek +workweeks +world +worldcom +worlders +worldliness +worldly +worlds +worldspan +worldview +worldwide +worley +worm +wormlike +worms +wormwood +wormy +worn +worrall +worrell +worried +worrier +worries +worrisome +worry +worrying +worse +worsen +worsened +worsening +worsens +worship +worshiped +worshiper +worshipers +worshipful +worshiping +worshipped +worshipper +worshippers +worshipping +worships +worst +worsted +wort +worth +wortham +worthier +worthies +worthiness +worthing +worthington +worthless +worthlessness +worths +worthwhile +worthy +woud +would +wouldn +wound +wounded +wounding +wounds +wove +woven +wow +wowed +wowing +wows +wozniak +wr +wrack +wracked +wracking +wraith +wrangle +wrangled +wrangler +wranglers +wrangles +wrangling +wrap +wraparound +wrapped +wrapper +wrappers +wrapping +wrappings +wraps +wrath +wray +wreak +wreaked +wreaking +wreaks +wreath +wreaths +wreck +wreckage +wrecked +wrecker +wreckers +wrecking +wrecks +wrede +wren +wrench +wrenched +wrenches +wrenching +wrest +wrested +wresting +wrestle +wrestled +wrestler +wrestlers +wrestles +wrestling +wretch +wretched +wretchedly +wriggle +wright +wrights +wrightson +wrigley +wring +wringer +wringing +wrinkle +wrinkled +wrinkles +wrinkling +wrist +wristband +wristbands +wriston +wrists +wristwatch +wristwatches +writ +write +writedown +writedowns +writeoff +writeoffs +writer +writers +writes +writeup +writhe +writhing +writing +writings +writs +written +wrong +wrongdoer +wrongdoers +wrongdoing +wrongdoings +wronged +wrongful +wrongfully +wrongheaded +wrongly +wrongness +wrongs +wrote +wrought +wrung +wry +wryly +ws +wt +wu +wuhan +wulff +wunderkind +wunsch +wurlitzer +wurst +wurzel +wuss +wuthering +wuxi +wy +wyatt +wyden +wye +wyeth +wylde +wyle +wyler +wylie +wyllie +wyman +wyndham +wynn +wynne +wyoming +wyse +wyss +xavier +xenia +xenon +xenophobia +xenophobic +xerox +xerxes +xi +xiamen +xian +xiao +xie +xilinx +xinhua +xinjiang +xmas +xray +xs +xtra +xu +xx +xxx +xxxx +xylene +xylophone +ya +yacht +yachting +yachts +yachtsman +yack +yagi +yah +yahoo +yahoos +yahweh +yak +yakima +yakking +yakov +yakovlev +yaks +yale +yam +yamada +yamaguchi +yamaha +yamamoto +yamanaka +yamato +yams +yan +yancey +yandell +yang +yangtze +yank +yanked +yankee +yankees +yankelovich +yanking +yanks +yano +yao +yap +yaps +yard +yardage +yardbirds +yarder +yardley +yards +yardstick +yardsticks +yarmouth +yarmulke +yarmulkes +yarn +yarns +yaron +yarrow +yasir +yasser +yassin +yassir +yasuda +yasuo +yates +yau +yaw +yawn +yawned +yawning +yawns +yay +ybarra +yds +ye +yea +yeager +yeah +year +yearbook +yearbooks +yearend +yearling +yearlings +yearlong +yearly +yearn +yearned +yearning +yearnings +yearns +years +yearwood +yeas +yeast +yeasts +yeats +yee +yeh +yehuda +yelena +yell +yelled +yellen +yellin +yelling +yellow +yellowing +yellowish +yellowknife +yellows +yellowstone +yells +yelps +yeltsin +yemen +yemeni +yen +yeo +yeoman +yeomans +yep +yer +yerba +yerevan +yergin +yes +yeshiva +yesterday +yesterdays +yesteryear +yet +yeung +yevgeny +yew +yi +yiddish +yield +yielded +yielding +yields +yigal +yikes +yin +ying +yip +yitzhak +ym +yo +yoakam +yoda +yodel +yoder +yoga +yoghurt +yogi +yogurt +yoho +yok +yoke +yoked +yokel +yokels +yokes +yoking +yoko +yokohama +yolanda +yolk +yolks +yom +yomiuri +yon +yonder +yong +yonkers +yoo +yore +york +yorker +yorkers +yorkshire +yorktown +yosemite +yoshi +yoshida +yoshihiro +yoshiro +yossi +you +youn +young +youngblood +younger +youngers +youngest +youngish +youngman +youngs +youngster +youngsters +youngstown +younis +younkers +your +youre +yours +yourself +yourselves +yous +youse +yousef +youssef +youth +youthful +youthfulness +youths +yow +yoyo +ypsilanti +yr +yrs +ys +yttrium +yu +yuan +yucatan +yucca +yuck +yucky +yuen +yugo +yugoslav +yugoslavia +yugoslavian +yugoslavs +yuk +yukon +yule +yuletide +yum +yuma +yummy +yung +yunis +yup +yuppie +yuppies +yuri +yurt +yury +yusuf +yuval +yvan +yves +yvette +yvon +yvonne +za +zabriskie +zacarias +zach +zachariah +zacharias +zachary +zack +zacks +zag +zagreb +zags +zaher +zahir +zahn +zaibatsu +zaire +zairian +zakat +zaki +zaleski +zalman +zaman +zambezi +zambia +zambian +zamboanga +zambrano +zamora +zander +zandt +zane +zang +zaniest +zanu +zany +zanzibar +zap +zapata +zapp +zappa +zapped +zapping +zaps +zara +zaremba +zaun +zbigniew +ze +zea +zeal +zealand +zealander +zealanders +zealot +zealotry +zealots +zealous +zealously +zebra +zebras +zech +zechariah +zed +zedong +zee +zeigler +zeile +zeiss +zeit +zeitgeist +zeitlin +zeitung +zeitz +zeke +zelda +zell +zeller +zemin +zen +zenaida +zeng +zenia +zenith +zeno +zenobia +zeos +zephyr +zephyrs +zeppelin +zero +zeroed +zeroes +zeroing +zeros +zest +zesty +zeta +zeus +zhang +zhao +zhejiang +zheng +zhivago +zhou +ziad +ziegler +zielinski +ziff +zig +zigzag +zigzagged +zigzagging +zigzags +zilch +zillion +zillions +zimbabwe +zimbabwean +zimmer +zimmerman +zimmermann +zina +zinc +zine +zinfandel +zing +zinger +zink +zinn +zinnia +zinnias +zion +zionism +zionist +zionists +zip +zipped +zipper +zippered +zippers +zipping +zippo +zippy +zips +zirconia +zirconium +zither +zito +zloty +zlotys +zo +zodiac +zoe +zoeller +zoellick +zogby +zola +zoltan +zombie +zombies +zona +zone +zoned +zones +zoning +zoo +zoological +zoologist +zoologists +zoology +zoom +zoomed +zooming +zooms +zoos +zoran +zorn +zoroaster +zorro +zoster +zucchini +zucker +zuckerman +zug +zulu +zulus +zuma +zuni +zuniga +zurich +zweig +zwick +zydeco +zygote +zzzz \ No newline at end of file diff --git a/btcrecover/wordlists/coolwallet-en.txt b/btcrecover/wordlists/coolwallet-en.txt new file mode 100644 index 000000000..650853765 --- /dev/null +++ b/btcrecover/wordlists/coolwallet-en.txt @@ -0,0 +1,2049 @@ +Number Seed,Mapped English Word +00001,abandon +00049,ability +00097,able +00145,about +00193,above +00241,absent +00289,absorb +00337,abstract +00385,absurd +00433,abuse +00481,access +00529,accident +00577,account +00625,accuse +00673,achieve +00721,acid +00769,acoustic +00817,acquire +00865,across +00913,act +00961,action +01009,actor +01057,actress +01105,actual +01153,adapt +01201,add +01249,addict +01297,address +01345,adjust +01393,admit +01441,adult +01489,advance +01537,advice +01585,aerobic +01633,affair +01681,afford +01729,afraid +01777,again +01825,age +01873,agent +01921,agree +01969,ahead +02017,aim +02065,air +02113,airport +02161,aisle +02209,alarm +02257,album +02305,alcohol +02353,alert +02401,alien +02449,all +02497,alley +02545,allow +02593,almost +02641,alone +02689,alpha +02737,already +02785,also +02833,alter +02881,always +02929,amateur +02977,amazing +03025,among +03073,amount +03121,amused +03169,analyst +03217,anchor +03265,ancient +03313,anger +03361,angle +03409,angry +03457,animal +03505,ankle +03553,announce +03601,annual +03649,another +03697,answer +03745,antenna +03793,antique +03841,anxiety +03889,any +03937,apart +03985,apology +04033,appear +04081,apple +04129,approve +04177,april +04225,arch +04273,arctic +04321,area +04369,arena +04417,argue +04465,arm +04513,armed +04561,armor +04609,army +04657,around +04705,arrange +04753,arrest +04801,arrive +04849,arrow +04897,art +04945,artefact +04993,artist +05041,artwork +05089,ask +05137,aspect +05185,assault +05233,asset +05281,assist +05329,assume +05377,asthma +05425,athlete +05473,atom +05521,attack +05569,attend +05617,attitude +05665,attract +05713,auction +05761,audit +05809,august +05857,aunt +05905,author +05953,auto +06001,autumn +06049,average +06097,avocado +06145,avoid +06193,awake +06241,aware +06289,away +06337,awesome +06385,awful +06433,awkward +06481,axis +06529,baby +06577,bachelor +06625,bacon +06673,badge +06721,bag +06769,balance +06817,balcony +06865,ball +06913,bamboo +06961,banana +07009,banner +07057,bar +07105,barely +07153,bargain +07201,barrel +07249,base +07297,basic +07345,basket +07393,battle +07441,beach +07489,bean +07537,beauty +07585,because +07633,become +07681,beef +07729,before +07777,begin +07825,behave +07873,behind +07921,believe +07969,below +08017,belt +08065,bench +08113,benefit +08161,best +08209,betray +08257,better +08305,between +08353,beyond +08401,bicycle +08449,bid +08497,bike +08545,bind +08593,biology +08641,bird +08689,birth +08737,bitter +08785,black +08833,blade +08881,blame +08929,blanket +08977,blast +09025,bleak +09073,bless +09121,blind +09169,blood +09217,blossom +09265,blouse +09313,blue +09361,blur +09409,blush +09457,board +09505,boat +09553,body +09601,boil +09649,bomb +09697,bone +09745,bonus +09793,book +09841,boost +09889,border +09937,boring +09985,borrow +10033,boss +10081,bottom +10129,bounce +10177,box +10225,boy +10273,bracket +10321,brain +10369,brand +10417,brass +10465,brave +10513,bread +10561,breeze +10609,brick +10657,bridge +10705,brief +10753,bright +10801,bring +10849,brisk +10897,broccoli +10945,broken +10993,bronze +11041,broom +11089,brother +11137,brown +11185,brush +11233,bubble +11281,buddy +11329,budget +11377,buffalo +11425,build +11473,bulb +11521,bulk +11569,bullet +11617,bundle +11665,bunker +11713,burden +11761,burger +11809,burst +11857,bus +11905,business +11953,busy +12001,butter +12049,buyer +12097,buzz +12145,cabbage +12193,cabin +12241,cable +12289,cactus +12337,cage +12385,cake +12433,call +12481,calm +12529,camera +12577,camp +12625,can +12673,canal +12721,cancel +12769,candy +12817,cannon +12865,canoe +12913,canvas +12961,canyon +13009,capable +13057,capital +13105,captain +13153,car +13201,carbon +13249,card +13297,cargo +13345,carpet +13393,carry +13441,cart +13489,case +13537,cash +13585,casino +13633,castle +13681,casual +13729,cat +13777,catalog +13825,catch +13873,category +13921,cattle +13969,caught +14017,cause +14065,caution +14113,cave +14161,ceiling +14209,celery +14257,cement +14305,census +14353,century +14401,cereal +14449,certain +14497,chair +14545,chalk +14593,champion +14641,change +14689,chaos +14737,chapter +14785,charge +14833,chase +14881,chat +14929,cheap +14977,check +15025,cheese +15073,chef +15121,cherry +15169,chest +15217,chicken +15265,chief +15313,child +15361,chimney +15409,choice +15457,choose +15505,chronic +15553,chuckle +15601,chunk +15649,churn +15697,cigar +15745,cinnamon +15793,circle +15841,citizen +15889,city +15937,civil +15985,claim +16033,clap +16081,clarify +16129,claw +16177,clay +16225,clean +16273,clerk +16321,clever +16369,click +16417,client +16465,cliff +16513,climb +16561,clinic +16609,clip +16657,clock +16705,clog +16753,close +16801,cloth +16849,cloud +16897,clown +16945,club +16993,clump +17041,cluster +17089,clutch +17137,coach +17185,coast +17233,coconut +17281,code +17329,coffee +17377,coil +17425,coin +17473,collect +17521,color +17569,column +17617,combine +17665,come +17713,comfort +17761,comic +17809,common +17857,company +17905,concert +17953,conduct +18001,confirm +18049,congress +18097,connect +18145,consider +18193,control +18241,convince +18289,cook +18337,cool +18385,copper +18433,copy +18481,coral +18529,core +18577,corn +18625,correct +18673,cost +18721,cotton +18769,couch +18817,country +18865,couple +18913,course +18961,cousin +19009,cover +19057,coyote +19105,crack +19153,cradle +19201,craft +19249,cram +19297,crane +19345,crash +19393,crater +19441,crawl +19489,crazy +19537,cream +19585,credit +19633,creek +19681,crew +19729,cricket +19777,crime +19825,crisp +19873,critic +19921,crop +19969,cross +20017,crouch +20065,crowd +20113,crucial +20161,cruel +20209,cruise +20257,crumble +20305,crunch +20353,crush +20401,cry +20449,crystal +20497,cube +20545,culture +20593,cup +20641,cupboard +20689,curious +20737,current +20785,curtain +20833,curve +20881,cushion +20929,custom +20977,cute +21025,cycle +21073,dad +21121,damage +21169,damp +21217,dance +21265,danger +21313,daring +21361,dash +21409,daughter +21457,dawn +21505,day +21553,deal +21601,debate +21649,debris +21697,decade +21745,december +21793,decide +21841,decline +21889,decorate +21937,decrease +21985,deer +22033,defense +22081,define +22129,defy +22177,degree +22225,delay +22273,deliver +22321,demand +22369,demise +22417,denial +22465,dentist +22513,deny +22561,depart +22609,depend +22657,deposit +22705,depth +22753,deputy +22801,derive +22849,describe +22897,desert +22945,design +22993,desk +23041,despair +23089,destroy +23137,detail +23185,detect +23233,develop +23281,device +23329,devote +23377,diagram +23425,dial +23473,diamond +23521,diary +23569,dice +23617,diesel +23665,diet +23713,differ +23761,digital +23809,dignity +23857,dilemma +23905,dinner +23953,dinosaur +24001,direct +24049,dirt +24097,disagree +24145,discover +24193,disease +24241,dish +24289,dismiss +24337,disorder +24385,display +24433,distance +24481,divert +24529,divide +24577,divorce +24625,dizzy +24673,doctor +24721,document +24769,dog +24817,doll +24865,dolphin +24913,domain +24961,donate +25009,donkey +25057,donor +25105,door +25153,dose +25201,double +25249,dove +25297,draft +25345,dragon +25393,drama +25441,drastic +25489,draw +25537,dream +25585,dress +25633,drift +25681,drill +25729,drink +25777,drip +25825,drive +25873,drop +25921,drum +25969,dry +26017,duck +26065,dumb +26113,dune +26161,during +26209,dust +26257,dutch +26305,duty +26353,dwarf +26401,dynamic +26449,eager +26497,eagle +26545,early +26593,earn +26641,earth +26689,easily +26737,east +26785,easy +26833,echo +26881,ecology +26929,economy +26977,edge +27025,edit +27073,educate +27121,effort +27169,egg +27217,eight +27265,either +27313,elbow +27361,elder +27409,electric +27457,elegant +27505,element +27553,elephant +27601,elevator +27649,elite +27697,else +27745,embark +27793,embody +27841,embrace +27889,emerge +27937,emotion +27985,employ +28033,empower +28081,empty +28129,enable +28177,enact +28225,end +28273,endless +28321,endorse +28369,enemy +28417,energy +28465,enforce +28513,engage +28561,engine +28609,enhance +28657,enjoy +28705,enlist +28753,enough +28801,enrich +28849,enroll +28897,ensure +28945,enter +28993,entire +29041,entry +29089,envelope +29137,episode +29185,equal +29233,equip +29281,era +29329,erase +29377,erode +29425,erosion +29473,error +29521,erupt +29569,escape +29617,essay +29665,essence +29713,estate +29761,eternal +29809,ethics +29857,evidence +29905,evil +29953,evoke +30001,evolve +30049,exact +30097,example +30145,excess +30193,exchange +30241,excite +30289,exclude +30337,excuse +30385,execute +30433,exercise +30481,exhaust +30529,exhibit +30577,exile +30625,exist +30673,exit +30721,exotic +30769,expand +30817,expect +30865,expire +30913,explain +30961,expose +31009,express +31057,extend +31105,extra +31153,eye +31201,eyebrow +31249,fabric +31297,face +31345,faculty +31393,fade +31441,faint +31489,faith +31537,fall +31585,FALSE +31633,fame +31681,family +31729,famous +31777,fan +31825,fancy +31873,fantasy +31921,farm +31969,fashion +32017,fat +32065,fatal +32113,father +32161,fatigue +32209,fault +32257,favorite +32305,feature +32353,february +32401,federal +32449,fee +32497,feed +32545,feel +32593,female +32641,fence +32689,festival +32737,fetch +32785,fever +32833,few +32881,fiber +32929,fiction +32977,field +33025,figure +33073,file +33121,film +33169,filter +33217,final +33265,find +33313,fine +33361,finger +33409,finish +33457,fire +33505,firm +33553,first +33601,fiscal +33649,fish +33697,fit +33745,fitness +33793,fix +33841,flag +33889,flame +33937,flash +33985,flat +34033,flavor +34081,flee +34129,flight +34177,flip +34225,float +34273,flock +34321,floor +34369,flower +34417,fluid +34465,flush +34513,fly +34561,foam +34609,focus +34657,fog +34705,foil +34753,fold +34801,follow +34849,food +34897,foot +34945,force +34993,forest +35041,forget +35089,fork +35137,fortune +35185,forum +35233,forward +35281,fossil +35329,foster +35377,found +35425,fox +35473,fragile +35521,frame +35569,frequent +35617,fresh +35665,friend +35713,fringe +35761,frog +35809,front +35857,frost +35905,frown +35953,frozen +36001,fruit +36049,fuel +36097,fun +36145,funny +36193,furnace +36241,fury +36289,future +36337,gadget +36385,gain +36433,galaxy +36481,gallery +36529,game +36577,gap +36625,garage +36673,garbage +36721,garden +36769,garlic +36817,garment +36865,gas +36913,gasp +36961,gate +37009,gather +37057,gauge +37105,gaze +37153,general +37201,genius +37249,genre +37297,gentle +37345,genuine +37393,gesture +37441,ghost +37489,giant +37537,gift +37585,giggle +37633,ginger +37681,giraffe +37729,girl +37777,give +37825,glad +37873,glance +37921,glare +37969,glass +38017,glide +38065,glimpse +38113,globe +38161,gloom +38209,glory +38257,glove +38305,glow +38353,glue +38401,goat +38449,goddess +38497,gold +38545,good +38593,goose +38641,gorilla +38689,gospel +38737,gossip +38785,govern +38833,gown +38881,grab +38929,grace +38977,grain +39025,grant +39073,grape +39121,grass +39169,gravity +39217,great +39265,green +39313,grid +39361,grief +39409,grit +39457,grocery +39505,group +39553,grow +39601,grunt +39649,guard +39697,guess +39745,guide +39793,guilt +39841,guitar +39889,gun +39937,gym +39985,habit +40033,hair +40081,half +40129,hammer +40177,hamster +40225,hand +40273,happy +40321,harbor +40369,hard +40417,harsh +40465,harvest +40513,hat +40561,have +40609,hawk +40657,hazard +40705,head +40753,health +40801,heart +40849,heavy +40897,hedgehog +40945,height +40993,hello +41041,helmet +41089,help +41137,hen +41185,hero +41233,hidden +41281,high +41329,hill +41377,hint +41425,hip +41473,hire +41521,history +41569,hobby +41617,hockey +41665,hold +41713,hole +41761,holiday +41809,hollow +41857,home +41905,honey +41953,hood +42001,hope +42049,horn +42097,horror +42145,horse +42193,hospital +42241,host +42289,hotel +42337,hour +42385,hover +42433,hub +42481,huge +42529,human +42577,humble +42625,humor +42673,hundred +42721,hungry +42769,hunt +42817,hurdle +42865,hurry +42913,hurt +42961,husband +43009,hybrid +43057,ice +43105,icon +43153,idea +43201,identify +43249,idle +43297,ignore +43345,ill +43393,illegal +43441,illness +43489,image +43537,imitate +43585,immense +43633,immune +43681,impact +43729,impose +43777,improve +43825,impulse +43873,inch +43921,include +43969,income +44017,increase +44065,index +44113,indicate +44161,indoor +44209,industry +44257,infant +44305,inflict +44353,inform +44401,inhale +44449,inherit +44497,initial +44545,inject +44593,injury +44641,inmate +44689,inner +44737,innocent +44785,input +44833,inquiry +44881,insane +44929,insect +44977,inside +45025,inspire +45073,install +45121,intact +45169,interest +45217,into +45265,invest +45313,invite +45361,involve +45409,iron +45457,island +45505,isolate +45553,issue +45601,item +45649,ivory +45697,jacket +45745,jaguar +45793,jar +45841,jazz +45889,jealous +45937,jeans +45985,jelly +46033,jewel +46081,job +46129,join +46177,joke +46225,journey +46273,joy +46321,judge +46369,juice +46417,jump +46465,jungle +46513,junior +46561,junk +46609,just +46657,kangaroo +46705,keen +46753,keep +46801,ketchup +46849,key +46897,kick +46945,kid +46993,kidney +47041,kind +47089,kingdom +47137,kiss +47185,kit +47233,kitchen +47281,kite +47329,kitten +47377,kiwi +47425,knee +47473,knife +47521,knock +47569,know +47617,lab +47665,label +47713,labor +47761,ladder +47809,lady +47857,lake +47905,lamp +47953,language +48001,laptop +48049,large +48097,later +48145,latin +48193,laugh +48241,laundry +48289,lava +48337,law +48385,lawn +48433,lawsuit +48481,layer +48529,lazy +48577,leader +48625,leaf +48673,learn +48721,leave +48769,lecture +48817,left +48865,leg +48913,legal +48961,legend +49009,leisure +49057,lemon +49105,lend +49153,length +49201,lens +49249,leopard +49297,lesson +49345,letter +49393,level +49441,liar +49489,liberty +49537,library +49585,license +49633,life +49681,lift +49729,light +49777,like +49825,limb +49873,limit +49921,link +49969,lion +50017,liquid +50065,list +50113,little +50161,live +50209,lizard +50257,load +50305,loan +50353,lobster +50401,local +50449,lock +50497,logic +50545,lonely +50593,long +50641,loop +50689,lottery +50737,loud +50785,lounge +50833,love +50881,loyal +50929,lucky +50977,luggage +51025,lumber +51073,lunar +51121,lunch +51169,luxury +51217,lyrics +51265,machine +51313,mad +51361,magic +51409,magnet +51457,maid +51505,mail +51553,main +51601,major +51649,make +51697,mammal +51745,man +51793,manage +51841,mandate +51889,mango +51937,mansion +51985,manual +52033,maple +52081,marble +52129,march +52177,margin +52225,marine +52273,market +52321,marriage +52369,mask +52417,mass +52465,master +52513,match +52561,material +52609,math +52657,matrix +52705,matter +52753,maximum +52801,maze +52849,meadow +52897,mean +52945,measure +52993,meat +53041,mechanic +53089,medal +53137,media +53185,melody +53233,melt +53281,member +53329,memory +53377,mention +53425,menu +53473,mercy +53521,merge +53569,merit +53617,merry +53665,mesh +53713,message +53761,metal +53809,method +53857,middle +53905,midnight +53953,milk +54001,million +54049,mimic +54097,mind +54145,minimum +54193,minor +54241,minute +54289,miracle +54337,mirror +54385,misery +54433,miss +54481,mistake +54529,mix +54577,mixed +54625,mixture +54673,mobile +54721,model +54769,modify +54817,mom +54865,moment +54913,monitor +54961,monkey +55009,monster +55057,month +55105,moon +55153,moral +55201,more +55249,morning +55297,mosquito +55345,mother +55393,motion +55441,motor +55489,mountain +55537,mouse +55585,move +55633,movie +55681,much +55729,muffin +55777,mule +55825,multiply +55873,muscle +55921,museum +55969,mushroom +56017,music +56065,must +56113,mutual +56161,myself +56209,mystery +56257,myth +56305,naive +56353,name +56401,napkin +56449,narrow +56497,nasty +56545,nation +56593,nature +56641,near +56689,neck +56737,need +56785,negative +56833,neglect +56881,neither +56929,nephew +56977,nerve +57025,nest +57073,net +57121,network +57169,neutral +57217,never +57265,news +57313,next +57361,nice +57409,night +57457,noble +57505,noise +57553,nominee +57601,noodle +57649,normal +57697,north +57745,nose +57793,notable +57841,note +57889,nothing +57937,notice +57985,novel +58033,now +58081,nuclear +58129,number +58177,nurse +58225,nut +58273,oak +58321,obey +58369,object +58417,oblige +58465,obscure +58513,observe +58561,obtain +58609,obvious +58657,occur +58705,ocean +58753,october +58801,odor +58849,off +58897,offer +58945,office +58993,often +59041,oil +59089,okay +59137,old +59185,olive +59233,olympic +59281,omit +59329,once +59377,one +59425,onion +59473,online +59521,only +59569,open +59617,opera +59665,opinion +59713,oppose +59761,option +59809,orange +59857,orbit +59905,orchard +59953,order +60001,ordinary +60049,organ +60097,orient +60145,original +60193,orphan +60241,ostrich +60289,other +60337,outdoor +60385,outer +60433,output +60481,outside +60529,oval +60577,oven +60625,over +60673,own +60721,owner +60769,oxygen +60817,oyster +60865,ozone +60913,pact +60961,paddle +61009,page +61057,pair +61105,palace +61153,palm +61201,panda +61249,panel +61297,panic +61345,panther +61393,paper +61441,parade +61489,parent +61537,park +61585,parrot +61633,party +61681,pass +61729,patch +61777,path +61825,patient +61873,patrol +61921,pattern +61969,pause +62017,pave +62065,payment +62113,peace +62161,peanut +62209,pear +62257,peasant +62305,pelican +62353,pen +62401,penalty +62449,pencil +62497,people +62545,pepper +62593,perfect +62641,permit +62689,person +62737,pet +62785,phone +62833,photo +62881,phrase +62929,physical +62977,piano +63025,picnic +63073,picture +63121,piece +63169,pig +63217,pigeon +63265,pill +63313,pilot +63361,pink +63409,pioneer +63457,pipe +63505,pistol +63553,pitch +63601,pizza +63649,place +63697,planet +63745,plastic +63793,plate +63841,play +63889,please +63937,pledge +63985,pluck +64033,plug +64081,plunge +64129,poem +64177,poet +64225,point +64273,polar +64321,pole +64369,police +64417,pond +64465,pony +64513,pool +64561,popular +64609,portion +64657,position +64705,possible +64753,post +64801,potato +64849,pottery +64897,poverty +64945,powder +64993,power +65041,practice +65089,praise +65137,predict +65185,prefer +65233,prepare +65281,present +65329,pretty +65377,prevent +65425,price +65473,pride +65521,primary +65569,print +65617,priority +65665,prison +65713,private +65761,prize +65809,problem +65857,process +65905,produce +65953,profit +66001,program +66049,project +66097,promote +66145,proof +66193,property +66241,prosper +66289,protect +66337,proud +66385,provide +66433,public +66481,pudding +66529,pull +66577,pulp +66625,pulse +66673,pumpkin +66721,punch +66769,pupil +66817,puppy +66865,purchase +66913,purity +66961,purpose +67009,purse +67057,push +67105,put +67153,puzzle +67201,pyramid +67249,quality +67297,quantum +67345,quarter +67393,question +67441,quick +67489,quit +67537,quiz +67585,quote +67633,rabbit +67681,raccoon +67729,race +67777,rack +67825,radar +67873,radio +67921,rail +67969,rain +68017,raise +68065,rally +68113,ramp +68161,ranch +68209,random +68257,range +68305,rapid +68353,rare +68401,rate +68449,rather +68497,raven +68545,raw +68593,razor +68641,ready +68689,real +68737,reason +68785,rebel +68833,rebuild +68881,recall +68929,receive +68977,recipe +69025,record +69073,recycle +69121,reduce +69169,reflect +69217,reform +69265,refuse +69313,region +69361,regret +69409,regular +69457,reject +69505,relax +69553,release +69601,relief +69649,rely +69697,remain +69745,remember +69793,remind +69841,remove +69889,render +69937,renew +69985,rent +70033,reopen +70081,repair +70129,repeat +70177,replace +70225,report +70273,require +70321,rescue +70369,resemble +70417,resist +70465,resource +70513,response +70561,result +70609,retire +70657,retreat +70705,return +70753,reunion +70801,reveal +70849,review +70897,reward +70945,rhythm +70993,rib +71041,ribbon +71089,rice +71137,rich +71185,ride +71233,ridge +71281,rifle +71329,right +71377,rigid +71425,ring +71473,riot +71521,ripple +71569,risk +71617,ritual +71665,rival +71713,river +71761,road +71809,roast +71857,robot +71905,robust +71953,rocket +72001,romance +72049,roof +72097,rookie +72145,room +72193,rose +72241,rotate +72289,rough +72337,round +72385,route +72433,royal +72481,rubber +72529,rude +72577,rug +72625,rule +72673,run +72721,runway +72769,rural +72817,sad +72865,saddle +72913,sadness +72961,safe +73009,sail +73057,salad +73105,salmon +73153,salon +73201,salt +73249,salute +73297,same +73345,sample +73393,sand +73441,satisfy +73489,satoshi +73537,sauce +73585,sausage +73633,save +73681,say +73729,scale +73777,scan +73825,scare +73873,scatter +73921,scene +73969,scheme +74017,school +74065,science +74113,scissors +74161,scorpion +74209,scout +74257,scrap +74305,screen +74353,script +74401,scrub +74449,sea +74497,search +74545,season +74593,seat +74641,second +74689,secret +74737,section +74785,security +74833,seed +74881,seek +74929,segment +74977,select +75025,sell +75073,seminar +75121,senior +75169,sense +75217,sentence +75265,series +75313,service +75361,session +75409,settle +75457,setup +75505,seven +75553,shadow +75601,shaft +75649,shallow +75697,share +75745,shed +75793,shell +75841,sheriff +75889,shield +75937,shift +75985,shine +76033,ship +76081,shiver +76129,shock +76177,shoe +76225,shoot +76273,shop +76321,short +76369,shoulder +76417,shove +76465,shrimp +76513,shrug +76561,shuffle +76609,shy +76657,sibling +76705,sick +76753,side +76801,siege +76849,sight +76897,sign +76945,silent +76993,silk +77041,silly +77089,silver +77137,similar +77185,simple +77233,since +77281,sing +77329,siren +77377,sister +77425,situate +77473,six +77521,size +77569,skate +77617,sketch +77665,ski +77713,skill +77761,skin +77809,skirt +77857,skull +77905,slab +77953,slam +78001,sleep +78049,slender +78097,slice +78145,slide +78193,slight +78241,slim +78289,slogan +78337,slot +78385,slow +78433,slush +78481,small +78529,smart +78577,smile +78625,smoke +78673,smooth +78721,snack +78769,snake +78817,snap +78865,sniff +78913,snow +78961,soap +79009,soccer +79057,social +79105,sock +79153,soda +79201,soft +79249,solar +79297,soldier +79345,solid +79393,solution +79441,solve +79489,someone +79537,song +79585,soon +79633,sorry +79681,sort +79729,soul +79777,sound +79825,soup +79873,source +79921,south +79969,space +80017,spare +80065,spatial +80113,spawn +80161,speak +80209,special +80257,speed +80305,spell +80353,spend +80401,sphere +80449,spice +80497,spider +80545,spike +80593,spin +80641,spirit +80689,split +80737,spoil +80785,sponsor +80833,spoon +80881,sport +80929,spot +80977,spray +81025,spread +81073,spring +81121,spy +81169,square +81217,squeeze +81265,squirrel +81313,stable +81361,stadium +81409,staff +81457,stage +81505,stairs +81553,stamp +81601,stand +81649,start +81697,state +81745,stay +81793,steak +81841,steel +81889,stem +81937,step +81985,stereo +82033,stick +82081,still +82129,sting +82177,stock +82225,stomach +82273,stone +82321,stool +82369,story +82417,stove +82465,strategy +82513,street +82561,strike +82609,strong +82657,struggle +82705,student +82753,stuff +82801,stumble +82849,style +82897,subject +82945,submit +82993,subway +83041,success +83089,such +83137,sudden +83185,suffer +83233,sugar +83281,suggest +83329,suit +83377,summer +83425,sun +83473,sunny +83521,sunset +83569,super +83617,supply +83665,supreme +83713,sure +83761,surface +83809,surge +83857,surprise +83905,surround +83953,survey +84001,suspect +84049,sustain +84097,swallow +84145,swamp +84193,swap +84241,swarm +84289,swear +84337,sweet +84385,swift +84433,swim +84481,swing +84529,switch +84577,sword +84625,symbol +84673,symptom +84721,syrup +84769,system +84817,table +84865,tackle +84913,tag +84961,tail +85009,talent +85057,talk +85105,tank +85153,tape +85201,target +85249,task +85297,taste +85345,tattoo +85393,taxi +85441,teach +85489,team +85537,tell +85585,ten +85633,tenant +85681,tennis +85729,tent +85777,term +85825,test +85873,text +85921,thank +85969,that +86017,theme +86065,then +86113,theory +86161,there +86209,they +86257,thing +86305,this +86353,thought +86401,three +86449,thrive +86497,throw +86545,thumb +86593,thunder +86641,ticket +86689,tide +86737,tiger +86785,tilt +86833,timber +86881,time +86929,tiny +86977,tip +87025,tired +87073,tissue +87121,title +87169,toast +87217,tobacco +87265,today +87313,toddler +87361,toe +87409,together +87457,toilet +87505,token +87553,tomato +87601,tomorrow +87649,tone +87697,tongue +87745,tonight +87793,tool +87841,tooth +87889,top +87937,topic +87985,topple +88033,torch +88081,tornado +88129,tortoise +88177,toss +88225,total +88273,tourist +88321,toward +88369,tower +88417,town +88465,toy +88513,track +88561,trade +88609,traffic +88657,tragic +88705,train +88753,transfer +88801,trap +88849,trash +88897,travel +88945,tray +88993,treat +89041,tree +89089,trend +89137,trial +89185,tribe +89233,trick +89281,trigger +89329,trim +89377,trip +89425,trophy +89473,trouble +89521,truck +89569,TRUE +89617,truly +89665,trumpet +89713,trust +89761,truth +89809,try +89857,tube +89905,tuition +89953,tumble +90001,tuna +90049,tunnel +90097,turkey +90145,turn +90193,turtle +90241,twelve +90289,twenty +90337,twice +90385,twin +90433,twist +90481,two +90529,type +90577,typical +90625,ugly +90673,umbrella +90721,unable +90769,unaware +90817,uncle +90865,uncover +90913,under +90961,undo +91009,unfair +91057,unfold +91105,unhappy +91153,uniform +91201,unique +91249,unit +91297,universe +91345,unknown +91393,unlock +91441,until +91489,unusual +91537,unveil +91585,update +91633,upgrade +91681,uphold +91729,upon +91777,upper +91825,upset +91873,urban +91921,urge +91969,usage +92017,use +92065,used +92113,useful +92161,useless +92209,usual +92257,utility +92305,vacant +92353,vacuum +92401,vague +92449,valid +92497,valley +92545,valve +92593,van +92641,vanish +92689,vapor +92737,various +92785,vast +92833,vault +92881,vehicle +92929,velvet +92977,vendor +93025,venture +93073,venue +93121,verb +93169,verify +93217,version +93265,very +93313,vessel +93361,veteran +93409,viable +93457,vibrant +93505,vicious +93553,victory +93601,video +93649,view +93697,village +93745,vintage +93793,violin +93841,virtual +93889,virus +93937,visa +93985,visit +94033,visual +94081,vital +94129,vivid +94177,vocal +94225,voice +94273,void +94321,volcano +94369,volume +94417,vote +94465,voyage +94513,wage +94561,wagon +94609,wait +94657,walk +94705,wall +94753,walnut +94801,want +94849,warfare +94897,warm +94945,warrior +94993,wash +95041,wasp +95089,waste +95137,water +95185,wave +95233,way +95281,wealth +95329,weapon +95377,wear +95425,weasel +95473,weather +95521,web +95569,wedding +95617,weekend +95665,weird +95713,welcome +95761,west +95809,wet +95857,whale +95905,what +95953,wheat +96001,wheel +96049,when +96097,where +96145,whip +96193,whisper +96241,wide +96289,width +96337,wife +96385,wild +96433,will +96481,win +96529,window +96577,wine +96625,wing +96673,wink +96721,winner +96769,winter +96817,wire +96865,wisdom +96913,wise +96961,wish +97009,witness +97057,wolf +97105,woman +97153,wonder +97201,wood +97249,wool +97297,word +97345,work +97393,world +97441,worry +97489,worth +97537,wrap +97585,wreck +97633,wrestle +97681,wrist +97729,write +97777,wrong +97825,yard +97873,year +97921,yellow +97969,you +98017,young +98065,youth +98113,zebra +98161,zero +98209,zone +98257,zoo \ No newline at end of file diff --git a/btcrecoveru.py b/btcrecoveru.py deleted file mode 100755 index f71e99ce8..000000000 --- a/btcrecoveru.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -# for backwards compatibility -import sys, os -print >> sys.stderr, "notice: you can also run 'btcrecover.py --utf8' instead of 'btcrecoveru.py'" -sys.argv.append("--utf8") -execfile(os.path.join(os.path.dirname(__file__), "btcrecover.py")) diff --git a/check-address-db.py b/check-address-db.py new file mode 100644 index 000000000..698bf0846 --- /dev/null +++ b/check-address-db.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +# check-address-db.py -- Bitcoin address database creator for seedrecover +# Copyright (C) 2021 Stephen Rothery +# +# This file is part of btcrecover. +# +# btcrecover is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# btcrecover is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +from btcrecover import addressset +from btcrecover import btcrseed +import sys,argparse, atexit +from os import path + +__version__ = "1.11.0-CryptoGuide" + +if __name__ == "__main__": + print("Starting CheckAddressDB", __version__) + + parser = argparse.ArgumentParser() + parser.add_argument("--dbfilename", nargs="?", default="addresses.db", help="the name of the database file (default: addresses.db)") + parser.add_argument("--checkaddresses", nargs="*", help="Check whether a single address is present in the addressDB") + parser.add_argument("--checkaddresslist", metavar="PATH", help="Check whether all of the addresses in a list file are present in the addressDB") + parser.add_argument("--suppress-found", action="store_true", + help="Suppress console messages for found addresses") + parser.add_argument("--suppress-notfound", action="store_true", + help="Suppress console messages for not-found addresses") + + # Optional bash tab completion support + try: + import argcomplete + argcomplete.autocomplete(parser) + except ImportError: + pass + + args = parser.parse_args() + + if not path.exists(args.dbfilename): + sys.exit("Address database file not found...") + + print("Loading address database ...") + addressdb = addressset.AddressSet.fromfile(open(args.dbfilename, "rb")) + print("Loaded", len(addressdb), "addresses from database ...") + + addresses = [] + comments = [] + + if args.checkaddresses: + for address in args.checkaddresses: + addresses.append(address) + comments.append("") + + if args.checkaddresslist: + with open(args.checkaddresslist) as addressistfile: + print("Loading: ", args.checkaddresslist) + for line in addressistfile: + if len(line) < 2: continue + if "#" in line: + address, comment = line.split("#") + else: + address = line + comment = "" + + addresses.append(address.strip()) + comments.append(comment.strip()) + + checklist = zip(addresses, comments) + + found = 0 + not_found = 0 + checked = 0 + + for address, comment in checklist: + checked += 1 + if (checked % 100000 == 0): + print("Checked:", checked, "addresses in current file,", len(addresses), + "lines in current addresslist") + + # Just use wallet base and walletethereum for now + try: + hash160 = btcrseed.WalletBase._addresses_to_hash160s([address]).pop() + except: + #print("Invalid Address in Checklist:", address, comment) + #continue + hash160 = btcrseed.WalletEthereum._addresses_to_hash160s([address]).pop() + + if hash160 in addressdb: + found += 1 + if not args.suppress_found: + print(address, "Found!", comment) + else: + not_found += 1 + if not args.suppress_notfound: + print(address, "Not Found!", comment) + + print("Checked", len(addresses), "addresses") + print(found, "Found") + print(not_found, "Not Found") + diff --git a/check_ripemd160.py b/check_ripemd160.py new file mode 100644 index 000000000..735bc00aa --- /dev/null +++ b/check_ripemd160.py @@ -0,0 +1,11 @@ +import hashlib + +# Enable functions that may not work for some standard libraries in some environments +try: + # this will work with micropython and python < 3.10 + # but will raise and exception if ripemd is not supported (openssl 3) + hashlib.new('ripemd160') + print("Good News! RIPEMD160 available and working in Hashlib, so can be used by BTCRecover") +except: + # otherwise use pure python implementation + print("Bad News! RIPEMD160 not available via Hashlib, Pure Python implementation will be used. This is much slower, see BTCRecover installation docs for more information.") \ No newline at end of file diff --git a/compatibility_check.py b/compatibility_check.py new file mode 100644 index 000000000..f4ef4981a --- /dev/null +++ b/compatibility_check.py @@ -0,0 +1,11 @@ +import sys +if sys.version_info < (3, 6): + sys.stdout.write("\n\n************************************ Python Version Error ******************************************\n\n") + sys.stdout.write("Sorry, BTCRecover no longer supports Python2 as it is officially End-of-Life...\n\n") + sys.stdout.write("Some features of this tool also require Python 3.7 or above to work....\n\n") + sys.stdout.write("You will need either to upgrade to at least Python 3.7 or download the final Python2 release.\n\n") + sys.stdout.write("Note: Python2 versions of this tool are now unsupported and will not receive improvements or fixes\n\n") + sys.stdout.write("Python2 releases and documentation for installing and using this tool with Python3 can be found at from https://github.com/3rdIteration/btcrecover.\n\n") + sys.stdout.write("************************************ Python Version Error ******************************************\n\n") + sys.exit(1) + diff --git a/create-address-db.py b/create-address-db.py index ef14fcabe..1d8ebe570 100644 --- a/create-address-db.py +++ b/create-address-db.py @@ -1,72 +1,86 @@ #!/usr/bin/env python - -# create-address-db.py -- Bitcoin address database creator for seedrecover -# Copyright (C) 2017 Christopher Gurnee -# -# This file is part of btcrecover. -# -# btcrecover is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version -# 2 of the License, or (at your option) any later version. -# -# btcrecover is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ - -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! - -from __future__ import print_function - -from btcrecover import addressset -import argparse, sys, atexit -from os import path - -if __name__ == "__main__": - - parser = argparse.ArgumentParser() - parser.add_argument("--datadir", metavar="DIRECTORY", help="the Bitcoin data directory (default: auto)") - parser.add_argument("--update", action="store_true", help="update an existing address database") - parser.add_argument("--force", action="store_true", help="overwrite any existing address database") - parser.add_argument("--no-pause", action="store_true", default=len(sys.argv)>1, help="never pause before exiting (default: auto)") - parser.add_argument("--no-progress",action="store_true", default=not sys.stdout.isatty(), help="disable the progress bar (shows cur. blockfile instead)") - parser.add_argument("--version", "-v", action="version", version="%(prog)s " + addressset.__version__) - parser.add_argument("dbfilename", nargs="?", default="addresses.db", help="the name of the database file (default: addresses.db)") - - # Optional bash tab completion support - try: - import argcomplete - argcomplete.autocomplete(parser) - except ImportError: - pass - - args = parser.parse_args() - - if not args.no_pause: - atexit.register(lambda: raw_input("\nPress Enter to exit ...")) - - if not args.update and not args.force and path.exists(args.dbfilename): - sys.exit("Address database file already exists (use --update to update or --force to overwrite)") - - if args.datadir: - blockdir = args.datadir - elif sys.platform == "win32": - blockdir = path.expandvars(r"%APPDATA%\Bitcoin") - elif sys.platform.startswith("linux"): - blockdir = path.expanduser("~/.bitcoin") - elif sys.platform == "darwin": - blockdir = path.expanduser("~/Library/Application Support/Bitcoin") - else: - sys.exit("Can't automatically determine Bitcoin data directory (use --datadir)") - blockdir = path.join(blockdir, "blocks") - - addressset.create_address_db(args.dbfilename, blockdir, args.update, progress_bar=not args.no_progress) + +# create-address-db.py -- Bitcoin address database creator for seedrecover +# Copyright (C) 2017 Christopher Gurnee +# 2021 Stephen Rothery +# +# This file is part of btcrecover. +# +# btcrecover is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# btcrecover is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +# If you find this program helpful, please consider a small +# donation to the developer at the following Bitcoin address: +# +# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 +# +# Thank You! + +import compatibility_check + +from btcrecover import addressset +import sys,argparse, atexit +from os import path + +__version__ = "1.11.0-CryptoGuide" + +if __name__ == "__main__": + print("Starting CreateAddressDB", __version__) + + parser = argparse.ArgumentParser() + parser.add_argument("--datadir", metavar="DIRECTORY", help="the Bitcoin data directory (default: auto)") + parser.add_argument("--update", action="store_true", help="update an existing address database") + parser.add_argument("--force", action="store_true", help="overwrite any existing address database") + parser.add_argument("--no-pause", action="store_true", default=len(sys.argv)>1, help="never pause before exiting (default: auto)") + parser.add_argument("--no-progress",action="store_true", default=not sys.stdout.isatty(), help="disable the progress bar (shows cur. blockfile instead)") + parser.add_argument("--version", "-v", action="version", version="%(prog)s " + addressset.__version__) + parser.add_argument("--dbyolo", action="store_true", help="Disable checking whether input blockchain is compatible with this tool...") + parser.add_argument("--addrs_to_text", action="store_true", help="Append all found addresses to address.txt in the working directory while creating addressDB (Useful for debugging, will slow down AddressDB creation and produce a really big file, about 4x the size of the required AddressDB, about 32GB as of Jan 2020)") + parser.add_argument("--dblength", default=31, help="The Maximum Number of Addresses the AddressDB can old, as a power of 2. Default = 31 ==> 2^31 Addresses. (Enough for BTC Blockchain @ April 2021", type=int) + parser.add_argument("--first-block-file", default=0, help="Start creating the AddressDB from a specific block file (Useful to keep DB size down)", type=int) + parser.add_argument("--blocks-startdate", default="2009-01-01", help="Ignore blocks earlier than the given date, format must be YYYY-MM-DD (Useful to keep DB size down)") + parser.add_argument("--blocks-enddate", default="3000-12-31", help="Ignore blocks later than the given date, format must be YYYY-MM-DD (Useful to keep DB size down)") + parser.add_argument("--dbfilename", nargs="?", default="addresses.db", help="the name of the database file (default: addresses.db)") + parser.add_argument("--inputlistfile", help="The file that contains a list of addresses that will be used to create the addressDB file") + parser.add_argument("--multifileinputlist", action="store_true", help="Whether to try and load multiple sequential input list files (incrementing the last 4 letters of file name from 0 to 9998)") + parser.add_argument("--forcegzip", action="store_true", help="Treat input list files as gzip'd files regardless of their extension") + + + # Optional bash tab completion support + try: + import argcomplete + argcomplete.autocomplete(parser) + except ImportError: + pass + + args = parser.parse_args() + + if not args.no_pause: + atexit.register(lambda: input("\nPress Enter to exit ...")) + + if not args.update and not args.force and path.exists(args.dbfilename): + sys.exit("Address database file already exists (use --update to update or --force to overwrite)") + + if args.datadir: + blockdir = args.datadir + elif sys.platform == "win32": + blockdir = path.expandvars(r"%APPDATA%\Bitcoin") + elif sys.platform.startswith("linux"): + blockdir = path.expanduser("~/.bitcoin") + elif sys.platform == "darwin": + blockdir = path.expanduser("~/Library/Application Support/Bitcoin") + else: + sys.exit("Can't automatically determine Bitcoin data directory (use --datadir)") + blockdir = path.join(blockdir, "blocks") + + addressset.create_address_db(args.dbfilename, blockdir, args.dblength, args.blocks_startdate, args.blocks_enddate, args.first_block_file, args.dbyolo, args.addrs_to_text, args.update, progress_bar=not args.no_progress, addresslistfile = args.inputlistfile, multiFile = args.multifileinputlist, forcegzip = args.forcegzip) diff --git a/derivationpath-lists/ADA.txt b/derivationpath-lists/ADA.txt new file mode 100644 index 000000000..168f33ba8 --- /dev/null +++ b/derivationpath-lists/ADA.txt @@ -0,0 +1,11 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +icarus:1852'/1815'/0' #Shelley Era Wallets (Adalite, Yoroi, Deadalus, Atomic, Ellipal, Trezor T (When using a seed length less than 24 words) +ledger:1852'/1815'/0' #Shelley Era Wallets (Adalite, Yoroi) when used with a Ledger Nano +trezor:1852'/1815'/0' #Shelley Era Wallets (Adalite, Yoroi) when used with a Trezor T (Only needed if 24 word seed used, if 12 word seed used then Icarus derivation works fine) +#byron:1852'/1815'/0' #Byron Era Yoroi Wallets (Currently unsupported) \ No newline at end of file diff --git a/derivationpath-lists/BCH.txt b/derivationpath-lists/BCH.txt new file mode 100644 index 000000000..01bf36b68 --- /dev/null +++ b/derivationpath-lists/BCH.txt @@ -0,0 +1,9 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/0'/0'/0 #Bitcoin BIP44 (Pre-Fork) +m/44'/145'/0'/0 #BCH BIP44 (Post-Fork) \ No newline at end of file diff --git a/derivationpath-lists/BTC.txt b/derivationpath-lists/BTC.txt new file mode 100644 index 000000000..799e99c36 --- /dev/null +++ b/derivationpath-lists/BTC.txt @@ -0,0 +1,17 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/0'/0'/0 #Bitcoin BIP44 (Legacy, generally addresses starting with 1) +m/49'/0'/0'/0 #Bitcoin BIP49 (Segwit, generally addresses starting with 3) +m/84'/0'/0'/0 #Bitcoin BIP84 (Native Segwit, generally addresses starting with bc1q) +m/86'/0'/0'/0 #Bitcoin BIP86 (Taproot, generally addresses starting with bc1p) +m/0'/0 #BRD Wallet (and other older BTC wallets) +#m/0 #Coldcard Address Explorer Default Legacy Account (Removed as default in mid 2021) +#m/86'/0'/0' #Taproot addresses for OrdinalsWallet + +# This is also the pathlist you want to use for "Generic" BIP39 wallets and altcoins (Wallet-Type BIP39 or Bitcoin Standard) +#m/44'/330'/0'/0 #Terra Station \ No newline at end of file diff --git a/derivationpath-lists/DASH.txt b/derivationpath-lists/DASH.txt new file mode 100644 index 000000000..65b2a1280 --- /dev/null +++ b/derivationpath-lists/DASH.txt @@ -0,0 +1,8 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/5'/0'/0 #Default BIP44 P2PKH \ No newline at end of file diff --git a/derivationpath-lists/DGB.txt b/derivationpath-lists/DGB.txt new file mode 100644 index 000000000..1fd3f81e0 --- /dev/null +++ b/derivationpath-lists/DGB.txt @@ -0,0 +1,10 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/20'/0'/0 #BIP44 (Legacy) +m/49'/20'/0'/0 #BIP49 (Segwit) +m/84'/20'/0'/0 #BIP84 (Native Segwit) \ No newline at end of file diff --git a/derivationpath-lists/DOGE.txt b/derivationpath-lists/DOGE.txt new file mode 100644 index 000000000..889fac0ce --- /dev/null +++ b/derivationpath-lists/DOGE.txt @@ -0,0 +1,8 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/3'/0'/0 #BIP44 (Legacy) \ No newline at end of file diff --git a/derivationpath-lists/ETH.txt b/derivationpath-lists/ETH.txt new file mode 100644 index 000000000..247fe95b1 --- /dev/null +++ b/derivationpath-lists/ETH.txt @@ -0,0 +1,27 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/60'/0' #Ethereum Coinomi & Ledger Legacy +m/44'/60'/0'/0 #Ethereum MEW, Trezor, etc (Binance Smart Chain uses this path too...) +m/44'/1'/0'/0 #Trezor Testnets (Selectable in Metamask, etc) + +# Other Eth clones that can be used with the Eth wallet type +#m/44'/61'/0'/0 #ETC +#m/44'/60'/160720'/0' #ETC Coinomi & Ledger Legacy +#m/44'/137'/0'/0 #RSK +#m/44'/178'/0'/0 #POA +#m/44'/242'/0'/0 #NIM (Nimiq) +#m/44'/425'/0'/0 #AION +#m/44'/500'/0'/0 #THETA +#m/44'/714'/0'/0 #Smart Chain Legacy +#m/44'/818'/0'/0 #VET +#m/44'/820'/0'/0 #CLO +#m/44'/889'/0'/0 #TOMO +#m/44'/1001'/0'/0 #TT +#m/44'/5718350'/0'/0 #WAN +#m/44'/6060'/0'/0 #GO +#m/44'/3030'/0'/0 #Hendra ECDSA EVM (Doesn't work for 25519 keys) \ No newline at end of file diff --git a/derivationpath-lists/Electrum.txt b/derivationpath-lists/Electrum.txt new file mode 100644 index 000000000..ffaded6ab --- /dev/null +++ b/derivationpath-lists/Electrum.txt @@ -0,0 +1,7 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +m/0 #Electrum Legacy Wallets (BTC + Alts) +m/0'/0 #Electrum Segwit Wallets ( BTC + Alts) & Legacy 2FA Keystore 1 +# m/1'/0 #Electrum Segwit Wallets ( BTC + Alts) & Legacy 2FA Keystore 2 \ No newline at end of file diff --git a/derivationpath-lists/GRS.txt b/derivationpath-lists/GRS.txt new file mode 100644 index 000000000..2d3a06ea2 --- /dev/null +++ b/derivationpath-lists/GRS.txt @@ -0,0 +1,10 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/17'/0'/0 #BIP44 (Legacy) +m/49'/17'/0'/0 #BIP49 (Segwit) +m/84'/17'/0'/0 #BIP84 (Native Segwit) \ No newline at end of file diff --git a/derivationpath-lists/HEDERA.txt b/derivationpath-lists/HEDERA.txt new file mode 100644 index 000000000..95a2adf0c --- /dev/null +++ b/derivationpath-lists/HEDERA.txt @@ -0,0 +1,2 @@ +# Default Hedera ed25519 derivation path (HIP-32 compliant) +m/44'/3030'/0'/0' diff --git a/derivationpath-lists/LTC.txt b/derivationpath-lists/LTC.txt new file mode 100644 index 000000000..65d0ac63b --- /dev/null +++ b/derivationpath-lists/LTC.txt @@ -0,0 +1,11 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/2'/0'/0 #Litecoin BIP44 (Legacy, generally addresses starting with L) +m/49'/2'/0'/0 #Litecoin BIP49 (Segwit, generally addresses starting with M) +m/84'/2'/0'/0 #Litecoin BIP84 (Native Segwit, generally addresses starting with ltc1) +m/44'/60'/0'/0 #Atomic wallet... (WTF... Avoid this wallet...) \ No newline at end of file diff --git a/derivationpath-lists/MONA.txt b/derivationpath-lists/MONA.txt new file mode 100644 index 000000000..8827eb923 --- /dev/null +++ b/derivationpath-lists/MONA.txt @@ -0,0 +1,10 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/22'/0'/0 #BIP44 (Legacy) +m/49'/22'/0'/0 #BIP49 (Segwit) +m/84'/22'/0'/0 #BIP84 (Native Segwit) \ No newline at end of file diff --git a/derivationpath-lists/VTC.txt b/derivationpath-lists/VTC.txt new file mode 100644 index 000000000..96b3afee9 --- /dev/null +++ b/derivationpath-lists/VTC.txt @@ -0,0 +1,10 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/28'/0'/0 #BIP44 (Legacy) +m/49'/28'/0'/0 #BIP49 (Segwit) +m/84'/28'/0'/0 #BIP84 (Native Segwit) \ No newline at end of file diff --git a/derivationpath-lists/XRP.txt b/derivationpath-lists/XRP.txt new file mode 100644 index 000000000..7283c6f63 --- /dev/null +++ b/derivationpath-lists/XRP.txt @@ -0,0 +1,8 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/144'/0'/0 #BIP44 (Legacy) \ No newline at end of file diff --git a/derivationpath-lists/ZIL.txt b/derivationpath-lists/ZIL.txt new file mode 100644 index 000000000..8812a7d56 --- /dev/null +++ b/derivationpath-lists/ZIL.txt @@ -0,0 +1,8 @@ +# Lines starting with # are ignored +# To Enable a derivation path, remove the # from the start of the line... +# To Disable a derivation path, add # at the start of the line... (This will improve performance) +# +# To check the second (or third) account for a given wallet type, you can generally increment the last number +# Eg: Ledger Eth - Second Account would be m/44'/60'/1' +# +m/44'/313'/0'/0 #Non-Hardened derivation, used in Zillet, Atomic Wallet (Not Ledger Nano) \ No newline at end of file diff --git a/docs/BIP39_descrambling_seedlists.md b/docs/BIP39_descrambling_seedlists.md new file mode 100644 index 000000000..a1a7a887a --- /dev/null +++ b/docs/BIP39_descrambling_seedlists.md @@ -0,0 +1,25 @@ +# Token Lists, Password Lists and BIP39 Seed Phrases + +## Tokenlists +The same "token list" functionality that can be used for creating passwords can also be used for creating seed phrases. + +This feature can be used to unscramble seed phrases where the words of the passphrase are available, but the ordering is unknown. (This is currently only really practical with a 12 word seed phrase, though is also usable for a 24 word seed where the position of 12 of the words is known) + +The syntax for creating these files is identical and information about that can be found here: [The Tokenlist File](tokenlist_file.md) + +An example of a file which has 6 characters of of known position and 6 unknown can be found here: [Sample TokenList](https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/test/test-listfiles/SeedTokenListTest.txt) + +An example command that will use this tokenlist is: +`python3 seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --tokenlist ./btcrecover/test/test-listfiles/SeedTokenListTest.txt` + +**It should be noted that you will need to specify the mnemonic length and the language when using this method** [Supported languages can be found here](https://github.com/3rdIteration/btcrecover/tree/master/btcrecover/wordlists) + +BTCRecover can also print the seeds that will be tested via the `--listpass` command, something that can be useful for debugging your tokenlist [See here for more info about seedlists](passwordlist_file.md) from a tokenlist... (Also useful if you will be generating lots of seed phrases, though this currently just dumps out text files that will get very large, very quickly... Will optimise this a bit in the future) + +## Seedlists +The "passwordlist" (See [here](passwordlist_file.md)) functionality can also be used with seedphrases through the `--seedlist` argument. + +The key difference from the password list is that while you still simply list one seed phrase per line, you will also need to format them in the same style that are exported via the `--listpass` command. This is to make it possible for the output of the tokenlst step of this tool to be directly used by the passwordlist step. See [Sample Seedlist](https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/test/test-listfiles/SeedListTest.txt) + +Example Usage for SeedList (Seedlist created using listseeds as the output from the token list command above): +`python3 seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --seedlist ./btcrecover/test/test-listfiles/SeedListTest.txt` diff --git a/docs/CREDITS.md b/docs/CREDITS.md index ed1f17db2..d7af1f8f2 100644 --- a/docs/CREDITS.md +++ b/docs/CREDITS.md @@ -4,18 +4,22 @@ respective authors, and the libraries themselves may only be distributed under the terms of their respective licenses. In alphabetical order, this includes but is not limited to: - * aespython, please see [aespython/README.txt](aespython/README.txt) for + * aespython, please see [aespython/README.txt](https://github.com/3rdIteration/btcrecover/blob/master/lib/aespython/README.txt) for more information - * Passlib, please see [passlib/README.txt](passlib/README.txt) for more information +* BitcoinLib, please see [bitcoinlib/README.rst](https://github.com/3rdIteration/btcrecover/blob/master/lib/bitcoinlib/README.rst) for more information - * progressbar, please see [progressbar/README.txt](progressbar/README.txt) +* Cashaddress, please see [cashaddress/README.md](https://github.com/3rdIteration/btcrecover/blob/master/lib/cashaddress/README.md) for more information + + * Passlib, please see [passlib/README](https://github.com/3rdIteration/btcrecover/blob/master/lib/passlib/README) for more information + + * progressbar, please see [progressbar/README.txt](https://github.com/3rdIteration/btcrecover/blob/master/lib/progressbar/README.txt) for more information - * bitcoinj wallet protobuf, please see [wallet.proto](wallet.proto) + * bitcoinj wallet protobuf, please see [wallet.proto.bitcoinj](https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/wallet.proto.bitcoinj) for more information - * sha512-kernel.cl, please see [sha512-bc-kernel.cl](sha512-bc-kernel.cl) + * sha512-kernel.cl, please see [sha512-bc-kernel.cl](https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/opencl/sha512-bc-kernel.cl) for more information diff --git a/docs/Creating_and_Using_AddressDB.md b/docs/Creating_and_Using_AddressDB.md new file mode 100644 index 000000000..8fb146d4d --- /dev/null +++ b/docs/Creating_and_Using_AddressDB.md @@ -0,0 +1,197 @@ +# Recovery with an Address Database + +## Background +When trying to recover BIP39/44 wallets, *seedrecover.py* and *btcrecover.py* tries different guesses based on the seed you entered, it needs a way to determine which seed guess is correct. Normally it uses each seed guess to create a master public key (an *mpk*) and compare it to the mpk you entered, or to create Bitcoin addresses and compare them to the addresses you entered. If you have neither your mpk nor any of your addresses, it's still possible to use *seedrecover.py* but it is more complicated and time consuming. **The main time cost in this process is in downloading the blockchain and generating the AddressDB, the actual checking part of the process runs at about the same speed regardless of whether it is being tested against a single address or an addressDB with 600,000 addresses in it... So if you are even a bit unsure about the addresses your wallet used, an AddressDB is very worthwhile** + +This works by generating addresses, just as above, and then looking for each generated address in the entire blockchain. In order to do this, you must first create a database of addresses based on the blockchain. + +There are two ways that an AddressDB can be generated, either through directly parsing raw blockchain data, or through processing a file containing a list of addresses. (This list of addresses can include any address types that BTCRecover supports, including addresses from multiple coin types) + +## Pre-Made AddressDB Files +**Note: AddressDB files are not compatible between Python2 and Python3 branches of BTCRecover. Make sure you download the right one. (The master branch of this Github is all Python3 now...)** + +I have created and uploaded AddressDatabases for some supported chains and will update them periodically. + +**[You can download them from my website here...](https://cryptoguide.tips/btcrecover-addressdbs/)** (You can then unzip them and use the --addressdb to include the full path and filename to tell seedrecover.py or btcrecover.py where to look) + +## Parameters to Manage AddressDB Size + +**dblength** + +This tool creates a database file where you need to specify its maximum size beforehand. This maximum number of addresses is given as a power of 2, eg: --dblength 30 makes space about for 2^30 addresses, just under a billion... Basically, if there are more addresses in the blockchain than room in the file, the program will just crash, so you may need to re-run it and increase --dblength by one. It defaults to 30, which creates an ~8GB file and is enough for the Bitcoin Blockchain in Nov 2018. (I plan to change this behavior so that by default it will start small and retry a few times as required after the Python3 move) **The thing is that the AddressDB file size depends on the max number of addresses it can accomodate, not how many are used.** What this means is that if you are generating an addressDB for a smaller blockchain like Vertcoin, you can get away with specifying a smaller dblength to save space. If you leave it as the defaults, you will end up with an 8GB file when a ~30mb file would have worked. **Though if you have plenty of HDD space, then it doesn't matter** + +A rought guide of the blockchain, AddressDB size and optimal parameters as at Jan 2021 is: + +| Coin | Blockchain Size | AddressDB Size | Required DBLength | +| -------------|:---------------:| ---------------:|------------------:| +| Bitcoin | 561 GB | 16 GB | 31 | +| Bitcoin Cash | 155 GB | 4 GB | 29 | +| Litecoin | 133 GB | 4 GB | 29 | +| Vertcoin | 5 GB | 32 MB | 22 | +| Monacoin | 2.5 GB | 32 MB | 22 | +| Ethereum | N/A (AddressList from BigQuery with ~250 million addresses) | 4 GB | 29 +| Dogecoin | N/A (Addresslist from BigQuery with ~60 million addresses) | 1GB | 27 | + +_If in doubt, just download the full blockchain and parse it in it entritiy... The default will be fine..._ + +**Limiting Date Range for AddressDB Creation** + +It is possible to create an address database that includes only addresses for transactions that happened between specific dates. This can be useful in that it requires less additional space for the AddressDB file and also uses significantly less ram. (Eg: You may select to only consider addresses that were used after you ordered your hardware wallet) This is done via the --blocks-startdate BLOCKS_STARTDATE and --blocks-enddate BLOCKS_ENDDATE arguments, with the date in the format of YYYY-MM-DD + +**Skipping X Number of Block Files in AddressDB Creation** + +It is also possible to tell the AddressDB creation script to start processing at a certain blockfile. This is helpful to speed up the processing of larger blockchains. (Eg: If you only wanted the addresses used in 2018 for Bitcoin) This is done via --first-block-file FIRST_BLOCK_FILE, with FIRST_BLOCK_FILE being the number of the block file. **This feature won't warn you if you tell it to start counting blocks AFTER the start-date if used with --blocks-startdate** + +## Creating an AddressDB from Blockchain Data + +You can generate an addressDB by parsing raw blockchain data from: +* Bitcoin +* Bitcoin Cash +* Litecoin +* Vertcoin +* Monacoin + +It may also work with other 'bitcoin like' blockchains via attempting to foce it via the --dbyolo flag. (You will know that it is successfully parsing the blockchain if you see the number of addresses found increasing as it processes) + +I have tested it and confirmed that it **doesn't** work with +* Dogecoin +* Verge +* Zcash and Zencash +* Monero +* Ethereum + +For these blockchains, you will need to obtain a list of addresses (through something like Google BigQuery) and generate the addressDB from this list. + +**Altcoin Blockchains** + +This tool is tested to work with the blockchains specified above. By default, it will scan the default Bitcoin install directory and use that. If you have installed Bitcoin somewhere else, or you want to create an AddressDB from an alternative blockchain, you will need to manually specifiy its location with the --datadir argument. + +The question of which blockchain you want to use comes down to your personal situation. That said, if you have a BIP39 wallet that you know you used to store Litecoin or Vertcoin at some point, then you may prefer to start by downloading and using one of these chains rather than downloading the full BTC blockchain. Your BIP39/44 wallet will use the same seed for all currencies, so it doesn't matter which one you use to recover your seed. + +**Examples to Reproduce** + +If you want to run some tests against an AddressDB, there are for test DBs that are in the [./btcrecover/test/test-addressdbs](https://github.com/3rdIteration/btcrecover/tree/master/btcrecover/test/test-addressdbs) folder of this project. Basically they are small because they only contain 24hr hours worth of addresses from each block. (They were created with the --blocks-startdate and enddate arguments) + +You can run a test using one of these databases with the command: + + `python seedrecover.py --no-dupchecks --addr-limit 2 --bip32-path "m/44'/28'/1'/0" --big-typos 1 --addressdb ./btcrecover/test/test-addressdbs/addresses-VTC-Test.db --wallet-type bip39 +` + +And the seed with the number 1 instead of the first word... + + `1 entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby +` + +You can find more examples of tests that use the small AddressDBs in the unit tests covered in [test_seeds.py](https://github.com/3rdIteration/btcrecover/blob/master/btcrecover/test/test_seeds.py) , just search for the methods starting with "test_addressdb_" and the parameters will list the addressDB limit, test phrase, derivation path and AddressDB used. + +**Steps to Create an AddressDb from the Blockchain Data:** + 1. You must use a computer with enough space for the full blockchain that you want to process and RAM equal to double the AddressDB size that you will end up with (This is an extremely generous estimate, you will likely be fine with less, but pretty much need to have at least as much as the AddressDB you want to create) . You must have the 64-bit version of Python installed. (Other smaller blockchains require significantly less space and RAM) + + 2. Install a full-node client for your blockchain of choice, such as [Bitcoin Core](https://bitcoincore.org/), [Bitcoin ABC](https://bitcoinabc.org), [Litecoin Core](https://litecoin.org), [Vertcoin](https://vertcoin.org/download-wallet/), [Monacoin Core](https://monacoin.org/). (A lite-client like Electrum, etc, won't work for this...) + + 3. Start your full-node client and allow it to fully sync. Depending on your Internet connection and your computer, fully syncing a node can take one or more days. Starting `bitcoin-qt` (or `bitcoind`) with the `-dbcache #` option can help: the `#` is the amount of RAM, in MB, to use for the database cache. If your computer has at least 8 GB of RAM, giving up to `4000` MB to the `-dbcache` will speed things up. Installing Bitcoin on a computer with an SSD can also help. + + 4. Once your full-node client is synced, close the full-node client software. + + 5. (On OS X, rename the `create-address-db.py` script file to `create-address-db.command`.) Double-click on the `create-address-db.py` script (in the same folder as `seedrecover.py`) to build the address database using the fully-synced blockchain (it will be saved into the same directory as `create-address-db.py` with the name `addresses.db`) . This process will take about one hour, and use about 4 GB of both RAM and drive space. + + 6. Follow the steps listed in the [Running *seedrecover.py*](Seedrecover_Quick_Start_Guide.md#running-seedrecoverpy) section, except that when you get to the address entry window in step 4, click `Cancel`. + + 7. For the next step, you still need to choose an address generation limit. This should be the number of unused addresses you suspect you have at the beginning of your wallet before the first one you ever used. If you're sure you used the very first address in your wallet, you can use `1` here, but if you're not so sure, you should choose a higher estimate (although it may take longer for *seedrecover.py* to run). + +Note that running with an AddressDB will use about the same amount of RAM as the size of the AddressDB file while it is running with an address database. (Eg: Full Bitcoin AddressDB will require about 8.5gb of RAM as of Nov 2019) + +## Creating an AddressDB from an Address List + +An alternative way to create an addressDB is to use a list of addresses. (eg: A list of all Eth addresses from something like Google BigQuery) + +You simply need to specify the input list using the --inputlist parameter as well as specify the dblength that you want to use. (Otherwise it will default to 30, creating an 8gb file) You will likely also need the --multifileinputlist so that you can automatically include a list of files automatically created when you export data from bigquery to Google Cloud Storage. + +If you want to combine addresses from multiple lists, or add a list of addresses to an existing blockchain generated addressDB, you can do this with the --update argument. + +Adding a file with about ~10 million addresses will take about a minute... (Based on performance from BigQuery Eth data) + +### Generating Address Lists from Google BigQuery + +_**Note:** Data on Google BigQuery is only updated every 1-2 months, sometimes less often, so be sure to look at the "Last Modified" information for the dataset that you are using to generate an AddressDB to ensure that it will include transactions related to your wallet... (Eg: That you made at least one transaction prior to the "last modified" date)_ + +**Useful Google BigQuery Queries** + +[All BTC Addresses](https://console.cloud.google.com/bigquery?sq=871259226971:05c3cbf256dd43a898f5168b94bc66cc) + +[All Eth Addresses](https://console.cloud.google.com/bigquery?sq=871259226971:c6370cf863224be1942ecfdf03e0f0ca) + +[All Doge Addresses](https://console.cloud.google.com/bigquery?sq=871259226971:c130730990e94212bf20b3dea5c4c815) + +[All BCH Addresses](https://console.cloud.google.com/bigquery?sq=871259226971:1cb1a218b17d4498bb3d9103e5b2fb3a) + +[All LTC Addresses](https://console.cloud.google.com/bigquery?sq=871259226971:13e998b9bf864df8b7c0772f4913b28d) + +### Generating Address Lists from Blockchair Database Dumps +Blockchair distribute a range of different database dumps, specifically lists of addresses and balance. They can be found here: + +The .tsv.gz files can be directly used to create address databases without decompressing the file via the --inputlist argument. + +**Note: These lists of addresses only include addresses that currently have a balance, as opposed to the other methods here which will include all addresses which have ever had a balance. What this means is that if you use this data from blockchair, you may run in to issues with address-generation-limits. (Which are normally not a consideration when using address databases)** + +### Generating Address Lists using Ethereum-ETL +Confirmed working for: +* Binance Smart Chain with Geth Node installed as per: + +For EVM type chains (eg: Binance Smart Chain), another option is to use the Ethereum-ETL tool. This allows you to query a full node (Running Geth or Parity, or a fork of these) and retrieve human readable CSV data representing transations. + +Once you have a Geth-Like node running, you can retrieve ETL data with a command like: + +`` +ethereumetl export_blocks_and_transactions --start-block STARTBLOCKNUMBER --end-block ENDBLOCKNUMBER --provider-uri http://FULLNODEIP:8545 --blocks-output LOCAL_BLOCKS_CSV_FILE --transactions-output LOCAL_TRANSACTIONS_CSV_FILE +`` + +Once you exported the transactions, you can then use the `addrListsFromETLTransactions.py` file in the `utilities` folder within this repository to produce files containing lists of addresses. These address lists can then be used to create an addressDB using the same process covered earlier. + +The key thing to understand with this approach is that you will need several TB worth of disk space to store/run and several TB worth of additional space for the full Ethereum ETL output. (So you probably want about 10TB of space...) + +### Checking/Validating AddressDBs +You can use the check-address-db.py file to test any addresslist file for whether it includes any given addresses. + +For example, you could validate that the Dogecoin AddressDB (downloadable above) contains a few specific addresses with the command: + + python check-address-db.py --dbfilename "E:\CryptoGuide\OneDrive\AddressDBs (Mega)\addresses-DOGE.db" --checkaddresses DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T DFgLZmxFnzv2wR4GAGS3GeHvoEeSkz9ubU DKTHYZwd81xT7qJF33YRi7ftDkvouGdxxN + +This will produce the following output + + Starting CheckAddressDB 1.9.0-CryptoGuide + Loading address database ... + Loaded 60750752 addresses from database ... + DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T Found! + DFgLZmxFnzv2wR4GAGS3GeHvoEeSkz9ubU Found! + DKTHYZwd81xT7qJF33YRi7ftDkvouGdxxN Found! + +**Checklist File** + +The BTCRecover repository comes bundled with some basic lists of addresses that can be used to check that an addressDB contains addresses which were first seed over a specific time interval. These addresses were randomly selected off the blockchain and are spaced at approximately 6 month intervals. (So can be used to ensure that a given addressDB roughly covers the dates you need) + +For example, you could validate that the Dogecoin AddressDB (downloadable above) contains addresses through to Feb 2021 with the command. + + python check-address-db.py --dbfilename addresses-DOGE.db --checkaddresslist ./addressdb-checklists/DOGE.txt + +This will produce the following output + + Starting CheckAddressDB 1.9.0-CryptoGuide + Loading address database ... + Loaded 60750752 addresses from database ... + Loading: ./addressdb-checklists/DOGE.txt + DMQ6uuLAtNoe5y6DCpxk2Hy83nYSPDwb5T Found! First Seen 2021-01-31 + DFgLZmxFnzv2wR4GAGS3GeHvoEeSkz9ubU Found! First seen 2020-06-29 + DKTHYZwd81xT7qJF33YRi7ftDkvouGdxxN Found! First seen 2019-12-30 + DPPg5BVqn7Ck5YVf6ei7NbXGVPDSzXnCBL Found! First seen 2019-05-17 + DBbTFW9PZJj9EsXu5Ji59Tp6ZdKNrTZmWq Found! First seen 2018-12-05 + DFJRDVzjk7NPbApWsLDreML7RDawp8UmoF Found! First seen 2018-05-16 + D9dWXJjYb4HDrXpdef234GHDDggrnGsfxm Found! First seen 2017-11-05 + D6A894uLhQjwSRpEroPMop4MPpUL4BZZHc Found! First seen 2017-05-19 + DGVxem7KdNBCJWygpRcypS5pMJgJVRZEXD Found! First seen 2016-12-25 + DMPHyer3WdKrSmwmFarXtXCxbbp4BMwo9J Found! First seen 2016-05-22 + DRusoAd1Q9PJq3KpkhXjpZAoCqdQzGS6AH Found! First seen 2015-12-29 + D6sxvQRSriU4pkozdYxDVRKRmoRYCVmqKv Found! First seen 2015-05-10 + DNULsd2gbojENHtRRx45PUWvPgkrbL2vjE Found! First seen 2014-12-15 + D5mrYgNeLwVXFyy9t9NhBpTqVaa58gUYAC Found! First seen 2014-04-29 + DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8 Found! First seen 2013-12-07 diff --git a/docs/Decrypting_dumping_walletfiles.md b/docs/Decrypting_dumping_walletfiles.md new file mode 100644 index 000000000..e86d2bdde --- /dev/null +++ b/docs/Decrypting_dumping_walletfiles.md @@ -0,0 +1,170 @@ +# Decrypting and Dumping Wallet Files + +## Background +Some of the wallets types that BTCRecover supports are no longer maintained or safe to use (Eg: Multibit), can be difficult for some users to set up and use (eg: Bitcoin Core) or may have bugs in the wallet itself that prevent users who have the correct wallet and passwords from accessing their funds. (Eg: Some old Blockchain.com or blockchain.info wallets) Never mind that there might be some situations where the value of the funds are such that importing these recovered wallets back in to a hot wallet might be a really bad idea... + +You may also just be interested in seeing what is in there or find this useful in terms of debugging... + +As such, BTCRecover supports the ability to dump the raw decrypted wallet file or to dump the raw private keys in a format that can be imported directly in to wallets like Electrum which allows the funds to be securely moved through offline signing... + +Support for dumping additional wallet types will be added over time... + +Generally there are two commands used to dump wallet files. + + --dump-wallet FILENAME + + --dump-privkeys FILENAME + +These above commands can be used as part of a standard recovery to automatically decrypt/dump the wallet if the password is found. + +There are also some extra parameters that mean you can avoid using a passwordlist or tokenlist if you are only interested in dumping/decrypting a wallet file... + + --correct-wallet-password + --correct-wallet-secondpassword + +### Blockchain.com Wallets (Previously known as blockchain.info) + +**Important Note** +Some older blockchain.com wallets (2014-2015 era at least, perhaps more) have a bug where some private keys were incorrectly encoded and saved to the wallet file... (Basically if the hex encoded private key included any leading zeroes, these were left off, leading to private keys that are less than 32 bytes... The current blockchain.com simply rejects these as invalid and assumes an incorrect password...) The symptoms of this are that your wallet may have correctly worked until about 2015/2016, BTCRecover will correctly match the wallet passwords (main password and/or second passwords) yet you will be unable to log in with your wallet at blockchain.com (Perhaps being prompted for a second password at login time, not just when sending funds) nor import old backups of your wallet file in to the platform. Blockchain support will simply dismiss your concerns and insist that you have the wrong password... (And given they are a non-custodial platform, don't actually have any visibility in to your specific wallet file file to be able to debug it...) The solution is to dump the private keys from these wallet files (or keys) and to import them in to something like Electrum. + +#### Decrypting/Dumping with Main Password +**Dumping Wallet File Without a Second Password** + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main password for this wallet is "mypassword""... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-github-v1-1 --dump-wallet blockchain-github-v1-1_main_dump.txt --correct-wallet-password mypassword + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/blockchain-github-v1-1_main_dump.txt" %} +``` + +The file above shows the full contents of the wallet as well, the base58 encoded private keys as well as the private keys that have been converted for both compressed and uncompresssed formats. (One one of these corresponds to the actual wallet address, I could tweak this to only dump one but there is no harm in simply importing both...) + +**Dumping Private Keys for a wallet without a second password** + +You can use the following command to dump the private keys from one of the wallets included in this repository to a file... The main password for this wallet is "mypassword"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-github-v1-1 --dump-privkeys blockchain-github-v1-1_main_privkeys.txt --correct-wallet-password mypassword + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/blockchain-github-v1-1_main_privkeys.txt" %} +``` + +You can then copy/paste the contents of this file directly in to Electrum... (Like the examples above, this will include both compressed and uncompressed private keys) + +**Dumping Wallet File (Where there is a second password)** + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main password for this wallet is "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json --dump-wallet blockchain-v2.0-wallet.aes.json_main_dump.txt --correct-wallet-password btcr-test-password + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_main_dump.txt" %} +``` + +This example decrypts using the "main password only", meaning that the body of the file will be decrypted, including addresses, but the private keys will remain encrypted. + + +#### Decrypting/Dumping with Second Password +**Dumping Wallet File with a Second Password** + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main and second passwords for this wallet are "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json --dump-wallet blockchain-v2.0-wallet.aes.json_secondpass_dump.txt --correct-wallet-password btcr-test-password --blockchain-secondpass --correct-wallet-secondpassword btcr-test-password + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_dump.txt" %} +``` + +Like the example above, this wallet dump will include the encrypted private keys, raw decrypted private keys (in base58) and private keys, both compressed and uncompressed, that can be imported directly in to wallets like Electrum. + +**Dumping Private Keys for a wallet with a second password** + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main and second passwords for this wallet are "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json --dump-privkeys blockchain-v2.0-wallet.aes.json_secondpass_privkeys.txt --correct-wallet-password btcr-test-password --blockchain-secondpass --correct-wallet-secondpassword btcr-test-password + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json_secondpass_privkeys.txt" %} +``` + +These can then be copy/pasted directly in to a wallet like Electrum. + +### Coinomi Wallets + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main and second passwords for this wallet are "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/coinomi.wallet.desktop --correct-wallet-password btcr-test-password --dump-privkeys coinomi.wallet.desktop.privkeys.txt + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/coinomi.wallet.desktop.privkeys.txt" %} +``` + +### Metamask Wallets + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main and second passwords for this wallet are "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn --correct-wallet-password btcr-test-password --dump-privkeys metamask.privkeys.txt + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/metamask.privkeys.txt" %} +``` + +### Multibit Classic Wallets Backups (And MultiDoge) +You can dump either .wallet files or key-backup files. + +You can use the following command to decrypt/dump one of the Multibit Classic wallet keyfiles that is included with the repository... The main password for this wallet is "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/multibit-wallet.key --correct-wallet-password btcr-test-password --dump-privkeys multibit-wallet.key.privkeys.txt + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/multibit-wallet.key.privkeys.txt" %} +``` +
+You can also decrypt/dump a .wallet file with the same type of command. + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted --correct-wallet-password btcr-test-password --dump-privkeys multibit.wallet.bitcoinj.encrypted.txt + +Which will produce the following: +``` linenums="1" +{% include "../btcrecover/test/test-wallets/multibit.wallet.bitcoinj.encrypted.txt" %} +``` +
+Likewise, you can decrypt/dump MultiDoge keyfiles with a command like: + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/multidoge-wallet.key --correct-wallet-password btcr-test-password --dump-privkeys multidoge-wallet.key.privkeys.txt + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/multidoge-wallet.key.privkeys.txt" %} +``` + +### Multibit HD Wallets + +You can use the following command to decrypt/dump one of the wallets that is included with the repository... The main password for this wallet is "btcr-test-password"... + + python btcrecover.py --wallet ./btcrecover/test/test-wallets/multibithd-v0.5.0/mbhd.wallet.aes --correct-wallet-password btcr-test-password --dump-privkeys mbhd.wallet.aes.privkeys.txt + +This command will produce the following dump. + +``` linenums="1" +{% include "../btcrecover/test/test-wallets/multibithd-v0.5.0/mbhd.wallet.aes.privkeys.txt" %} +``` + diff --git a/docs/Extract_Scripts.md b/docs/Extract_Scripts.md index 5393f754b..57a4409b2 100644 --- a/docs/Extract_Scripts.md +++ b/docs/Extract_Scripts.md @@ -1,4 +1,4 @@ -## *btcrecover* extract scripts ## +# *btcrecover* extract scripts # Sometimes, it is not desirable to run *btcrecover* directly on the computer which stores the target wallet file. For example: @@ -6,123 +6,65 @@ Sometimes, it is not desirable to run *btcrecover* directly on the computer whic * Configuring *btcrecover* to search for your password correctly can be tricky; you might be interested in finding someone who can configure and run *btcrecover* for you on their computer. * You may not trust that *btcrecover* is free from harmful bugs or other malicious behavior. *btcrecover* is open source, and requires no untrustworthy binaries be installed. However it's also a fairly long and complicated Python script, which makes it difficult even for other Python programmers to be certain that it doesn't contain any harmful code (either intentionally malicious or just by accident). -The extract scripts in this directory are relatively short and simple scripts which extract the just enough information from a wallet file to allow *btcrecover* to perform a password search. With the exception of Armory, these scripts never extract enough information to put any of your bitcoin funds at risk, even after the password is found. For Armory, only a single (typically unused) address and private key are extracted, putting only that one address at risk (however please read the [Armory Technical Details](#armory-technical-details) for an important caveat). +The extract scripts in this directory are relatively short and simple scripts which extract the just enough information from a wallet file to allow *btcrecover* to perform a password search. These scripts never extract enough information to put any of your bitcoin funds at risk, even after the password is found. -For more information regarding *btcrecover*, please see [TUTORIAL.md](../TUTORIAL.md). +For more information regarding *btcrecover*, please see [TUTORIAL.md](TUTORIAL.md). -### Download ### +## Download ## -You can download the entire *btcrecover* package from: +You can download the entire *btcrecover* package from: If you'd prefer to download just a single extract script, please select the one for your wallet software from below, then right click and choose “Save link as...” or “Save target as...”: - * Armory - - * Bitcoin Unlimited/Classic/XT/Core - - * Bither - - * Blockchain main password - - * Blockchain second password - - * Electrum 1.x - - * Electrum 2.x - - * mSIGNA - - * MultiBit Classic - - * MultiBit HD - - -If you're on Windows, you will also need to install the latest version of Python 2.7. For Armory wallets, please follow the full instructions in the [Installation Guide](INSTALL.md). For any other wallets, just follow the [instructions to install Python 2.7 here](INSTALL.md#python-27). - - -### Table of Contents ### - - * [Armory](#usage-for-armory) - * [Bitcoin Unlimited/Classic/XT/Core (including pywallet dump files)](#usage-for-bitcoin-unlimitedclassicxtcore) - * [Bither](#usage-for-bither) - * [Blockchain.info](#usage-for-blockchaininfo) - * [Electrum (1.x or 2.x)](#usage-for-electrum) - * [mSIGNA](#usage-for-msigna) - * [MultiBit Classic](#usage-for-multibit-classic) - * [MultiBit HD](#usage-for-multibit-hd) + * Bitcoin Core - + * Bither - + * Blockchain main password - + * Blockchain second password - + * Electrum 1.x - + * Electrum 2.x - + * mSIGNA - + * MultiBit Classic - + * MultiBit HD - +If you're on Windows, you will also need to install the latest version of Python 3.7 or above. For any other wallets, just follow the [instructions to install Python here](INSTALL.md#2-install-python). ---------- - -### Usage for Armory ### - -Open Armory (in offline mode if you like), and take note of the wallet ID whose password you've lost. Open this wallet, and click Receive Bitcoins to display a new Bitcoin address. Add a label to the address, such as "Recovery address - DO NOT USE", and copy the Bitcoin address to someplace temporary (a Notepad document, a Post-It, or wherever). Quit Armory. - -If you've ever used this wallet on more than one computer, the address you just created might already exist. It's up to you to find an unused and unpublished address for this procedure, and to never use this address in the future. You can check if the address has been used in the past at , but you're the only one who might know if you've given this address out to someone else before today. - -After downloading the script, **make a copy of your wallet file into a different folder** (to make it easy, into the same folder as the extract script). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open your Armory folder which contains your wallet files: `%appdata%\Armory`. From here you can copy and paste the wallet file that matches the wallet ID you noted earlier into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your wallet file into the same folder): - - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-armory-privkey.py armory_2dRkxw76K_.wallet extract 1LhkzLtY5drbUxXvsk8LmU1aRFz13EDcp4 - -Of course, you need to replace the wallet file name with yours, and the Bitcoin address with the one you created earlier. You should get a message which looks like this as a result. You should double-check that the address matches the one you just created (along with the label which you gave to it): - - 1LhkzLtY5drbUxXvsk8LmU1aRFz13EDcp4 First:04/19/14 Last:04/19/14 Recovery address - DO NOT USE - - WARNING: once decrypted, this will provide access to all Bitcoin - funds available now and in the future of this one address - - Armory address, encrypted private key, iv, kdf parameters, and crc in base64: - YXI62B+/jb1Pthvjsrh+LlW5PS87FpfdBR3d5G1yWPY0cEUl3D+U2382qq0YkqoBDfnHDda/a3bOay/OKq9UWy/nra5SGyMAAEAAAgAAABiymPHbLR+L8tKm+wpnzDioxV+lMgAwB2SH0hpYvez8w5aWGQ== - -When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-armory-privkey.py*. To continue the example: - - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt - Please enter the data from the extract script - > YXI62B+/jb1Pthvjsrh+LlW5PS87FpfdBR3d5G1yWPY0cEUl3D+U2382qq0YkqoBDfnHDda/a3bOay/OKq9UWy/nra5SGyMAAEAAAgAAABiymPHbLR+L8tKm+wpnzDioxV+lMgAwB2SH0hpYvez8w5aWGQ== - WARNING: an Armory private key, once decrypted, provides access to that key's Bitcoin - ... - Password found: xxxx - -Once your password has been found, it's **strongly** recommended that you make a new wallet for maximum safety. Please read the technical details section below to understand why. - -#### Armory Technical Details #### - -The *extract-armory-privkey.py* script is intentionally short and should be easy to read for any Python programmer. As detailed above, it extracts a single address and private key using the official armoryengine library, putting this one address at risk. However, *without access to the rest of your wallet file*, the rest of your addresses and private keys are not at risk, even after a successful password guess and decryption. - -If someone has one of your (decrypted) private keys and also has or gains access to *any version* of your wallet file (normal or watching only, current or a backup, even if it's encrypted with a different password), then your *entire* wallet has been compromised. For maximum safety, you should make a new wallet and stop using your old wallet to prevent this from occurring. - -Armory automatically pre-generates 100 addresses and private keys before they are needed, which is why you can ask it to display a "new" address without a password. If you've asked for and then used 100 new addresses without providing a password, it's possible that Armory will be unable to provide a new address (without a password) as required by this procedure. If this is the case, you'll have no choice but to choose an already used address. To assist in choosing such an address, you can run `extract-armory-privkey.py list` from the command line to display a list of addresses available in the wallet which include an encrypted private key (including pre-generated addresses that may not be visible via the Armory GUI) along with the first and last known dates of use for each address. These dates of known use do not check the current block chain; you should always check a questionable address on to check it's current balance before you use it with this procedure. - - -### Usage for Bitcoin Unlimited/Classic/XT/Core ### +## Usage for Bitcoin Core ## After downloading the script, **make a copy of your wallet.dat file into a different folder** (to make it easy, into the same folder as *extract-bitcoincore-mkey.py*). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open your Bitcoin folder which contains your wallet.dat file: `%appdata%\Bitcoin`. From here you can copy and paste your wallet.dat file into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your wallet.dat into the same folder): - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-bitcoincore-mkey.py wallet.dat + cd btcrecover-master\extract-scripts + python3 extract-bitcoincore-mkey.py wallet.dat You should get a message which looks like this as a result: Bitcoin Core encrypted master key, salt, iter_count, and crc in base64: lV/wGO5oAUM42KTfq5s3egX3Uhk6gc5gEf1R3TppgzWNW7NGZQF5t5U3Ik0qYs5/dprb+ifLDHuGNQIA+8oRWA== -If you instead have a dump file of a Bitcoin Unlimited/Classic/XT/Core wallet that was created by pywallet, just follow these same instructions except use the *extract-bitcoincore-mkey-from-pywallet.py* script instead. +If you instead have a dump file of a Bitcoin Core wallet that was created by pywallet, just follow these same instructions except use the *extract-bitcoincore-mkey-from-pywallet.py* script instead. When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-bitcoincore-mkey.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > lV/wGO5oAUM42KTfq5s3egX3Uhk6gc5gEf1R3TppgzWNW7NGZQF5t5U3Ik0qYs5/dprb+ifLDHuGNQIA+8oRWA== ... Password found: xxxx -#### Bitcoin Unlimited/Classic/XT/Core Technical Details #### +### Bitcoin Core Technical Details ### -The *extract-bitcoincore-mkey.py* script is intentionally short and should be easy to read for any Python programmer. It opens a wallet.dat file using the Python bsddb.db library (the Berkeley DB library which comes with Python 2.7), and then extracts a single key/value pair with the key string of `\x04mkey\x01\x00\x00\x00`. This key/value pair contains an encrypted version of the Bitcoin Unlimited/Classic/XT/Core “master key”, or mkey for short, along with some other information required to try decrypting the mkey, specifically the mkey salt and iteration count. This information is then converted to base64 format for easy copy/paste, and printed to the screen. +The *extract-bitcoincore-mkey.py* script is intentionally short and should be easy to read for any Python programmer. It opens a wallet.dat file using the Python bsddb.db (Or a Pure Python implementation if this module isn't available) or SQLite, and then extracts a single key/value pair with the key string of `\x04mkey\x01\x00\x00\x00`. This key/value pair contains an encrypted version of the Bitcoin Core “master key”, or mkey for short, along with some other information required to try decrypting the mkey, specifically the mkey salt and iteration count. This information is then converted to base64 format for easy copy/paste, and printed to the screen. The encrypted mkey is useful to *btcrecover*, but it does not contain any of your Bitcoin address or private key information. *btcrecover* can attempt to decrypt the mkey by trying different password combinations. Should it succeed, it and whoever runs it will then know the password to your wallet file, but without the rest of your wallet file, the password and the decrypted mkey are of no use. - -### Usage for Bither ### +## Usage for Bither ## After downloading the script, **make a copy of your wallet file into a different folder** (to make it easy, into the same folder as the extract script). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open the folder which usually contains your wallet file: `%appdata%\Bither`. From here you can copy and paste your wallet file (it's usually named `address.db`), into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming your wallet file is in the same folder): - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-bither-partkey.py address.db + cd btcrecover-master\extract-scripts + python3 extract-bither-partkey.py address.db You should get a message which looks like this: @@ -131,14 +73,14 @@ You should get a message which looks like this: When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-bither-partkey.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > YnQ6PocfHvWGVbCzlVb9cUtPDjosnuB7RoyspTEzZZAqURlCsLudQaQ4IkIW8YE= ... Password found: xxxx -#### Bither Technical Details #### +### Bither Technical Details ### The *extract-bither-partkey.py* script is intentionally short and should be easy to read for any Python programmer. A Bither encrypted private key is 48 bytes long. It contains 32 bytes of encrypted private key data, followed by 16 bytes of encrypted padding. @@ -147,12 +89,15 @@ Because only the last half of the private key is extracted, the private key cann Without access to the rest of your wallet file, it is impossible the decrypted padding could ever lead to a loss of funds. -### Usage for Blockchain.info ### +## Usage for Blockchain.com ## -Locate your Blockchain.info wallet backup file (it's usually named `wallet.aes.json`), and **make a copy of it into a different folder** (to make it easy, into the same folder as the extract script). Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your wallet file into the same folder): +[Firstly you download the wallet file as per the documentation here:](./TUTORIAL.md#downloading-blockchaincom-wallet-files) - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-blockchain-main-data.py wallet.aes.json +When prompted, enter your wallet ID and then approve the login request on the email account associated with the wallet. Once the login is approved, your wallet.aes.json file will be saved to you PC. + +Next you'll need to open a Command Prompt window and type something like this : + + python3 extract-blockchain-main-data.py wallet.aes.json Of course, you need to replace the wallet file name with yours. You should get a message which looks like this as a result: @@ -161,19 +106,19 @@ Of course, you need to replace the wallet file name with yours. You should get a When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-blockchain-main-data.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > Yms6abF6aZYdu5sKpStKA4ihra6GEAeZTumFiIM0YQUkTjcQJwAAj8ekAQ== ... Password found: xxxx -#### Blockchain.info Second Passwords #### +### Blockchain.com Second Passwords ### -If you've enabled the Second Password (also called the double encryption) feature of your Blockchain.info wallet, and if you need to search for this second password, you must start by finding the main password if you don't already have it (see above). Once you have your main password, take your wallet backup file (it's usually named `wallet.aes.json`), and **make a copy of it into a different folder** (to make it easy, into the same folder as the extract script). Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your wallet file into the same folder): +If you've enabled the Second Password feature of your Blockchain.com wallet, and if you need to search for this second password, you must start by finding the main password if you don't already have it (see above). Once you have your main password, take your wallet backup file (it's usually named `wallet.aes.json`), and **make a copy of it into a different folder** (to make it easy, into the same folder as the extract script). Next you'll need to open a Command Prompt window and type something like this : - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-blockchain-second-hash.py wallet.aes.json + cd btcrecover-master\extract-scripts + python3 extract-blockchain-second-hash.py wallet.aes.json Please enter the Blockchain wallet's main password: You need to enter your wallet's main password when prompted so that the extract script can remove the first level of encryption to gain access to the second level of encrypted data. You should get a message which looks like this as a result: @@ -183,8 +128,8 @@ You need to enter your wallet's main password when prompted so that the extract When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-blockchain-second-hash.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > YnM6LeP7peG853HnQlaGswlwpwtqXKwa/1rLyeGzvKNl9HpyjnaeTCZDAaC4LbJcVkxaECcAACwXY6w= ... @@ -192,21 +137,48 @@ When you (or someone else) runs *btcrecover* to search for passwords, you will n Please note that you must either download the entire *btcrecover* package which includes an AES decryption library, or you must already have PyCrypto installed in order to use the *extract-blockchain-second-hash.py* script. -#### Blockchain.info Technical Details #### +### Blockchain.com Technical Details ### -The *extract-blockchain-main-data.py* script is intentionally short and should be easy to read for any Python programmer. This script extracts the first 32 bytes of encrypted data from a Blockchain.info wallet, of which 16 bytes are an AES initialization vector, and the remaining 16 bytes are the first encrypted AES block. This information is then converted to base64 format for easy copy/paste, and printed to the screen. The one encrypted block does not contain any private key information, but once decrypted it does contain a non-sensitive string (specifically the string "guid") which can be used by *btcrecover* to test for a successful password try. +The *extract-blockchain-main-data.py* script is intentionally short and should be easy to read for any Python programmer. This script extracts the first 32 bytes of encrypted data from a Blockchain.com wallet, of which 16 bytes are an AES initialization vector, and the remaining 16 bytes are the first encrypted AES block. This information is then converted to base64 format for easy copy/paste, and printed to the screen. The one encrypted block does not contain any private key information, but once decrypted it does contain a non-sensitive string (specifically the string "guid", "tx_notes", "address_book" or "double") which can be used by *btcrecover* to test for a successful password try. -The *extract-blockchain-second-hash.py* script is a bit longer, but it should still be short enough for most Python programmers to read and understand. After decrypting the first level of encryption of a Blockchain.info wallet, it extracts a password hash and salt which can be used by *btcrecover* to test for a successful password try. It does not extract any of the encrypted private keys. +The *extract-blockchain-second-hash.py* script is a bit longer, but it should still be short enough for most Python programmers to read and understand. After decrypting the first level of encryption of a Blockchain.com wallet, it extracts a password hash and salt which can be used by *btcrecover* to test for a successful password try. It does not extract any of the encrypted private keys. Without access to the rest of your wallet file, the bits of information extracted by these scripts alone do not put any of your Bitcoin funds at risk, even after a successful password guess and decryption. +## Usage for Coinomi ## + +**Note: This only supports wallets that are protected by a password. If you selected "no password", "biometrics" or "password + biometrics" then you will also need information from your phones keystore... (Which may be impossible to retrieve)** + +The first step for Coinomi depends on which platform you are running it on. + +For Windows users, it's simply a case of navigating to %localappdata%\Coinomi\Coinomi\wallets and you will find your wallet files. + +For Android users, you will need to have a rooted phone which will allow you to access the .wallet file in the Coinomi. (It should be found in the folder data\data\com.coinomi.wallet\files\wallets) How to get root access on your particular phone is beyond the scope of this document, but be warned that some methods of rooting your phone will involve a factory reset. + +If there are mulitiple wallets there and you are not sure which is the correct one, the name of each wallet can be found in clear text at the end of the file. [See the test wallets included with this repository in ./btcrecover/test/test-wallets](https://github.com/3rdIteration/btcrecover/tree/master/btcrecover/test/test-wallets) for an example) + +Once you have the file, you can either use it directly with BTCRecover, or you can create an extract. + + python extract-coinomi-privkey.py ../btcrecover/test/test-wallets/coinomi.wallet.android + Coinomi partial first encrypted private key, salt, n, r, p and crc in base64: + Y246uwodSaelErkb7GIYls3xaeX5i5YWtmh814zgsBCx+y8xgjp7Mul0TQBAAAAIAAEASAgdvw== + +## Usage for Dogechain.info ## + +[Firstly you download the wallet file as per the documentation here:](./TUTORIAL.md#downloading-dogechaininfo-wallet-files) + +You can then create an extract script from the downloaded wallet file with the a command like the one below. (Which uses the sample wallet file that is part of the repository) + + python extract-dogechain-privkey.py ../btcrecover\test\test-wallets/dogechain.wallet.aes.json + Dogechain first 16 encrypted bytes, iv, and iter_count in base64: + ZGM6jJzIUd6i9DMEgCFG9JQ1/z4xSamItXAiQnV4AeJ0BwcZznn+169Eb84PFQ3QQ2JGiBMAAGL+4VE= -### Usage for Electrum ### +## Usage for Electrum ## -After downloading the script, **make a copy of your wallet file into a different folder** (to make it easy, into the same folder as the extract script). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open the folder which contains the first wallet file created by Electrum after it is installed: `%appdata%\Electrum\wallets`. From here you can copy and paste your wallet file, usually named `default_wallet`, into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your wallet file into the same folder): +After downloading the script, **make a copy of your wallet file into a different folder** (to make it easy, into the same folder as the extract script). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open the folder which contains the first wallet file created by Electrum after it is installed: `%appdata%\Electrum\wallets`. From here you can copy and paste your wallet file, usually named `default_wallet`, into a separate folder. Next you'll need to open a Command Prompt window and type something like this : - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-electrum2-partmpk.py default_wallet + cd btcrecover-master\extract-scripts + python3 extract-electrum2-partmpk.py default_wallet The example above assumes you have an Electrum 2.x wallet. If it's an Electrum 1.x wallet instead, replace *extract-electrum2-partmpk.py* with *extract-electrum-halfseed.py*. Of course, you'll also need to replace the wallet file name with yours. You should get a message which looks either like this: @@ -220,14 +192,14 @@ Or like this, depending on the wallet details: When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-electrum-halfseed.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > ZWw6kLJxTDF7LxneT7c5DblJ9k9WYwV6YUIUQO+IDiIXzMUZvsCT ... Password found: xxxx -#### Electrum 1.x Technical Details #### +### Electrum 1.x Technical Details ### The *extract-electrum-halfseed.py* script is intentionally short and should be easy to read for any Python programmer. An Electrum encrypted seed is 64 bytes long. It contains a 16-byte AES initialization vector, followed by 48 bytes of encrypted seed data, the last 16 of which are padding (so just 32 bytes of actual seed data). The script extracts the 16-byte initialization vector and just the first 16 bytes of actual seed data (50% of the seed). @@ -235,7 +207,7 @@ Because only half of the seed is extracted, the private keys cannot be feasibly Without access to the rest of your wallet file, it is extremely unlikely that these 16 characters alone could put any of your Bitcoin funds at risk, even after a successful password guess and decryption. -#### Electrum 2.x Technical Details #### +### Electrum 2.x Technical Details ### The *extract-electrum2-partmpk.py* script is intentionally short and should be easy to read for any Python programmer. An Electrum 2.x encrypted master private key (mpk) is 128 bytes long. It contains a 16-byte AES initialization vector, followed by 112 bytes of encrypted mpk data, with the last byte being padding (so 111 bytes of actual mpk data). Of these 111 bytes, roughly 18 comprise a header, the next 44 the chaincode, and the remaining 47 a private key. The script extracts the 16-byte initialization vector and just the first 16 bytes of mpk data, all of it non-sensitive header information. @@ -243,13 +215,45 @@ Once decrypted, these 16 characters always begin with the string "xprv", and the Without access to the rest of your wallet file, it is impossible the decrypted header information could ever lead to a loss of funds. +## Usage for Metamask ## +There are two extract scripts for Metamask, that lets you extract all the vault data (including old overwritten wallets) from the extension and one that allows you to create a n extract for trustedless recovery. -### Usage for mSIGNA ### +For Chrome Based Browsers, you will need to locate the data folder for the browser extension. + +For Metamask this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn\ + +For Binance Wallet Extension this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\fhbohimaelbohpjbbldcngcnapndodjp\ + +For Ronin Wallet this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\fnjhmkhhmkbjkkabndcnnogagogbneec\ + +The paths for the extension data will be a bit different for other Chrome based browserse (Like Brave) but the general location and final folder name will be the same. + +_You can then view all of the vault data for that extension by using a command similar to the one below (Except you will want to use the path to your own browser extension data)_ + + python extract-metamask-vaults.py ../btcrecover/test/test-wallets/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn + ===== (Likely) Old Vault Data ===== + + {"data":"vXOTraxWuDmDrhxZ759NodhTmd4UQkThRG6YLvPt14OdZgnvJo4P5wj+LRupmb+7Vql+fOM5IF33Qb3FQvWro8Ro201M1YOH5zBdSwK6wzYmlFndlwqgOq61HSDUD9Ee1ccUF/iUgqJIngCw9/bRo93kpj11MuVonNOayTFztRc68+/JPCmIe0vqPYShRfJbeI8IBvauJdUxg6VqG0PId0Pw30ZO3f3QXmKFE+ZoibgbO111j7gQ0l7j6KdABeA=","iv":"7hvnbvsoSQmAbWzfvtMkjA==","salt":"13+DUqg893kPM0MiJz3bz2iYGAxPtPisX1JE1+be9IU="} + + ===== Current Vault Data ===== + + {"data":"Ny6zeXCgltvFkIWycZU3gYLocIM+gH/2m4fozdKdJxwff2BUHXaxBkaLDuzMs7WtazXJ+3P+XsFFG2W8+7tpNfCv2RCNNHWX9aVEBKeKEwQPUT6MD4rNU2XYykPROAcbdUPHKEVpaAEj+1VlCiMk1m3j7KhIHpt1cI7Qp8rV7lxzCUc5FWAWlc+gxvFTfSXOPJd0k23/F9MgRq0vn2h+UJolmLzpQFvEv2BUuL6CoEbog8Vn2N+ktypbR2pnNMA=","iv":"H82DrVooOndR7fk1SKKGwQ==","salt":"cxp0pRtsgyUBjll6MktU2HySubrtnMaPXAwaBsANA1Y="} + +For Firefox, you will need to retrieve your Metamask vault using the process described here: +https://metamask.zendesk.com/hc/en-us/articles/360018766351-How-to-use-the-Vault-Decryptor-with-the-MetaMask-Vault-Data + +Once you have the vault data, you can put it in a text file and you can either use it directly with BTCRecover, or you can create an extract. + + python extract-metamask-privkey.py ../btcrecover/test/test-wallets/metamask.9.8.4_firefox_vault + Metamask first 16 encrypted bytes, iv, and salt in base64: + bXQ6bB5JP1EW0xwBmWZ9vI/iw9IRkorRs9rI6wJCNrd8KUw61ubkQxf9JF9jDv9kZIlxVVkKb7lIwnt7+519MLodzoK0sOw= + +## Usage for mSIGNA ## After downloading the script, **make a copy of your wallet file into a different folder** (to make it easy, into the same folder as the extract script). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this to open the folder which usually contains your wallet file: `%homedrive%%homepath%`. From here you can copy and paste your wallet file (it's a `.vault` file), into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming your wallet file is named `msigna-wallet.vault` and it's in the same folder): - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-msigna-partmpk.py msigna-wallet.vault + cd btcrecover-master\extract-scripts + python3 extract-msigna-partmpk.py msigna-wallet.vault You should get a message which looks like this: @@ -258,14 +262,14 @@ You should get a message which looks like this: When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-msigna-partmpk.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > bXM6SWd6U+qTKOzQDfz8auBL1/tzu0kap7NMOqctt7U0nA8XOI6j6BCjxCsc7mU= ... Password found: xxxx -#### mSIGNA Technical Details #### +### mSIGNA Technical Details ### The *extract-msigna-partmpk.py* script is intentionally short and should be easy to read for any Python programmer. An mSIGNA encrypted master private key is 48 bytes long. It contains 32 bytes of encrypted private key data, followed by 16 bytes of encrypted padding (the chaincode is stored separately). @@ -274,7 +278,7 @@ Because only the last half of the private key is extracted, the wallet cannot be Without access to the rest of your wallet file, it is impossible the decrypted padding could ever lead to a loss of funds. -### Usage for MultiBit Classic ### +## Usage for MultiBit Classic ## ***Warning:*** Using the `extract-multibit-privkey.py` script on a MultiBit Classic key file, as described below, can lead to *false positives*. A *false positive* occurs when *btcrecover* reports that it has found the password, but is mistaken—the password which it displays may not be correct. If you plan to test a large number of passwords (on the order of 10 billion (10,000,000,000) or more), it's **strongly recommended** that you use *btcrecover* directly with a key file instead of using `extract-multibit-privkey.py`. @@ -288,8 +292,8 @@ For more details on locating your MultiBit private key backup files, see: bWI6sTaHldcBFFj9zlgNpO1szOwy8elpl20OWgj+lA== ... Password found: xxxx -#### MultiBit Classic Technical Details #### +### MultiBit Classic Technical Details ### **Warning:** MultiBit Classic data-extracts have a false positive rate of approximately 1 in 3×1011. See the warning above for more information. @@ -314,12 +318,12 @@ The *extract-multibit-privkey.py* script is intentionally short and should be ea Without access to the rest of your private key backup file or your wallet file, these 16 characters alone do not put any of your Bitcoin funds at risk, even after a successful password guess and decryption. -### Usage for MultiBit HD ### +## Usage for MultiBit HD ## After downloading the script, **make a copy of your mbhd.wallet.aes file into a different folder** (to make it easy, into the same folder as *extract-multibit-hd-data.py*). As an example for Windows, click on the Start Menu, then click “Run...”, and then type this: `%appdata%\MultiBitHD`. From here you can open your wallet folder, and copy and paste your mbhd.wallet.aes file into a separate folder. Next you'll need to open a Command Prompt window and type something like this (depending on where the downloaded script is, and assuming you've made a copy of your mbhd.wallet.aes into the same folder): - cd \Users\Chris\Downloads\btcrecover-master\extract-scripts - C:\python27\python extract-multibit-hd-data.py mbhd.wallet.aes + cd btcrecover-master\extract-scripts + python3 extract-multibit-hd-data.py mbhd.wallet.aes You should get a message which looks like this as a result: @@ -328,14 +332,14 @@ You should get a message which looks like this as a result: When you (or someone else) runs *btcrecover* to search for passwords, you will not need your wallet file, only the output from *extract-multibit-hd-data.py*. To continue the example: - cd \Users\Chris\Downloads\btcrecover-master - C:\python27\python btcrecover.py --data-extract --tokenlist tokens.txt + cd btcrecover-master + python3 btcrecover.py --data-extract --tokenlist tokens.txt Please enter the data from the extract script > bTI6LbH/+ROEa0cQ0inH7V3thbdFJV4= ... Password found: xxxx -#### MultiBit HD Technical Details #### +### MultiBit HD Technical Details ### The *extract-multibit-hd-data* script is intentionally short and should be easy to read for any Python programmer. A MultiBit HD wallet file is entirely encrypted. The extract script simply reads the first 32 bytes from the wallet file. @@ -343,7 +347,3 @@ These 32 bytes optionally (starting with MultiBit HD v0.5.0) start with a 16-byt Without access to the rest of your wallet file, it is impossible the decrypted header information could ever lead to a loss of funds. - -### Limitations ### - -As mentioned in the [Usage for Armory](#usage-for-armory) section, the address and private key extracted from Armory wallets does put that one address at risk, and might also put other funds in that wallet at risk. diff --git a/docs/GPU_Acceleration.md b/docs/GPU_Acceleration.md index c7347faea..3741902ca 100644 --- a/docs/GPU_Acceleration.md +++ b/docs/GPU_Acceleration.md @@ -1,21 +1,118 @@ -## *btcrecover* GPU Acceleration Guide ## +## BTCRecover GPU Acceleration Guide ## -*btcrecover* includes experimental support for using one or more graphics cards or dedicated accelerator cards to increase search performance. This can offer on the order of *100x* better performance with Bitcoin Unlimited/Classic/XT/Core or altcoin wallets when enabled and correctly tuned. With Armory (which uses a GPU-resistant key derivation function), this can offer a modest improvement of 2x - 5x. +### Performance Notes -In order to use this feature, you must have a card and drivers which support OpenCL (most AMD and NVIDIA cards and drivers already support OpenCL on Windows), and you must install the required Python libraries as described in the [Windows GPU acceleration](INSTALL.md#windows-gpu-acceleration-for-bitcoin-unlimitedclassicxtcore-armory-or-litecoin-qt) section of the Installation Guide. GPU acceleration should also work on Linux and OS X, however instructions for installing the required Python libraries are not currently included in this tutorial. +BTCRecover includes support for using one or more graphics cards or dedicated accelerator cards to increase search performance. -Due to its experimental status, it's highly recommended that you run the GPU unit tests before running it with a wallet. The two commands below will run the relevant tests in ASCII and Unicode modes, respectively (or you can leave out `GPUTests` to run all of the unit tests if you'd prefer). Any skipped tests can be safely ignored, unless *all* the tests are skipped which probably means there was a problem loading OpenCL. +The performance increase that this offers depends on the type of wallet you are trying to recover, your CPU and your GPU. - C:\python27\python -m btcrecover.test.test_passwords -v GPUTests - C:\python27\python -m btcrecover.test.test_passwords -v --utf8 GPUTests +For the sake of comparison, the CPU vs GPU performance for an i7-8750 vs an NVidia 1660ti, for a variety of wallets is generally: + +| Recovery Type | CPU Performance (kp/s) | GPU Performance (kp/s) | GPU speed boost vs CPU | +|---|---|---|---| +| Bitcoin Core (JTR Kernel) | 0.07 | 6.75 | 96x | +| Bitcoin Core (OpenCL_Brute) | 0.07 | 0.95 | 14x | +| Blockchain.com Main Password | 1 | 10 | 10x | +| Blockchain.com Second Password | 0.39 | 15.5 | 40x | +| Dogechain.info | 1.3 | 11.3 | 10x | +| Electrum 2 Wallet Password | 4.5 | 21 | 4.5x | +| BIP39 Passphrase (Or Electrum 'Extra Words' | 2.3 | 10.4 | 4.5 | +| BIP39 12 Word Seed | 33 | 134 | 4.3x | +| BIP39 12 Word Seed (Tokenlist) | 33 | 130 | 4x | +| BIP39 24 Word Seed | 160 | 180 | 1.15x | +| BIP39 24 Word Seed (Tokenlist) | 140 | 160 | 1.15x | +| Electrum Seed | 200 | 366 | 1.8x | +| BIP38 Encrypted Key | 0.02 | 0.02 | 1x (But scales well with Multiple GPUs) | + +**Don't simply assume that enabling GPU/OpenCL will give a speed boost at this point, especially if you have a very high end CPU and low end GPU... Test your command both with and without OpenCL/GPU and use the --no-eta and --performance arguments to evaluate performance** + +_This drastic performance difference is mostly due to different parts of the process being CPU bound to varying degrees, particularly for BIP39 and Electrum seed recovery. As such shifting more processing in to the OpenCL and creating a more efficient seed generator will be future areas of work._ + +[You can also find performance information for a wide variety of GPUs, particularly multi-gpu situations, in this article here](Example_Multi-GPU_with_vastai.md) + +## PyOpenCL Installation + +GPU/OpenCL acceleration depends on your having a working install of PyOpenCL for OpenCL 1.2. + +In order to use this feature, you must have a card and drivers which support OpenCL (most AMD and NVIDIA cards and drivers already support OpenCL on Windows), and you must install the required Python libraries as described below. + +GPU acceleration should also work on MacOS, however instructions for installing the required Python libraries are not currently included in this tutorial. + +## PyOpenCL Installation for Windows +As of June 2023, PyOpenCL can now be simply installed via PIP and will work fine as long as you have the required GPU drivers installed on your system. + + 1. Install the driver package for your GPU... Nothing else will work without this... + 2. Open a command prompt window, and type this to install PyOpenCL and its dependencies: + + pip3 install pyopencl + +## PyOpenCL Installation for Linux + +**Usage with Ubuntu 20.04** +1. For NVidia GPUs, install the Nvidia binary driver for your system. (In Ubuntu this is straight forward and explained here: https://help.ubuntu.com/community/BinaryDriverHowto/Nvidia#NVIDIA_driver_from_the_Ubuntu_repositories the 440 version of the driver metapack was tested and works fine) - I don't have a current AMD system to test with, but this should work fine as long as you have the AMD drivers installed... +2. Install the pyOpenCL library for your system. + + + sudo apt install python3-pyopencl + +Depending on your Linux environment, the Python libraries that are availale via APT may be very out of date and may not work correctly. In this case, you may need to install and build PyOpenCL via Pip. (And whether a particular version of PyOpenCL will build on your system may vary, so trying an older PyOpenCL package version may help, eg: pyopencl==2019.1.1) + +**Beyond this, no additional support will be provided for any distribution other than the most recent Ubuntu LTS release.** +Generally speaking, instructions for installing and configuring an environment for Hashcat will cover what you need to get your environment set up and working... + +## Testing your System +To check if your PyOpenCL installation is working correctly, you can run the unit tests relating to the type of GPU accelerated recovery you want to run: + + +**Bitcoin Core John-The-Ripper Kernel (JTR)** + + python3 -m btcrecover.test.test_passwords -v GPUTests Assuming the tests do not fail, GPU support can be enabled by adding the `--enable-gpu` option to the command line. There are other additional options, specifically `--global-ws` and `--local-ws`, which should also be provided along with particular values to improve the search performance. Unfortunately, the exact values for these options can only be determined by trial and error, as detailed below. -### GPU performance tuning for Bitcoin Unlimited/Classic/XT/Core and Litecoin-Qt ### +**Blockchain.com, Electrum Wallets & BIP39 Passphrases (Or Electrum 'Extra words') via OpenCL_Brute Kernel (Supports Bitcoin core too, but slower than JTR)** + + python3 -m btcrecover.test.test_passwords -v OpenCL_Tests + +If all tests pass, then you can simply add --enable-opencl to the command line argument. The default for OpenCL platform selection and work group size should give a good result. + +**BIP39 or Electrum Seed Recovery** + + python3 -m btcrecover.test.test_seeds -v OpenCL_Tests + +If all tests pass, then you can simply add --enable-opencl to the command line argument. The default for OpenCL platform selection and work group size should give a good result. + +### Performance Tuning: Background +The key thing to understand when it comes ot OpenCL performance tuning is that there is a fundamental difference between the way that a CPU processes instructions and a GPU. + +CPU's can process commands very quickly, but can basically only perform once task at a time per CPU core. GPU's on the other hand can actually be slower at performing the same task, but the difference is that they might be able to perform a batch of 1000 tasks at the same time in parallel, rather than one after the other as occurs on a CPU. + +What this means is that there can be significant perfomance differences for GPU processing depending on how large the batch of work that you are loading in to the GPU is. (And doing things like only half-filling the potential batch size will cut your performance in half) + +As such, setting the Global and Local work-size arguments can make a massive difference for the JTR kernel, while using the workgroup-size command can make a big difference when using the OpenCL_Brute kernel (Though the defaults for the OpenCL_Brute kernel should automatically work out something close to optimal for your system) + +This also means that performance bottleknecks that aren't an issue in CPU processing become a problem when using GPU processing. (This is precisely why a tokenlist for a 24 word seed doesn't get nearly as much of a performance boost solving as standard recovery with a BIP39 12 word seed) + +This is also why you may find that there is some benefit to creating a checksummed seed list on one PC and loading that into another using the --savevalidseeds, --savevalidseeds-filesize, --multi-file-seedlist and --skip-worker-checksum arguments. + +### Multi-GPU Systems +By default, both OpenCL kernels will use all GPUs that are available in a system, but they will utilise them a bit dfferently. + +**JohnTheRipper Kernel (used by Bitcoin Core when the --enable-gpu argument is used)** + +Will just use a single CPU thread and use all GPUs, though it really needs the GPUs to be identical in terms of performance. + +**The OpenCL_Brute kernel (enabled via the --enable-opencl argument)** + +Will allocate GPUs to threads in a round-robin. (Eg if you had 3 GPUs and 3 CPU cores, it would allocate a GPU1->CPU1, GPU2->CPU2, GPU3->CPU3, etc...) Given this, you will generally want to have at least as many threads as you have GPUs. (Though I haven't seen any systems other than ex-crypto mining rigs where you have more GPUs than CPUS) BTCRecover will default to using as many threads as there are logical CPU cores, but if your system has fewer cores than GPUs, you can always just manually specify the thread count with the --threads argument. **_Generally speaking, I would suggest that 2 threads per GPU is probably your best starting point performance wise..._** + +You can also manually specify which OpenCL devices you want to use through the --opencl-devices argument. You can also list a GPU twice here, something that may be useful if one GPU is twice as powerful as the others, so you want it to be allocated a larger share. (eg: specifying GPUs 0 0 1 will allocate GPU0 to twice as many threads as GPU1) Like mentioned above, these GPUs are allocated in a round robin fashion, so you can basically specify as many devices as you have threads. + +### GPU performance tuning for Bitcoin Core and derived altcoin wallets with the JTR kernel### A good starting point for these wallets is: - C:\python27\python btcrecover.py --wallet wallet.dat --performance --enable-gpu --global-ws 4096 --local-ws 512 + python btcrecover.py --wallet ./btcrecover/test/test-wallets/bitcoincore-wallet.dat --performance --enable-gpu --global-ws 4096 --local-ws 256 The `--performance` option tells *btcrecover* to simply measure the performance until Ctrl-C is pressed, and not to try testing any particular passwords. You will still need a wallet file (or an `--extract-data` option) for performance testing. After you you have a baseline from this initial test, you can try different values for `--global-ws` and `--local-ws` to see if they improve or worsen performance. @@ -25,53 +122,38 @@ Generally when testing, you should increase or decrease these two values by powe Although this procedure can be tedious, with larger tokenlists or passwordlists it can make a significant difference. -### GPU performance tuning for Armory ### - -Performance tuning for Armory is similar to tuning for Bitcoin Unlimited/Classic/XT/Core, but unfortunately it's much more complex. Armory uses a memory-hard key derivation function called ROMix-SHA-512 which is specifically designed to resist GPU-based acceleration. You should start by reading the section above, which also applies to Armory. In addition to `--global-ws` and `--local-ws`, there is a third option, `--mem-factor`, which affects the GPU memory usage, and as a consequence overall performance. +### OpenCL performance tuning for other wallets ### -GPU memory usage is directly proportional to `--global-ws`. The larger the `--global-ws`, the more GPU memory is used. With Armory wallets, a larger `--global-ws` usually improves performance, so you should start with a `--global-ws` that is as high as your GPU will allow. In order to help you locate this value, you can run *btcrecover* with the `--calc-memory` options, as seen below: +#### Limiting Derivation Paths Searched for Seed Based Wallets +By default, BTCRecover will now automatically search all common derivation paths for a given cryptocurrency. (eg: Bitcoin BIP44, 49 and 84) - C:\python27\python btcrecover.py --wallet armory_2dRkxw76K_.wallet --enable-gpu --calc-memory - Details for this wallet - ROMix V-table length: 32,768 - outer iteration count: 4 - with --mem-factor 1 (the default), - memory per global worker: 2,048 KB +For CPU based recovery, this doesn't present a major decrease in performance, but depending on your CPU, this could **halve** your OpenCL performance. As such, if you know the derivation path that you are searching for, you should manually specify it via the --bip32-path command. - Details for GeForce GTX 560 Ti - global memory size: 1,024 MB - with --mem-factor 1 (the default), - est. max --global-ws: 480 - with --global-ws 4096 (the default), - est. memory usage: 8,192 MB +#### Address Generation Limit for Seed Based Wallets +Cryptocurrencies like Bitcoin will generally generate a new address each time you choose to "receive". The address generation limit (--addr-limit argument) tells BTCRecover how many addresses it should generate and search within for each seed. (This is why you want to use the earliest possible address from your wallet) -So for this particular wallet and GPU, a `--global-ws` of around 480 is the maximum supported, and this would be a good starting point for performance searching: +For CPU based recovery, setting the address generation limit to something like 10 doesn't make a huge impact on performance, whereas for OpenCL based recovery, an address generation limit as long as 10 can **halve** your search performance. - C:\python27\python btcrecover.py --wallet armory_2dRkxw76K_.wallet --enable-gpu --global-ws 480 --local-ws 32 --performance +That said, if you are doing a recovery on something like an Ethereum or Ripple wallet, you can generally just leave your address generation limit at 1, as these account based cryptos tend to use a fixed receiving address. -If this generates a memory allocation error message (and it probably will), try decreasing `--global-ws` by 32 or 64 at a time until it succeeds. Once you've determined a good `--global-ws` (near its maximum) and a good `--local-ws` (through trial and error, unfortunately), you can move onto the next step. +It should also be noted that if you are using an Address Database, you can generally just leave the address generation limit at 1... -The `--mem-factor #` option *decreases* memory usage by the factor specified, allowing you to use a larger `--global-ws`. Next, you should try the same procedure above, but use a higher `--mem-factor` (the default is 1) to see if performance can be further improved. In this example, I've determined that the best settings so far are `--global-ws 448 --local-ws 64`, and now I'm starting with these values and adding `--mem-factor 2`: +#### Work Group Size - C:\python27\python btcrecover.py --wallet armory_2dRkxw76K_.wallet --enable-gpu --global-ws 448 --local-ws 64 --mem-factor 2 --calc-memory +If you use the --opencl-info command, you will be presented with a list of OpenCL devices and their corresponding max work-group size. - Details for this wallet - ROMix V-table length: 32,768 - outer iteration count: 4 - with --mem-factor 2, - memory per global worker: 1,024 KB +You can then use the --opencl-workgroup-size command to try setting the workgroup size manually. - Details for GeForce GTX 560 Ti - global memory size: 1,024 MB - with --mem-factor 2, - est. max --global-ws: 992 - with --global-ws 448, - est. memory usage: 448 MB +**For Password Recovery:** You should try to set the workgroup command to be an exact multiple of the max workgroup size. -So for the next round of testing, we should start somewhere around `--global-ws 992 --local-ws 64 --mem-factor 2`, except that 992 isn't evenly divisible by 64, so we reduce it until it is evenly divisible which results in starting values of `--global-ws 960 --local-ws 64 --mem-factor 2`. +**For Seed Recovery** You will notice that seed recovery will automatically set the workgroup size to a much larger value. -For the next few rounds of testing, you can generally keep `--local-ws` at the same value, and just focus on determining the highest value of `--global-ws` which provides the best performance for each successively larger value of `--mem-factor`. Eventually you will reach a point where increasing `--mem-factor` and `--global-ws` will no longer provide any performance benefit, and may actually start hurting performance. This is generally the best you can get with this particular wallet and GPU combination. In this example, the best I could get was at these settings which resulted in a 3.5x improvement over my quad-core CPU, or a 6x improvement when using two GPUs: +This is because the majority of seeds generated are only checksummed, never fully hashed. The ratio of seeds generated:hashed varies for different wallet types and seed lenghts. - C:\python27\python btcrecover.py --wallet armory_2dRkxw76K_.wallet --enable-gpu --global-ws 2816 --local-ws 128 --mem-factor 6 --performance +Generally speaking it is: +* BIP39 12 Word: 16:1 +* BIP39 18 Word: 64:1 +* BIP39 24 Word: 256:1 +* Electrum : 125:1 -This procedure is definitely tedious, so it's up to you to decide whether the improvement you might realize is worth it. +What this means is that in order to fill the maximum workgroup size for the GPU, the seedgenerator needs to pass it a chunk of possible seeds that is many times larger than the max workgroup size. (Eg: for a work group size of 1024, a BIP39 24 word seed will need 262,144 potential seeds) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 9cd099f9f..aec1fd348 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,199 +1,250 @@ -## *btcrecover* Installation ## +# Installing BTCRecover # -Just download the latest version from and unzip it to a location of your choice. There’s no installation procedure for *btcrecover* itself, however there are additional requirements below depending on your operating system and the wallet type you’re trying to recover. +There are a few basic steps to installing BTCRecover. -### Wallet Installation Requirements ### +1) Download and unzip the BTCRecover script -Locate your wallet type in the list below, and follow the instructions for only the sections listed next to your wallet. +2) Download and install Python3 -**Note** that for Armory wallets, you must have Armory 0.92.x or later installed on the computer where you run *btcrecover*. - - * Armory 0.91.x or earlier - unsupported, please upgrade Armory first - * Armory 0.92.x on Windows - [Python 2.7](#python-27) **32-bit** (x86) - * Armory 0.93+ on Windows - [Python 2.7](#python-27) **64-bit** (x86-64) - * Armory 0.92+ on Linux - no additional requirements - * Armory 0.92+ on OS X - some versions of Armory may not work correctly on OS X, if in doubt use version 0.95.1 - * Bitcoin Unlimited/Classic/XT/Core - [Python 2.7](#python-27), optional: [PyCrypto](#pycrypto) - * MultiBit Classic - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * MultiBit HD - [Python 2.7](#python-27), [scrypt](#scrypt), optional: [PyCrypto](#pycrypto) - * Electrum (1.x or 2.x) - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Electrum 2.8+ fully encrypted wallets - [Python 2.7](#python-27) (2.7.8+ recommended), [coincurve](Seedrecover_Quick_Start_Guide.md#installation), optional: [PyCrypto](#pycrypto) - * BIP-39 Bitcoin passphrases (e.g. TREZOR) - [Python 2.7](#python-27) (2.7.8+ recommended), [coincurve](Seedrecover_Quick_Start_Guide.md#installation) - * BIP-39 Ethereum passphrases (e.g. TREZOR) - [Python 2.7](#python-27) (2.7.8+ recommended), [coincurve and pysha3](Seedrecover_Quick_Start_Guide.md#installation) - * Hive for OS X - [Python 2.7](#python-27), [scrypt](#scrypt), [Google protobuf](#google-protocol-buffers), optional: [PyCrypto](#pycrypto) - * mSIGNA (CoinVault) - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Blockchain.info - [Python 2.7](#python-27) (2.7.8+ recommended), recommended: [PyCrypto](#pycrypto) - * Bitcoin Wallet for Android/BlackBerry backup - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Bitcoin Wallet for Android/BlackBerry spending PIN - [Python 2.7](#python-27), [scrypt](#scrypt), [Google protobuf](#google-protocol-buffers), optional: [PyCrypto](#pycrypto) - * KnC Wallet for Android backup - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Bither - [Python 2.7](#python-27), [scrypt](#scrypt), [coincurve](Seedrecover_Quick_Start_Guide.md#installation), optional: [PyCrypto](#pycrypto) - * Litecoin-Qt - [Python 2.7](#python-27), optional: [PyCrypto](#pycrypto) - * Electrum-LTC - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Litecoin Wallet for Android - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Dogecoin Core - [Python 2.7](#python-27), optional: [PyCrypto](#pycrypto) - * MultiDoge - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) - * Dogecoin Wallet for Android - [Python 2.7](#python-27), recommended: [PyCrypto](#pycrypto) +3) Install required packages via Python PIP +4) (Optional) Install PyOpenCL module for GPU Acceleration ----------- +5) Test your installation (Optional, but a good idea) + +These steps are also covered in Videos below for each supported Operating System. + +**Note: Depending on your operating system and python environment, you may need to replace the `python` command with `python3`. (By default, the command to use will be `python` in Windows and `python3` in Linux) Most non-technical users are on Windows, so all example commands will use `python` to match the defaults for this platform** + +**Video Tutorials** + +Windows: + +Ubuntu Linux: +MacOS: -### Python 2.7 ### +Android via Termux: -##### Windows ##### +## 1) Downloading *btcrecover* ## -Visit the Python download page here: , and click the link for the latest **Python 2.7** release near the top of the page under the heading *Python Releases for Windows*. Download and run either the `Windows x86 MSI installer` for the 32-bit version of Python, or the `Windows x86-64 MSI installer` for the 64-bit one (for Armory wallets, be sure to choose the correct one as noted above). Modern PCs should use the 64-bit version, however if you're unsure which one is compatible with your PC, choose the 32-bit one. +Just download the latest version from and unzip it to a location of your choice. There’s no installation procedure for *btcrecover* itself, however there are additional requirements below depending on your operating system and the wallet type you’re trying to recover. -##### Linux ##### +You can also use Git (If you have it installed) to do this with the command `git clone https://github.com/3rdIteration/btcrecover/` -Most distributions include Python 2.7 pre-installed. -Note that for Blockchain.info wallets, Python version 2.7.8 or greater is recommended, and will run approximately 5 times faster than earlier versions. You can determine which version of Python you have installed by running `python --version` in a terminal. If your version is earlier than 2.7.8, you may want to check if your distribution has a “backports” repository with a more up-to-date version. +## 2) Install Python ## -Some Linux distributions do not include the bsddb (Berkeley DB) Python module. This is usually not a problem, however if you encounter a `master key #1 not found` error, it might be resolved by installing the bsddb module (or a version of Python which includes it). +**Note:** Only Python 3.9 and later are officially supported... BTCRecover is automatically tested with all supported Python versions (3.9, 3.10, 3.11, 3.12, 3.13) on all supported environments (Windows, Linux, Mac), so you can be sure that both BTCRecover and all required packages will work correctly. Some features of BTCRecover may work on earlier versions of Python, your best bet is to use run-all-tests.py to see what works and what doesn't... -##### OS X ##### +### Windows ### +Video Demo of Installing BTCRecover in Windows: -Since OS X includes an older version of Python 2, it's strongly recommended that you install the latest version. Doing so will not affect the older OS X version, the new one will be installed in a different place from the existing one. +Visit the Python download page here: , and click the link for the latest **Python 3.12** release (Python 3.13, etc, will work, but Python 3.12 has simpler installation of required modules) release near the top of the page under the heading *Python Releases for Windows*. Download and run either the `Windows x86 MSI installer` for the 32-bit version of Python, or the `Windows x86-64 MSI installer` for the 64-bit one. Modern PCs should use the 64-bit version, however if you're unsure which one is compatible with your PC, choose the 32-bit one. -To install the latest version, visit the Python download page here: , and click the link for the latest **Python 2** release. Download and open either the `Mac OS X 64-bit/32-bit installer` for OS X 10.6 and later (most people will want this one), or the `Mac OS X 32-bit i386/PPC installer` for OS X 10.5. +_**When installing Python in Windows, be sure to select to "Add Python to PATH" on the first screen of the installer...**_ -If you have any Terminal windows open, close them after the installation completes to allow the new version to go into effect. +**Note for Large Multi-CPU Systems:** Windows limits the number of possible threads to 64. If your system has more logical/physical cores than this, your best bet is to run the tool in Linux. (Ubuntu is an easy place to start) -If (and only if) you decide *not* to install the latest version of Python 2, you will need to manually install `pip` if you need to install any of the other requirements below: +### Linux ### +Video Demo of Installing BTCRecover in Ubuntu Live USB: - curl https://bootstrap.pypa.io/get-pip.py | sudo python +Most modern distributions include Python 3 pre-installed. Older Linux distributions will include Python2, so you will need to install python3. +If you are using SeedRecover, you will also need to install tkinter (python3-tk) if you want to use the default GUI popups for seedrecover. (Command line use will work find without this package) -### PyCrypto ### +Some distributions of Linux will bundle this with Python3, but for others like Ubuntu, you will need to manually install the tkinter module. -PyCrypto is not strictly required for any wallet, however it offers a 20x speed improvement for wallets that tag it as recommended in the list above. +You can install this with the command: `sudo apt install python3-tk` -##### Windows ##### +If any of the "pip3" commands below fail, you may also need to install PIP via the command: `sudo apt install python3-pip` -Download and run the PyCrypto 2.6 installer for Python 2.7, either the 32-bit version or the 64-bit version to match your version of Python. This is either `PyCrypto 2.6 for Python 2.7 32bit` or `PyCrypto 2.6 for Python 2.7 64bit` available here: +If your installation fails to build cffi, you may also need to install libffi-dev with the command: `sudo apt install libffi-dev` -##### Linux ##### +If you get a message that there is no installation candidate for Python3-pip, you will need to enable the "universe" repository with the command: `sudo add-apt-repository universe` -Many distributions include PyCrypto pre-installed, check your distribution’s package management system to see if it is available (it is often called “python-crypto”). If not, try installing it from PyPI, for example on Debian-like distributions (including Ubuntu), if this doesn't work: +You can then re-run the command to install python3-pip from above. - sudo apt-get install python-crypto +### Android via Termux ### +Some warnings and notes... -then try this instead: +* Termux support remains experimental, but automated tests now run weekly via the Termux Docker container on GitHub Actions + +* Your phone may not have sufficient cooling to run BTCRecover for any meaninful length of time + +* Performance will also vary dramatically between phones and Android versions... (Though it is actually fast enough to be useful for simple recoveries) + +* Termux is not a standard Linux environment and is not officially supported, but might work following the process below... (And if it doesn't, just use a PC instead...) + +* Install Termux following the instructions here: (Currently not officially distributed on Google Play and the version on Google Play is not currently up-to-date) - sudo apt-get install python-pip - sudo pip install pycrypto +You will then need to install Python as well as some other packages (Mostly the Coincurve build requirements) -##### OS X ##### + pkg install python-pip git autoconf automake build-essential libtool pkg-config binutils-is-llvm rust - 1. Open a terminal window (open the Launchpad and search for "terminal"). Type this and then choose `Install` to install the command line developer tools: + The `python-pip` package already includes PIP. Attempting to upgrade it with + `pip install --upgrade pip` will fail and is unnecessary. - xcode-select --install +Once this is done, you can install the base requirements for BTCRecover that allow recovery of common wallet types. (The full requirements have a lot of packages and will take a long time to build, like 15-20 minutes or more...) - 2. Type this to install PyCrypto: +#### Enabling Native RIPEMD160 Support +As of OpenSSL v3 (Late 2021), ripemd160 is no longer enabled by default in some Linux environments and is now part of the "Legacy" set of hash functions. In Linux/MacOS environments, the hashlib module in Python relies on OpenSSL for ripemd160, so if you want full performance in these environments, you may need modify your OpenSSL settings to enable the legacy provider. - sudo pip install pycrypto +You can check if this is required by running `python check_ripemd160.py` +As of July 2022, BTCRecover does include a "pure Python" implementation of RIPEMD160, but this only offers about 1/3 of the performance when compared to a native implementation via hashlib. -### scrypt ### +Video Demo of this applying fix can be found here: -##### Windows ##### +An example of the modified configuration file can be found here: - 1. Open a command prompt window, and type this to install pylibscrypt: +For more information, see the relevant issue on the OpenSSL Github repository: - C:\Python27\Scripts\pip install pylibscrypt +### MacOS ### - 2. Download this libsodium zip file, and extract it to a temporary location: +Video Demo of Installing BTCRecover in MacOS: - 3. Find the correct `libsodium.dll` file from the extracted files, it will be located at one of these two paths: +1) [Install brew via instructions at brew.sh](https://brew.sh) + +The Install command is: - Win32\Release\v141\dynamic\libsodium.dll - x64\Release\v141\dynamic\libsodium.dll + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - Choose either the 32-bit version (the first one above) or the 64-bit version (the second), it **must** match the version of Python that you've installed. Note that the 64-bit version is recommended if it's supported by your computer (it is approximately 35% faster than the 32-bit version, so install the 64-bit versions of both libsodium *and* Python for best performance). +_Be sure to follow the instructions and add brew to your path..._ + +2) [Install coincurve build requirements](https://ofek.dev/coincurve/install/) + +The Install command is: - 4. Copy the chosen `libsodium.dll` file into your `C:\Python27` directory. + brew install autoconf automake libffi libtool pkg-config python python-tk - 5. Download and install one of the two update packages below from Microsoft, either the 32-bit version or the 64-bit version (the second) to match the version of Python that you've installed. - - * [Microsoft Visual C++ Redistributable for Visual Studio 2017 **32-bit**](https://go.microsoft.com/fwlink/?LinkId=746571) - * [Microsoft Visual C++ Redistributable for Visual Studio 2017 **64-bit**](https://go.microsoft.com/fwlink/?LinkId=746572) +**Once you have installed Python via Brew, you will need to run both Python and PIP with commands that include the full version numnber. (eg: python3.12 and pip3.12)** -##### Linux ##### +3) Install Rust (Optional, but needed for requirements-full modules) -Install pylibscrypt and at least one scrypt library, for example on Debian-like distributions (including Ubuntu): + curl https://sh.rustup.rs -sSf | sh - 1. Open a terminal window, and type this to install pylibscrypt: +## 3) Install requirements via Python Pip ## - sudo apt-get install python-pip - sudo pip install pylibscrypt +Once both Python3 and PIP have been installed, you can install the requirements for BTCRecover. - 2. Install *one* of the scrypt libraries listed under the Requirements section here: , e.g. try each of these commands, stopping after the *first one* succeeds: +### Essential Requirements +You will first want to install the basic packages required for BTCRecover with the command: - sudo apt-get install libscrypt0 - sudo apt-get install python-scrypt - sudo pip install scrypt - sudo apt-get install libsodium18 - sudo apt-get install libsodium13 +`pip3 install -r requirements.txt` -##### OS X ##### +This will give you the functionality needed recovery of Bitcoin/Ethereum wallets (And clones of these chains) - 1. Open a terminal window (open the Launchpad and search for "terminal"). Type this and then choose `Install` to install the command line developer tools: +If when run this command, you get an error message similar to **error: externally-managed-environment** then you need to add an additional argument `--break-system-packages` to the above command. (So the command will be `pip3 install -r requirements.txt --break-system-packages`) - xcode-select --install +Note: If you use Python for other things beyond BTCRecover, then the `--break-system-packages` could cause other issues, but in such situations, managing your python virtual environments for your specific system is beyond the scope of this documentation. - 2. Type this to install pylibscrypt and libscrypt: +If you are running Python 3.13 and get a build error you may also need to set the following environment variable. - sudo pip install pylibscrypt + export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 - curl -Lo libscrypt.zip https://github.com/technion/libscrypt/archive/master.zip - unzip libscrypt.zip - cd libscrypt-master - make CFLAGS_EXTRA= LDFLAGS= LDFLAGS_EXTRA= && sudo make install-osx +### Packages for Extended Wallet Support +Depending on your wallet type, you may also want to install the packages required for full wallet support. This is a much larger download and may also require that you install additional software on your PC for these packages to build and install. +`pip3 install -r requirements-full.txt` -### Google Protocol Buffers ### +### Installing individual packages +If you are an advanced user, you may choose to install only those additional packages that are required for the specific recovery you are attempting. More information about which wallets require which packages is at the bottom of this guide.* -##### Windows ##### +## 4) Install PyOpenCL for GPU Acceleration ## -Open a command prompt window, and type this to install Google Protocol Buffers: +GPU Support will require additional OpenCL libraries to be installed that aren't covered by the above commands... - C:\Python27\Scripts\pip install protobuf +For more information and instructions, [see the GPU acceleration page here](./GPU_Acceleration.md) -##### Linux ##### +## 5) Testing your Installation ## -Install the Google's Python protobuf library, for example on Debian-like distributions (including Ubuntu), open a terminal window and type this: +Once you have downloaded and unzipped BTCRecover, installed Python and all required libraries, you can test the program with the command: - sudo apt-get install python-pip - sudo pip install protobuf +`python run-all-tests.py -vv` -##### OS X ##### +This command will take a few minutes to run and should complete without errors, indicating that your system is ready to use all features of BTCRecover. - 1. Open a terminal window (open the Launchpad and search for "terminal"). Type this and then choose `Install` to install the command line developer tools: +## Wallet Python Package Requirements ## - xcode-select --install +**If you want to install all requirements for all wallet types, you can simply use the command `pip3 install -r requirements-full.txt`** - 2. Type this to install Google Protocol Buffers: +Locate your wallet type in the list below, and follow the instructions for only the sections listed next to your wallet. - sudo pip install protobuf + * Bitcoin Core - optional: [PyCryptoDome](#pycryptodome) + * MultiBit Classic - recommended: [PyCryptoDome](#pycryptodome) + * MultiBit HD - optional: [PyCryptoDome](#pycryptodome) + * Electrum (1.x or 2.x) - recommended: [PyCryptoDome](#pycryptodome) + * Electrum 2.8+ fully encrypted wallets - [coincurve](Seedrecover_Quick_Start_Guide.md#installation), optional: [PyCryptoDome](#pycryptodome) + * BIP-39 Bitcoin passphrases (e.g. TREZOR) - [coincurve](Seedrecover_Quick_Start_Guide.md#installation) + * BIP-39 Ethereum passphrases (e.g. TREZOR) - [PyCryptoDome](#pycryptodome) [coincurve](Seedrecover_Quick_Start_Guide.md#installation) + * Hive for OS X - [Google protobuf](https://pypi.org/project/protobuf/), optional: [PyCryptoDome](#pycryptodome) + * mSIGNA (CoinVault) - recommended: [PyCryptoDome](#pycryptodome) + * Blockchain.info - recommended: [PyCryptoDome](#pycryptodome) + * Bitcoin Wallet for Android/BlackBerry backup - recommended: [PyCryptoDome](#pycryptodome) + * Bitcoin Wallet for Android/BlackBerry spending PIN - [scrypt](#scrypt), [Google protobuf](https://pypi.org/project/protobuf/), optional: [PyCryptoDome](#pycryptodome) + * KnC Wallet for Android backup - recommended: [PyCryptoDome](#pycryptodome) + * Bither - [coincurve](Seedrecover_Quick_Start_Guide.md#installation), optional: [PyCryptoDome](#pycryptodome) + * Litecoin-Qt - optional: [PyCryptoDome](#pycryptodome) + * Electrum-LTC - recommended: [PyCryptoDome](#pycryptodome) + * Litecoin Wallet for Android - recommended: [PyCryptoDome](#pycryptodome) + * Dogecoin Core - optional: [PyCryptoDome](#pycryptodome) + * MultiDoge - recommended: [PyCryptoDome](#pycryptodome) + * Dogecoin Wallet for Android - recommended: [PyCryptoDome](#pycryptodome) + * SLIP39 Wallets: [shamir-mnemonic](https://pypi.org/project/shamir-mnemonic/) + * Py_Crypto_HD_Wallet Based BIP39 Wallets: [py_crypto_hd_wallet](https://pypi.org/project/py-crypto-hd-wallet/) + * Avalanche + * Cosmos (Atom) + * Polkadot + * Secret Network + * Solana + * Stellar + * Tezos + * Tron + * Helium BIP39 Wallets: [pynacl](https://pypi.org/project/PyNaCl/) and [bitstring](https://pypi.org/project/bitstring/) + * Eth Keystore Files: [eth-keyfile](https://pypi.org/project/eth-keyfile/) + * Eth2 Validator Seed Recovery: [staking-deposit](https://github.com/ethereum/staking-deposit-cli/) + * Groestlecoin BIP39 Wallets: [groestlcoin_hash](https://pypi.org/project/groestlcoin-hash/) + +* BIP38 Encrypted Private Keys: [ecdsa](https://pypi.org/project/ecdsa/) + +### scrypt + +BTCRecover prefers the `wallycore` implementation of `scrypt` when available. +If `wallycore` is not installed it falls back to `pylibscrypt`, which is +approximately 20% slower for BIP38 operations and other wallet formats that rely +on scrypt. ---------- +### PyCryptoDome ### + +With the exception of Ethereum wallets, PyCryptoDome is not strictly required for any wallet, however it offers a 20x speed improvement for wallets that tag it as recommended in the list above. + +### Py_Crypto_HD_Wallet ### + +This module is required for a number of different wallet types. + +For Windows Users, you will also need to install the Microsoft Visual C++ Build Tools befor you will be able to successfully install the module. + +A video tutorial that covers this can be found here: + +For MacOS and Linux users, the module should build/install just fine if you follow the installation instructions on this page for your platform. -### Windows GPU acceleration for Bitcoin Unlimited/Classic/XT/Core, Armory, or Litecoin-Qt ### +### Staking-Deposit ### - 1. Download the latest version of PyOpenCL for OpenCL 1.2 / Python 2.7, either the 32-bit version or the 64-bit version to match the version of Python you installed, from here: . For best compatibility, be sure to select a version for OpenCL 1.2 *and no later* (look for "cl12" in the file name, and also look for "27" to match Python 2.7). +This module isn't available as a pre-compiled package and must be downloaded and built from source. - As of this writing, the 32-bit and 64-bit versions are named respectively: +It is installed as part of the collection of modules installed by `requirements-full.txt`. - pyopencl-2017.1.1+cl12-cp27-cp27m-win32.whl - pyopencl-2017.1.1+cl12-cp27-cp27m-win_amd64.whl +Alternately, you can attempt to download, build and install it via pip3 with the following command: + +`pip3 install git+https://github.com/ethereum/staking-deposit-cli.git@v2.5.0` - 2. Open a command prompt window, and type this to install PyOpenCL and its dependencies: +This module also requires the `py_ecc` module which can be installed with the command: - cd %USERPROFILE%\Downloads - C:\Python27\Scripts\pip install pyopencl-2017.1.1+cl12-cp27-cp27m-win_amd64.whl +`pip3 install py_ecc` - Note that you may need to change either the directory (on the first line) or the filename (on the second) depending on the filename you downloaded and its location. +[More information can be found at its repository](https://github.com/ethereum/staking-deposit-cli/) -[PyCrypto](#pycrypto) is also recommended for Bitcoin Unlimited/Classic/XT/Core or Litecoin-Qt wallets for a 2x speed improvement. +Note: Some dependencies for this module won't always build if you are running the latest version of Python, if you run into build errors, you should try the previous major Python Versions until you find one that works on your system (eg: Something like 3.9) diff --git a/docs/Images/Yoroi_Extract_MasterKey_Chrome.jpg b/docs/Images/Yoroi_Extract_MasterKey_Chrome.jpg new file mode 100644 index 000000000..8fc0eb2f7 Binary files /dev/null and b/docs/Images/Yoroi_Extract_MasterKey_Chrome.jpg differ diff --git a/docs/Images/Yoroi_Extract_MasterKey_Firefox.jpg b/docs/Images/Yoroi_Extract_MasterKey_Firefox.jpg new file mode 100644 index 000000000..182d95cec Binary files /dev/null and b/docs/Images/Yoroi_Extract_MasterKey_Firefox.jpg differ diff --git a/docs/Images/donate-bch-qr.png b/docs/Images/donate-bch-qr.png new file mode 100644 index 000000000..39caa37c1 Binary files /dev/null and b/docs/Images/donate-bch-qr.png differ diff --git a/docs/Images/donate-btc-qr.png b/docs/Images/donate-btc-qr.png new file mode 100644 index 000000000..f883df743 Binary files /dev/null and b/docs/Images/donate-btc-qr.png differ diff --git a/docs/Images/donate-eth-qr.png b/docs/Images/donate-eth-qr.png new file mode 100644 index 000000000..f977313a9 Binary files /dev/null and b/docs/Images/donate-eth-qr.png differ diff --git a/docs/Images/donate-ltc-qr.png b/docs/Images/donate-ltc-qr.png new file mode 100644 index 000000000..7cf57136c Binary files /dev/null and b/docs/Images/donate-ltc-qr.png differ diff --git a/docs/Images/download_block_io_wallet.png b/docs/Images/download_block_io_wallet.png new file mode 100644 index 000000000..f184cc730 Binary files /dev/null and b/docs/Images/download_block_io_wallet.png differ diff --git a/docs/Images/download_blockchain_com_wallet.png b/docs/Images/download_blockchain_com_wallet.png new file mode 100644 index 000000000..03ba24977 Binary files /dev/null and b/docs/Images/download_blockchain_com_wallet.png differ diff --git a/docs/Images/download_blockchain_com_wallet_alt.png b/docs/Images/download_blockchain_com_wallet_alt.png new file mode 100644 index 000000000..78d7feab3 Binary files /dev/null and b/docs/Images/download_blockchain_com_wallet_alt.png differ diff --git a/docs/Images/download_dogechain_wallet.png b/docs/Images/download_dogechain_wallet.png new file mode 100644 index 000000000..f5024356e Binary files /dev/null and b/docs/Images/download_dogechain_wallet.png differ diff --git a/docs/Images/gurnec-donate-btc-qr.png b/docs/Images/gurnec-donate-btc-qr.png new file mode 100644 index 000000000..4425f8df4 Binary files /dev/null and b/docs/Images/gurnec-donate-btc-qr.png differ diff --git a/docs/Limitations_and_Caveats.md b/docs/Limitations_and_Caveats.md index 45bfcf1f8..f93257566 100644 --- a/docs/Limitations_and_Caveats.md +++ b/docs/Limitations_and_Caveats.md @@ -1,36 +1,21 @@ -## *btcrecover* Limitations & Caveats ## +# Resource Usage and Security Notes -### Beta Software ### +## Beta Software ## Although this software is unlikely to harm any wallet files, **you are strongly encouraged to only run it with copies of your wallets**. In particular, this software is distributed **WITHOUT ANY WARRANTY**; please see the accompanying GPLv2 licensing terms for more details. Because this software is beta software, and also because it interacts with other beta software, it’s entirely possible that it may fail to find a password which it’s been correctly configure by you to find. -### Delimiters, Spaces, and Special Symbols in Passwords ### +## Resource Usage ## -By default, *btcrecover* uses one or more whitespaces to separate tokens in the tokenlist file, and to separated to-be-replaced characters from their replacements in the typos-map file. It also ignores any extra whitespace in these files. This makes it difficult to test passwords which include spaces and certain other symbols. - -One way around this that only works for the tokenlist file is to use the `%s` wildcard which will be replaced by a single space. Another option that works both for the tokenlist file and a typos-map file is using the `--delimiter` option which allows you to change this behavior. If used, whitespace is no longer ignored, nor is extra whitespace stripped. Instead, the new `--delimiter` string must be used *exactly as specified* to separate tokens or typos-map columns. Any whitespace becomes a part of a token, so you must take care not to add any inadvertent whitespace to these files. - -Additionally, *btcrecover* considers the following symbols special under certain specific circumstances in the tokenlist file (and for the `#` symbol, also in the typos-map file). A special symbol is part of the syntax, and not part of a password. - - * `%` - always considered special (except when *inside* a `%[...]`-style wildcard, see the [Wildcards](../TUTORIAL.md#expanding-wildcards) section); `%%` in a token will be replaced by `%` during searches - * `^` - only special if it's the first character of a token; `%^` will be replaced by `^` during searches - * `$` - only special if it's the last character of a token; `%S` (note the capital `S`) will be replaced by `$` during searches - * `#` - only special if it's the *very first* character on a line, see the [note about comments here](../TUTORIAL.md#basics) - * `+` - only special if it's the first (not including any spaces) character on a line, immediately followed by a space (or delimiter) and then some tokens (see the [Mutual Exclusion](../TUTORIAL.md#mutual-exclusion) section); if you need a single `+` character as a token, make sure it's not the first token on the line, or it's on a line all by itself - * `]` - only special when it follows `%[` in a token to mark the end of a `%[...]`-style wildcard. If it appears *immediately after* the `%[`, it is part of the replacement set and the *next* `]` actually ends the wildcard, e.g. the wildcard `%[]x]` contains two replacement characters, `]` and `x`. - -None of this applies to passwordlist files, which always treat spaces and symbols (except for carriage-returns and line-feeds) verbatim, treating them as parts of a password. - -### Resource Usage ### - -#### Memory #### +### Memory ### When *btcrecover* starts, it's first task is to count all the passwords it's about to try, looking for and recording duplicates for future reference (so that no password is tried twice) and also so it can display an ETA. This duplicate checking can take **a lot** of memory, depending on how many passwords need to be counted, but in some circumstances it can also save a lot of time. If *btcrecover* appears to hang after displaying the `Counting passwords ...` message, or if it outright crashes, try running it again with the `--no-dupchecks` option. After this initial counting phase, it doesn't use up much RAM as it searches through passwords. Although this initial counting phase can be skipped by using the `--no-eta` option, it's not recommended. If you do use `--no-eta`, it's highly recommended that you also use `--no-dupchecks` at the same time. +Before the main search begins *btcrecover* also runs a short benchmark to estimate how quickly passwords or seeds can be checked. This can take a noticeable amount of time for some wallet types, so a message is displayed while it runs. By default the benchmark stops after about 30 seconds, but if you want to shorten or skip this step you can supply `--pre-start-seconds ` to limit how long the benchmark runs (use `0` to skip it entirely) or `--skip-pre-start`. Skipping the benchmark may reduce the accuracy of the reported ETA. + You may want to always use a single `--no-dupchecks` option when working with MultiBit Classic or Electrum wallets because the duplicate checking can actually decrease CPU efficiency (and always decreases memory efficiency) with these wallets in many cases. If you specify `--no-dupchecks` more than once, it will disable even more of the duplicate checking logic: @@ -40,7 +25,7 @@ If you specify `--no-dupchecks` more than once, it will disable even more of the * 3 times - disables duplicate checking which consumes very little memory relative to the duplicates it can potentially find; it's almost never useful to use this level * 4 times - disables duplicate checking which consumes no additional memory; it's never useful to use this level (and it's only available for debugging purposes) -#### CPU #### +### CPU ### By default, *btcrecover* tries to use as much CPU time as is available and spare. You can use the `--threads` option to decrease the number of worker threads (which defaults to the number of logical processors in your system) if you'd like to decrease CPU usage (but also the guess rate). @@ -48,7 +33,7 @@ With MultiBit or Electrum wallets, *btcrecover* may not be able to efficiently u *btcrecover* places itself in the lowest CPU priority class to minimize disruption to your PC while searching. -### Security Issues ### +## Security Issues ## Most Bitcoin wallet software goes to great lengths to protect your wallet password while it's stored unencrypted. *btcrecover* does not. This includes, but is not limited to: @@ -57,24 +42,3 @@ Most Bitcoin wallet software goes to great lengths to protect your wallet passwo * unless you use the `--no-dupchecks` option, a large amount of sensitive password information is stored in RAM temporarily, is not securely overwritten, and is very likely swapped out to the paging file where it could remain for a long time even after *btcrecover* has exited. None of these issues are intentionally malicious, they should be considered security bugs. There are no workarounds for them, short of only running *btcrecover* inside a VM on a hard disk drive (not a solid-state drive) and securely deleting the VM once finished, all of which is far beyond the scope of this tutorial... - -### Typos Gory Details ### - -The intent of the typos features is to only apply at most one typo at a time to any single character, even when applying multiple typos to a single password guess. For example, when specifying `--typos 2 --typo-case --typo-repeat`, each password guess can have up to two typos applied (so two case changes, **or** two repeated characters, **or** one case change plus one repeated character, at most). No single character in a guess will have more than one typo applied to it in a single guess, e.g. a single character will never be both repeated and case-changed at the same time. - -There are however some exceptions to this one-typo-per-character rule-- one intentional, and one due to limitations in the software. - -The `--typos-capslock` typo simulates leaving the caps lock turned on during a guess. It can affect all the letters in a password at once even though it's a single typo. As in exception to the one-typo-per-character rule, a single letter *can* be affected by a caps lock typo plus another typo at the same time. - -The `--typos-swap` typo also ignores the one-typo-per-character rule. Two adjacent characters can be swapped (which counts as one typo) and then a second typo can be applied to one (or both) of the swapped characters. This is more a software limitation than a design choice, but it's unlikely to change. You are however guaranteed that a single character will never be swapped more than once per guess. - -Finally it should be noted that wildcard substitutions (expansions and contractions) occur before typos are applied, and that typos can be applied to the results of wildcard expansions. The exact order of password creation is: - - 1. Create a "base" password from one or more tokens, following all the token rules (mutual exclusion, anchors, etc.). - 2. Apply all wildcard expansions and contractions. - 3. Apply up to a single caps lock typo. - 4. Apply zero or more swap typos. - 5. Apply zero or more character-changing typos (these typos *do* follow the one-typo-per-character rule). - 6. Apply zero or more typo insertions (from the `typos-insert` option). - -At no time will the total number of typos in a single guess be more than requested with the `--typos #` option (nor will it be less than the `--min-typos` option if it's used). diff --git a/docs/Seedrecover_Quick_Start_Guide.md b/docs/Seedrecover_Quick_Start_Guide.md index d6aa8121a..0f7f34ab1 100644 --- a/docs/Seedrecover_Quick_Start_Guide.md +++ b/docs/Seedrecover_Quick_Start_Guide.md @@ -1,52 +1,10 @@ -# *seedrecover.py* # - -*seedrecover.py* is a Bitcoin and Ethereum seed recovery tool which can take a seed with one or more mistakes in it, and recover the correct seed if not too many mistakes have been made. +# seedrecover.py # +*seedrecover.py* is a seed recovery tool which can take a seed with one or more mistakes in it, and recover the correct seed if not too many mistakes have been made. ## Installation ## -Just download the latest version from and unzip it to a location of your choice. - -Additional requirements are described below. - -### Windows ### - - 1. Follow [these instructions](INSTALL.md#python-27) to download and install Python 2.7. - - 2. Open a command prompt window, and type this to install the required Python libraries: - - C:\Python27\Scripts\pip install coincurve==5.2.0 pysha3 - -### Linux ### - -Most distributions include Python 2.7 pre-installed. Two additional Python libraries, coincurve and (for Ethereum wallets) pysha3 are required. For example on Debian-like distributions (including Ubuntu), open a terminal window and type this: - - sudo apt-get install python-pip - sudo pip install coincurve==5.2.0 pysha3 - -### OS X ### - - 1. Follow [these instructions](INSTALL.md#os-x) to download and install the latest version of Python 2.7. - - 2. Open a terminal window (open the Launchpad and search for "terminal"). Type this and then choose `Install` to install the command line developer tools: - - xcode-select --install - - 3. Type this to install the GNU Multiple Precision Arithmetic Library: - - curl -O https://gmplib.org/download/gmp/gmp-6.1.2.tar.xz - tar xf gmp-6.1.2.tar.xz - cd gmp-6.1.2 - ./configure --prefix=/usr/local/opt/gmp - make && make check - sudo make install - cd .. - rm -rf gmp-6.1.2 gmp-6.1.2.tar.xz - - 4. Type this to install coincurve and (for Ethereum wallets) pysha3: - - sudo pip install coincurve==5.2.0 pysha3 - +[Follow the installation guide here... ](INSTALL.md) ## Running *seedrecover.py* ## @@ -58,42 +16,20 @@ In order to run *seedrecover.py*, you'll need these two things: 1. for Electrum (1.x or 2.x), a copy of your wallet file (a wallet file using Electrum 2.8's new full-file encryption won't work here), *or* 2. your master public key (sometimes called an *xpub*), *or* 3. a receiving address that was generated by your wallet from your seed, along with a good estimate of how many addresses you created before the receiving address you'd like to use, *or* - 4. an "address database". If you don't have i., ii., or iii. from above, please see the [Recovery with an Address Database](#recovery-with-an-address-database) section below. + 4. an "address database". If you don't have i., ii., or iii. from above, please see [Recovery with an Address Database](Creating_and_Using_AddressDB.md) To start *seedrecover.py* on OS X, first rename the `seedrecover.py` script file to `seedrecover.command`. Aside from this, starting it is the same for every system: just double-click the `seedrecover.py` (or `seedrecover.command`) file. If you're asked about running it in a terminal, choose *Run in Terminal*. Next, you'll be asked a short series of questions: 1. First you'll be asked to open your wallet file. If you have an Electrum wallet file, find it now - the rest of the steps will then be skipped. Otherwise, click `Cancel` to continue. - 2. Next, select your wallet type. If you're unsure of what to choose, feel free to open an [issue on GitHub](https://github.com/gurnec/btcrecover/issues/new) to see if your wallet software is supported. + 2. Next, select your wallet type. If you're unsure of what to choose, feel free to open an [issue on GitHub](https://github.com/3rdIteration/btcrecover/issues/new) to see if your wallet software is supported. 3. Next you'll be asked for your master public key. If you don't have yours stored anywhere, click `Cancel` to continue. - 4. If you don't have your master public key, next you'll be asked for your Bitcoin addresses. Find as many of your addresses associated with this wallet as you can, and enter them here (separated by spaces). Addresses you created early in your wallet's lifetime are prefereable. If your wallet supports multiple "accounts" each with their own address list, only addresses from your first account should be entered here. + 4. If you don't have your master public key, next you'll be asked for your addresses. Find as many of your addresses associated with this wallet as you can, and enter them here (separated by spaces). Addresses you created early in your wallet's lifetime are prefereable. If your wallet supports multiple "accounts" each with their own address list, only addresses from your first account should be entered here. - 5. If you entered Bitcoin addresses above, next you'll be asked to enter the "address generation limit". *seedrecover.py* works by generating one or more addresses based on each seed it tries. The generation limit is the number of addresses it generates for each seed. Generating fewer addresses will improve *seedrecover.py*'s speed, however if it generates too few, it will miss the correct seed entirely. + 5. If you entered addresses above, next you'll be asked to enter the "address generation limit". *seedrecover.py* works by generating one or more addresses based on each seed it tries. The generation limit is the number of addresses it generates for each seed. Generating fewer addresses will improve *seedrecover.py*'s speed, however if it generates too few, it will miss the correct seed entirely. (If you have done something non-standard and your wallet doesn't increment from the first address, you can also specify the index that this starts counting from with the --addr-start-limit) - For example, let's say you found and entered three Bitcoin addresses in step 4. If you're reasonably sure that all three were within the first 10 addresses ever created in your wallet, you should use `10` for the address generation limit. + For example, let's say you found and entered three addresses in step 4. If you're reasonably sure that all three were within the first 10 addresses ever created in your wallet, you should use `10` for the address generation limit. Finally, you'll be asked for your best guess of what your seed is. - -### Recovery with an Address Database ### - -When *seedrecover.py* tries different guesses based on the seed you entered, it needs a way to determine which seed guess is correct. Normally it uses each seed guess to create a master public key (an *mpk*) and compare it to the mpk you entered, or to create Bitcoin addresses and compare them to the addresses you entered. If you have neither your mpk nor any of your addresses, it's still possible to use *seedrecover.py* but it is more complicated and time consuming. - -This works by generating Bitcoin addresses, just as above, and then looking for each generated address in the entire Bitcoin blockchain. In order to do this, you must first create a database of addresses based on the full blockchain: - - 1. You must use a computer with at least 150GB of free drive space to store the full blockchain and at least 6 GB of RAM. You must have the 64-bit version of Python installed. - - 2. Install a full-node Bitcoin client, such as [Bitcoin Unlimited](https://www.bitcoinunlimited.info/), [Bitcoin Classic](https://bitcoinclassic.com/), [Bitcoin XT](https://bitcoinxt.software/), or [Bitcoin Core](https://bitcoincore.org/). - - 3. Start your Bitcoin client and allow it to fully sync. Depending on your Internet connection and your computer, fully syncing a node can take one or more days. Starting `bitcoin-qt` (or `bitcoind`) with the `-dbcache #` option can help: the `#` is the amount of RAM, in MB, to use for the database cache. If your computer has at least 8 GB of RAM, giving up to `4000` MB to the `-dbcache` will speed things up. Installing Bitcoin on a computer with an SSD can also help. - - 4. Once your Bitcoin client is synced, close the Bitcoin software. - - 5. (On OS X, rename the `create-address-db.py` script file to `create-address-db.command`.) Double-click on the `create-address-db.py` script (in the same folder as `seedrecover.py`) to build the address database using the fully-synced blockchain (it will be saved into the same directory as `create-address-db.py` with the name `addresses.db`) . This process will take about one hour, and use about 4 GB of both RAM and drive space. - - 6. Follow the steps listed in the [Running *seedrecover.py*](#running-seedrecoverpy) section, except that when you get to the address entry window in step 4, click `Cancel`. - - 7. For the next step, you still need to choose an address generation limit. This should be the number of unused addresses you suspect you have at the beginning of your wallet before the first one you ever used. If you're sure you used the very first address in your wallet, you can use `1` here, but if you're not so sure, you should choose a higher estimate (although it may take longer for *seedrecover.py* to run). - -Note that *seedrecover.py* will use about 4 GB of RAM while it is running with an address database. diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md new file mode 100644 index 000000000..ea47580d6 --- /dev/null +++ b/docs/TUTORIAL.md @@ -0,0 +1,431 @@ +contributors: gurnec +# btcrecover.py Tutorial # + +*btcrecover.py* is a free and open source multithreaded wallet password recovery tool with support for Bitcoin Core, MultiBit (Classic and HD), Electrum (1.x and 2.x), mSIGNA (CoinVault), Hive for OS X, Blockchain.com (v1-v3 wallet formats, both main and second passwords), Bither, and Bitcoin & KNC Wallets for Android. It is designed for the case where you already know most of your password, but need assistance in trying different possible combinations. This tutorial will guide you through the features it has to offer. + +## Installation ## + +[Follow the installation guide here... ](INSTALL.md) + +## Running *btcrecover.py* ## + +This tutorial is pretty long... you don't have to read the whole thing. Here are some places to start. + + 1. If you already have a `btcrecover-tokens-auto.txt` file, skip straight to step 6. If not, and you need help creating passwords from different combinations of smaller pieces you remember, start with step 4. If you you think there's a typo in your password, or if you mostly know what your whole password is and only need to try different variations of it, read step 5. + 2. Read [The Token File](#token-lists-and-password-or-seed-lists) section (at least the beginning), which describes how *btcrecover* builds up a whole password you don't remember from smaller pieces you do remember. Once you're done, you'll know how to create a `tokens.txt` file you'll need later. + 3. Read the [Typos](#typos) section, which describes how *btcrecover* can make variations to a whole password to create different password guesses. Once you're done, you'll have a list of command-line options which will create the variations you want to test. + * If you skipped step 4 above, read the simple [Passwordlist](#token-lists-and-password-or-seed-lists) section instead. + 4. Read the [Running *btcrecover*](#running-btcrecover) section to see how to put these pieces together and how to run *btcrecover* in a Command Prompt window. + * (optional) Read the [Testing your config](#testing-your-config) section to view the passwords that will be tested. + * (optional) If you're testing a lot of combinations that will take a long time, use the [Autosave](#autosave) feature to safeguard against losing your progress. + 5. (optional, but highly recommended) Donate huge sums of Bitcoin to the donation address once your password's been found. + +## BIP39/44 Wallets with AddressDB ## + +If you are recovering the passphrase from a BIP39/44 wallet, you can do so either with, or without knowing an address that you are looking for, please see [Recovery with an Address Database](Creating_and_Using_AddressDB.md) for more info. + +## Token Lists and Password or Seed Lists ## +Both password and seed recovery methods allow the use of both a token file and a password/seed list file. For password recovery, at least one of these will be required. (And may be required for some types of seed recovery, eg: unscrambling a seed phrase) + +The password/seed list file also allows the task of generating passwords, and that of testing them, to be split into two seperate steps, enabling the user to take advantages of the speed boost that PYPY offers for password generation, the increased speed of testing in cpython, while also making it trivial to split the task of testing a large number of passphrase across multiple servers. (Or doing single threaded operation of creating a password list seperately to the task of testing it on a more powerful/expensive system) + +Both the password list file and the token file have their own documentation below... + +[Password/Seed List File](passwordlist_file.md) + +[Token List File](tokenlist_file.md) + + +## Typos ## + +*btcrecover* can generate different variations of passwords to find typos or mistakes you may have inadvertently made while typing a password in or writing one down. This feature is enabled by including one or more command-line options when you run *btcrecover*. + +If you'd just like some specific examples of command-line options you can add, please see the [Typos Quick Start Guide](Typos_Quick_Start_Guide.md). + +With the `--typos #` command-line option (with `#` replaced with a count of typos), you tell *btcrecover* up to how many typos you’d like it to add to each password (that has been either generated from a token file or taken from a passwordlist as described above). You must also specify the types of typos you’d like it to generate, and it goes through all possible combinations for you (including the no-typos-present possibility). Here is a summary of the basic types of typos along with the command-line options which enable each: + + * `--typos-capslock` - tries the whole password with caps lock turned on + * `--typos-swap` - swaps two adjacent characters + * `--typos-repeat` - repeats (doubles) a character + * `--typos-delete` - deletes a character + * `--typos-case` - changes the case (upper/lower) of a single letter + +For example, with `--typos 2 --typos-capslock --typos-repeat` options specified on the command line, all combinations containing up to two typos will be tried, e.g. `Cairo` (no typos), `cAIRO` (one typo: caps lock), `CCairoo` (two typos: both repeats), and `cAIROO` (two typos: one of each type) will be tried. Adding lots of typo types to the command line can significantly increase the number of combinations, and increasing the `--typos` count can be even more dramatic, so it’s best to tread lightly when using this feature unless you have a small token file or passwordlist. + +Here are some additional types of typos that require a bit more explanation: + + * `--typos-closecase` - Like `--typos-case`, but it only tries changing the case of a letter if that letter is next to another letter with a different case, or if it's at the beginning or the end. This produces fewer combinations to try so it will run faster, and it will still catch the more likely instances of someone holding down shift for too long or for not long enough. + + * `--typos-replace s` - This tries replacing each single character with the specified string (in the example, an `s`). The string can be a single character, or some longer string (in which case each single character is replaced by the entire string), or even a string with one or more [expanding wildcards](tokenlist_file.md#expanding-wildcards) in it. For example, `--typos 1 --typos-replace %a` would try replacing each character (one at a time) with a lower-case letter, working through all possible combinations. Using wildcards can drastically increase the total number of combinations. + + * `--typos-insert s` - Just like `--typos-replace`, but instead of replacing a character, this tries inserting a single copy of the string (or the wildcard substitutions) in between each pair of characters, as well as at the beginning and the end. + + Even when `--typos` is greater than 1, `--typos-insert` will not normally try inserting multiple copies of the string at the same position. For example, with `--typos 2 --typos-insert Z` specified, guesses such as `CaiZro` and `CZairoZ` are tried, but `CaiZZro` is not. You can change this by using `--max-adjacent-inserts #` with a number greater than 1. + +#### Typos Map #### + + * `--typos-map typos.txt` - This is a relatively complicated, but also flexible type of typo. It tries replacing certain specific characters with certain other specific characters, using a separate file (in this example, named `typos.txt`) to spell out the details. For example, if you know that you often make mistakes with punctuation, you could create a typos-map file which has these two lines in it: + + . ,/; + ; [‘/. + + + In this example, *btcrecover* will try replacing each `.` with one of the three punctuation marks which follow the spaces on the same line, and it will try replacing each `;` with one of the four punctuation marks which follow it. + + This feature can be used for more than just typos. If for example you’re a fan of “1337” (leet) speak in your passwords, you could create a typos-map along these lines: + + aA @ + sS $5 + oO 0 + + This would try replacing instances of `a` or `A` with `@`, instances of `s` or `S` with either a `$` or a `5`, etc., up to the maximum number of typos specified with the `--typos #` option. For example, if the token file contained the token `Passwords`, and if you specified `--typos 3`, `P@55words` and `Pa$sword5` would both be tried because they each have three or fewer typos/replacements, but `P@$$w0rd5` with its 5 typos would not be tried. + + The *btcrecover* package includes a few typos-map example files in the `typos` directory. You can read more about them in the [Typos Quick Start Guide](Typos_Quick_Start_Guide.md#typos-maps). + +### Max Typos by Type ### + +As described above, the `--typos #` command-line option limits the total number of typos, regardless of type, that will ever be applied to a single guess. You can also set limits which are only applied to specific types of typos. For each of the `--typos-xxxx` command-line options above there is a corresponding `--max-typos-xxxx #` option. + +For example, with `--typos 3 --typos-delete --typos-insert %a --max-typos-insert 1`, up to three typos will be tried. All of them could be delete typos, but at most only one will ever be an insert typo (which would insert a single lowercase letter in this case). This is particularly useful when `--typos-insert` and `--typos-replace` are used with wildcards as in this example, because it can greatly decrease the total number of combinations that need to be tried, turning a total number that would take far too long to test into one that is much more reasonable. + +## Typos Gory Details ## + +The intent of the typos features is to only apply at most one typo at a time to any single character, even when applying multiple typos to a single password guess. For example, when specifying `--typos 2 --typo-case --typo-repeat`, each password guess can have up to two typos applied (so two case changes, **or** two repeated characters, **or** one case change plus one repeated character, at most). No single character in a guess will have more than one typo applied to it in a single guess, e.g. a single character will never be both repeated and case-changed at the same time. + +There are however some exceptions to this one-typo-per-character rule-- one intentional, and one due to limitations in the software. + +The `--typos-capslock` typo simulates leaving the caps lock turned on during a guess. It can affect all the letters in a password at once even though it's a single typo. As in exception to the one-typo-per-character rule, a single letter *can* be affected by a caps lock typo plus another typo at the same time. + +The `--typos-swap` typo also ignores the one-typo-per-character rule. Two adjacent characters can be swapped (which counts as one typo) and then a second typo can be applied to one (or both) of the swapped characters. This is more a software limitation than a design choice, but it's unlikely to change. You are however guaranteed that a single character will never be swapped more than once per guess. + +Finally it should be noted that wildcard substitutions (expansions and contractions) occur before typos are applied, and that typos can be applied to the results of wildcard expansions. The exact order of password creation is: + + 1. Create a "base" password from one or more tokens, following all the token rules (mutual exclusion, anchors, etc.). + 2. Apply all wildcard expansions and contractions. + 3. Apply up to a single caps lock typo. + 4. Apply zero or more swap typos. + 5. Apply zero or more character-changing typos (these typos *do* follow the one-typo-per-character rule). + 6. Apply zero or more typo insertions (from the `typos-insert` option). + +At no time will the total number of typos in a single guess be more than requested with the `--typos #` option (nor will it be less than the `--min-typos` option if it's used). + + +## Autosave ## + +Depending on the number of passwords which need to be tried, running *btcrecover* might take a very long time. If it is interrupted in the middle of testing (with Ctrl-C (see below), due to a reboot, accidentally closing the Command Prompt, or for any other reason), you might lose your progress and have to start the search over from the beginning. To safeguard against this, you can add the `--autosave savefile` option when you first start *btcrecover*. It will automatically save its progress about every 5 minutes to the file that you specify (in this case, it was named `savefile` – you can just make up any file name, as long as it doesn’t already exist). + +If interrupted, you can restart testing by either running it with the exact same options, or by providing this option and nothing else: `--restore savefile`. *btcrecover* will then begin testing exactly where it had left off. (Note that the token file, as well as the typos-map file, if used, must still be present and must be unmodified for this to work. If they are not present or if they’ve been changed, *btcrecover* will refuse to start.) + +The autosave feature is not currently supported with passwordlists, only with token files. + + +### Interrupt and Continue ### + +If you need to interrupt *btcrecover* in the middle of testing, you can do so with Ctrl-C (hold down the Ctrl key and press C) and it will respond with a message such this and then it will exit: + + Interrupted after finishing password # 357449 + +If you didn't have the autosave feature enabled, you can still manually start testing where you left off. You need to start *btcrecover* with the *exact same* token file or passwordlist, typos-map file (if you were using one), and command-line options plus one extra option, `--skip 357449`, and it will start up right where it had left off. + + +## Unicode Support ## + +If your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII#ASCII_printable_code_chart) (non-English) characters, you will need to add the `--utf8` command-line option to enable Unicode support. + +Please note that all input to and output from *btcrecover* must be UTF-8 encoded (either with or without a Byte Order Mark, or "BOM"), so be sure to change the Encoding to UTF-8 when you save any text files. For example in Windows Notepad, the file *Encoding* setting is right next to the *Save* button in the *File* -> *Save As...* dialog. + +On Windows (but usually not on Linux or OS X), you may have trouble if any of the command line options you need to use contain any non-ASCII characters. Usually, if it displays in the command prompt window correctly when you type it in, it will work correctly with `btcrecover.py`. If it doesn't display correctly, please read the section describing how to put [command-line options inside the tokens file](#command-line-options-inside-the-tokens-file). + +Also on Windows (but usually not on Linux or OS X), if your password is found it may not be displayed correctly in the command prompt window. Here is an example of what an incorrect output might look like: + + Password found: 'btcr-????-??????' + HTML encoded: 'btcr-тест-пароль' + +As you can see, the Windows command prompt was incapable of rendering some of the characters (and they were replaced with `?` characters). To view the password that was found, copy and paste the `HTML encoded` line into a text file, and save it with a name that ends with `.html` instead of the usual `.txt`. Double-click the new `.html` file and it will open in your web browser to display the correct password: + + HTML encoded: 'btcr-тест-пароль' + + +## Running *btcrecover* ## + +(Also see the [Seed Recovery Quick Start Guide](Seedrecover_Quick_Start_Guide.md).) After you've installed all of the requirements (above) and have downloaded the latest version: + + 1. Unzip the `btcrecover-master.zip` file, it contains a single directory named "btcrecover-master". Inside the btcrecover-master directory is the Python script (program) file `btcrecover.py`. + 2. **Make a copy of your wallet file** into the directory which contains `btcrecover.py`. On Windows, you can usually find your wallet file by clicking on the Start Menu, then “Run...” (or for Windows 8+ by holding down the *Windows* key and pressing `r`), and then typing in one of the following paths and clicking OK. Some wallet software allows you to create multiple wallets. Of course, you need to be sure to copy the correct wallet file. + * BIP-39 passphrases - Please see the [BIP-39 Passphrases](#bip-39-passphrases-electrum-extra-words) section below. + * Bitcoin Core - `%appdata%\Bitcoin` (it's named `wallet.dat`) + * Bitcoin Wallet for Android/BlackBerry, lost spending PINs - Please see the [Bitcoin Wallet for Android/BlackBerry Spending PINs](#bitcoin-wallet-for-androidblackberry-spending-pins) section below. + * Bither - `%appdata%\Bither` (it's named `address.db`) + * Blockchain.com - it's usually named `wallet.aes.json`; if you don't have a backup of your wallet file, you can download one by running the `download-blockchain-wallet.py` tool in the `extract-scripts` directory if you know your wallet ID (and 2FA if enabled) + * Coinomi - Please see the [Finding Coinomi Wallet Files](#finding-coinomi-wallet-files) section below. + * Electrum - `%appdata%\Electrum\wallets` + * imToken - Please see the [Finding imToken Wallet Files](#finding-imtoken-wallet-files) section below. + * Litecoin-Qt - `%appdata%\Litecoin` (it's named `wallet.dat`) + * Metamask (And Metamask clones like Binance Chain Wallet, Ronin Wallet, etc) - Please see the [Finding Metamask Wallet Files](#finding-metamask-wallet-files) section below. + * MultiBit Classic - Please see the [Finding MultiBit Classic Wallet Files](#finding-multibit-classic-wallet-files) section below. + * MultiBit HD - `%appdata%\MultiBitHD` (it's in one of the folders here, it's named `mbhd.wallet.aes`) + * mSIGNA - `%homedrive%%homepath%` (it's a `.vault` file) + + 3. If you have a `btcrecover-tokens-auto.txt` file, you're almost done. Copy it into the directory which contains `btcrecover.py`, and then simply double-click the `btcrecover.py` file, and *btcrecover* should begin testing passwords. (You may need to rename your wallet file if it doesn't match the file name listed insided the `btcrecover-tokens-auto.txt` file.) If you don't have a `btcrecover-tokens-auto.txt` file, continue reading below. + 4. Copy your `tokens.txt` file, or your passwordlist file if you're using one, into the directory which contains `btcrecover.py`. + 5. You will need to run `btcrecover.py` with at least two command-line options, `--wallet FILE` to identify the wallet file name and either `--tokenlist FILE` or `--passwordlist FILE` (the FILE is optional for `--passwordlist`), depending on whether you're using a [Token File](tokenlist_file.md) or [Passwordlist](passwordlist_file.md). If you're using [Typos](#typos) or [Autosave](#autosave), please refer the sections above for additional options you'll want to add. + 6. Here's an example for both Windows and OS X. The details for your system will be different, for example the download location may be different, or the wallet file name may differ, so you'll need to make some changes. Any additional options are all placed at the end of the *btcrecover* line. + * *Windows*: Open a Command Prompt window (click the Start Menu and type "command"), and type in the two lines below. + + cd Downloads\btcrecover-master + python btcrecover.py --wallet wallet.dat --tokenlist tokens.txt [other-options...] + + * *OS X*: Open a terminal window (open the Launchpad and search for "terminal"), and type in the two lines below. + + cd Downloads/btcrecover-master + python btcrecover.py --wallet wallet.dat --tokenlist tokens.txt [other-options...] + +After a short delay, *btcrecover* should begin testing passwords and will display a progress bar and an ETA as shown below. If it appears to be stuck just counting upwards with the message `Counting passwords ...` and no progress bar, please read the [Memory limitations](Limitations_and_Caveats.md#memory) section. If that doesn't help, then you've probably chosen too many tokens or typos to test resulting in more combinations than your system can handle (although the [`--max-tokens`](tokenlist_file.md#token-counts) option may be able to help). + + Counting passwords ... + Done + Using 4 worker threads + 439 of 7661527 [-------------------------------] 0:00:10, ETA: 2 days, 0:25:56 + +If one of the combinations is the correct password for the wallet, the password will eventually be displayed and *btcrecover* will stop running: + + 1298935 of 7661527 [####-----------------------] 8:12:42, ETA: 1 day, 16:13:24 + Password found: 'Passwd42' + +If all of the password combinations are tried, and none of them were correct for the wallet, this message will be dislayed instead: + + 7661527 of 7661527 [########################################] 2 days, 0:26:06, + Password search exhausted + +Running `btcrecover.py` with the `--help` option will give you a summary of all of the available command-line options, most of which are described in the sections above. + +### Testing your config ### + +If you'd just like to test your token file and/or chosen typos, you can use the `--listpass` option in place of the `--wallet FILE` option as demonstrated below. *btcrecover* will then list out all the passwords to the screen instead of actually testing them against a wallet file. This can also be useful if you have another tool which can test some other type of wallet, and is capable of taking a list of passwords to test from *btcrecover*. Because this option can generate so much output, you may want only use it with short token files and few typo options. + + python btcrecover.py --listpass --tokenlist tokens.txt | more + +The `| more` at the end (the `|` symbol is a shifted `\` backslash) will introduce a pause after each screenful of passwords. + +### Extracting Yoroi Master Key ### +**Chrome Based Browser Wallets** + +You will need to first open your Yoroi Wallet, then enable open the Developer Tools in your browser. + +You then need to navigate to "Application" (Chrome), go to the "IndexedDB" section, open the "Yoroi-Schema" and navigate to the "Key" section. + +You will then see a list of master keys. You probably want the first Encrypted Key, as shown below: + +![Yoroi_Masterkey](Yoroi_Extract_MasterKey_Chrome.jpg) + +You can then click on the "Hash" field and select Copy. This string is what you will use with the `--yoroi-master-password` argument + +**Firefox Browser Wallets** + +You can find the data by accessing the .sqlite file directly for the extension. + +This will be found in your browser profile folder (This location of this will vary based on your environment) for the extension. You can see an example of were this file was found for a Windows environment in the very top of the screenshot below. + +![Yoroi_Masterkey](Yoroi_Extract_MasterKey_Firefox.jpg) + +You can then simply open it with a text editor and look for the string "Hash" or "isEncrypted", your encrypted Master-Password will be visible in clear text. (Highlighted in green in the screenshot above) + +This string is what you will use with the `--yoroi-master-password` argument + +### Finding MultiBit Classic Wallet Files ### + +There are two different files that *btcrecover* can be used wth for this type of wallet. While you can run BTCRecover with MultiBit Classic .wallet files, you are far better off using MultiBit private key backup files. These private key backup files are much faster to try passwords against (Faster by a factor of over 1,000) These key backup files are created when you first add a password to your MultiBit Classic wallet, and after that each time you add a new receiving address or change your wallet password. + +These are key backups are created in the`key-backup` directory that's near the wallet file. + +For the default wallet that is created when MultiBit is first installed, this directory is located here: + + %appdata%\MultiBit\multibit-data\key-backup + +The key files have names which look like `walletname-20140407200743.key`. If you've created additional wallets, their `key-backup` directories will be located elsewhere and it's up to you to locate them. Once you have, choose the most recent `.key` file and copy it into the directory containing `btcrecover.py` for it to use. + +### Finding Metamask Wallet Files ### +For Chrome Based Browsers, you will need to locate the data folder for the browser extension. You then use the path to this wallet folder with the --wallet argument. + +For Metamask this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn + +For Binance Wallet Extension this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\fhbohimaelbohpjbbldcngcnapndodjp + +For Ronin Wallet this is: %localappdata%\Google\Chrome\User Data\Default\Local Extension Settings\fnjhmkhhmkbjkkabndcnnogagogbneec + +If you are trying to recover anything other than the most recent wallet, you will need to use the extract script to list all of the possible vaults that are in the extension data. + +For Firefox and iOS, you will need to retrieve your Metamask vault using the process described here: +https://metamask.zendesk.com/hc/en-us/articles/360018766351-How-to-use-the-Vault-Decryptor-with-the-MetaMask-Vault-Data + +For Mobile wallets (iOS and Android) the "wallet-file" that you pass BTCRecover is the file: `persist-root` You can find it using the process above and use it directly with BTCRecover. (No need to extract the vault data only, remove excess `\` characters, etc, all this is handled automatically) + +For Android devices, you will mostly need a "rooted" phone. The file you are after is: `/data/data/io.metamask/files/persistStore/persist-root` + +You can then copy/paste the vault data (from either Firefox or an extract script) in to a text file and use that directly with the --wallet argument. + +### Finding Coinomi Wallet Files ### +**Note: This only supports wallets that are protected by a password. If you selected "no password", "biometrics" or "password + biometrics" then you will also need information from your phones keystore... (Which may be impossible to retrieve)** + +The first step for Coinomi depends on which platform you are running it on. + +For Windows users, it's simply a case of navigating to %localappdata%\Coinomi\Coinomi\wallets and you will find your wallet files. + +For Android users, you will need to have a rooted phone which will allow you to access the .wallet file in the Coinomi. (It should be found in the folder data\data\com.coinomi.wallet\files\wallets) How to get root access on your particular phone is beyond the scope of this document, but be warned that some methods of rooting your phone will involve a factory reset. + +If there are mulitiple wallets there and you are not sure which is the correct one, the name of each wallet can be found in clear text at the end of the file. [See the test wallets included with this repository in ./btcrecover/test/test-wallets](https://github.com/3rdIteration/btcrecover/tree/master/btcrecover/test/test-wallets) for an example) + +### Finding imToken Wallet Files ### +For Android users, you will need to have a rooted phone which will allow you to access the files. + +The main wallet file is located at `/data/data/im.token.app/files/wallets/identity.json` + +How to get root access on your particular phone is beyond the scope of this document, but be warned that some methods of rooting your phone will involve a factory reset. + +For iOS users, the file that you are looking for should have the same name and be in a similar location, but you will need a Jailbroken device to be able to access it. + +### Downloading Blockchain.com wallet files ### +Downloading these kinds of wallet files id done via your browser, through the "Developer Tools" feature. + +Basically you need to attempt to log in to your wallet (even with the wrong password) and save the wallet file that is downloaded as part of this process. + +Once you are at the blockchain.com wallet login page, with the developer tools open in your browser, you will need to do the following steps: + +1) Select the Network tab + +2) Enter your Wallet ID, Enter a placeholder password (you can enter anything) + +3) Approve the login via email authentication (if applicable) + +4) Enter your 2fa code (if applicable) and click Log In (It will say "Wrong Password", but this is normal) + +5) Select the "wallet" items + +6) Select the "Response" tab + +7) There are two possible ways that the wallet data may be presented. The main difference will be the filename for the response that contains the wallet payload. + +It will look like this: + +![Download Blockchain Wallet](download_blockchain_com_wallet.png) + +Or this + +![Download Blockchain Wallet Alt](download_blockchain_com_wallet_alt.png) + +In both instances, what we want is the "payload" + +Once you have a response that looks like wallet data, right click on the wallet item, copy it and paste it in to a file using a text editor... + +### Downloading Dogechain.info wallet files ### +Downloading these kinds of wallet files id done via your browser, through the "Developer Tools" feature. + +Basically you need to attempt to log in to your wallet (even with the wrong password) and save the wallet file that is downloaded as part of this process. + +![Download Dodgechain Wallet](download_dogechain_wallet.png) + +### Downloading block.io wallet files ### +Downloading these kinds of wallet files id done via your browser, through the "Developer Tools" feature. + +Basically you need to log in to your wallet and then go in to the "Settings" screen, once there you can open the "Developer tools" in your browser. + +1) Select the Network tab + +2) Enter a placeholder PIN in the "Current PIN" field. (This can be anything, eg: "123") + +3) Enter a placeholder password in the New Secret PIN field. (This can be anything, but must be valid, eg: btcrtestpassword2022) + +4) Click "Change Secret PIN" (This will give an error that your Secret PIN is incorrect, but that doesn't matter...) + +5) Select "Responses" + +6) Select the initiate_change_secrets file. + +7) Once you have a response that looks like wallet data, copy it and paste it in to a text file. This is your wallet file... + +![Download Block IO Wallet](download_block_io_wallet.png) + +### Bitcoin Wallet for Android/BlackBerry Spending PINs ### + +Bitcoin Wallet for Android/BlackBerry has a *spending PIN* feature which can optionally be enabled. If you lose your spending PIN, you can use *btcrecover* to try to recover it. + + 1. Open the Bitcoin Wallet app, press the menu button, and choose Safety. + 2. Choose *Back up wallet*. + 3. Type in a password to protect your wallet backup file, and press OK. You'll need to remember this password for later. + 4. Press the Archive button in the lower-right corner. + 5. Select a method of sharing the wallet backup file with your PC, for example you might choose Gmail or perhaps Drive. + +This wallet backup file, once saved to your PC, can be used just like any other wallet file in *btcrecover* with one important exception: when you run *btcrecover*, you **must** add the `--android-pin` option. When you do, *btcrecover* will ask you for your backup password (from step 3), and then it will try to recover the spending PIN. + +Because PINs usually just contain digits, your token file will usually just contain something like this (for PINs of up to 6 digits for example): `%1,6d`. (See the section on [Wildcards](tokenlist_file.md#expanding-wildcards) for more details.) + +Note that if you don't include the `--android-pin` option, *btcrecover* will try to recover the backup password instead. + +### BIP-39 Passphrases & Electrum "Extra Words" ### + +Some [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) compliant wallets offer a feature to add a "25th word", “BIP-39 passphrase” or “plausible deniability passphrase” to your seed (mnemonic) (Note that most hardware wallets also offer a PIN feature which is not supported by *btcrecover*.) + +If you know your seed, but don't remember this passphrase, *btcrecover* may be able to help. You will also need to know either: + + 1. Preferably your master public key / “xpub” (for the *first* account in your wallet, if it supports multiple accounts), *or* + 2. a receiving address that was generated by your wallet (in its first account), along with a good estimate of how many addresses you created before the receiving address you'd like to use. + +Once you have this information, run *btcrecover* normally, except that *instead* of providing a wallet file on the command line as described above with the `--wallet wallet.dat` option, use the `--bip39` option, e.g.: + + python btcrecover.py --bip39 --tokenlist tokens.txt [other-options...] + +If the address/accounts that you are trying to recover are from a BIP39/44 wallet, but for a currency other than Bitcoin, you can use the `--wallet-type` argument and specify any supported BIP39 wallet type that is supported by seedrecover.py. (Eg: bch, bip39, bitcoinj, dash, digibyte, dogecoin, ethereum, electrum2, groestlecoin, litecoin, monacoin, ripple, vertcoin, zilliqa) You can also attempt recovery with unsupported coins that share a derivation scheme with any of these by using the `--bip32-path` argument with the derivation path for that coin. + +For more info see the notes on [BIP39 Accounts and Altcoins](bip39-accounts-and-altcoins.md) + +If you are unsure of both your seed AND your BIP39 passphrase, then you will need to use seedrecover.py and specify multiple BIP39 passphrases. (But be aware that this is very slow) + +### GPU acceleration for Bitcoin Core and Litecoin-Qt wallets ### + +*btcrecover* includes experimental support for using one or more graphics cards or dedicated accelerator cards to increase search performance. This can offer on the order of *100x* better performance with Bitcoin Unlimited/Classic/XT/Core or Litecoin-Qt wallets when enabled and correctly tuned. + +For more information, please see the [GPU Acceleration Guide](GPU_Acceleration.md). + +### command-line options inside the tokens file ### + +If you'd prefer, you can also place command-line options directly inside the `tokens.txt` file. In order to do this, the very first line of the tokens file must begin with exactly `#--`, and the rest of this line (and only this line) is interpreted as additional command-line options. For example, here's a tokens file which enables autosave, pause-before-exit, and one type of typo: + + #--autosave progress.sav --pause --typos 1 --typos-case + Cairo + Beetlejuice Betelgeuse + Hotel_california + +### btcrecover-tokens-auto.txt ### + +Normally, when you run *btcrecover* it expects you to run it with at least a few options, such as the location of the tokens file and of the wallet file. If you run it without specifying `--tokenlist` or `--passwordlist`, it will check to see if there is a file named `btcrecover-tokens-auto.txt` in the current directory, and if found it will use that for the tokenlist. Because you can specify options inside the tokenlist file if you'd prefer (see above), this allows you to run *btcrecover* without using the command line at all. You may want to consider using the `--pause` option to prevent a Command Prompt window from immediately closing once it's done running if you decide to run it this way. + + +# Limitations & Caveats # + +### Beta Software ### + +Although this software is unlikely to harm any wallet files, **you are strongly encouraged to only run it with copies of your wallets**. In particular, this software is distributed **WITHOUT ANY WARRANTY**; please see the accompanying GPLv2 licensing terms for more details. + +Because this software is beta software, and also because it interacts with other beta software, it’s entirely possible that it may fail to find a password which it’s been correctly configure by you to find. + +### Additional Limitations & Caveats ### + +Please see the separate [Limitations and Caveats](Limitations_and_Caveats.md) documentation for additional details on these topics: + + * Delimiters, Spaces, and Special Symbols in Passwords + * Memory & CPU Usage + * Security Issues + * Typos Details + + +# Copyright and License # + +btcrecover -- Bitcoin wallet password and seed recovery tool + +Copyright (C) 2014-2017 Christopher Gurnee + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ diff --git a/docs/Typos_Quick_Start_Guide.md b/docs/Typos_Quick_Start_Guide.md index 533f50a4d..2dbbe19c1 100644 --- a/docs/Typos_Quick_Start_Guide.md +++ b/docs/Typos_Quick_Start_Guide.md @@ -1,6 +1,6 @@ ## *btcrecover* Typos Quick Start Guide ## -If you only have a single (or just a few) passwords that you'd like to apply some typos to, you can use the following table to pick a set of [typos command-line options](../TUTORIAL.md#typos) with which to run *btcrecover*. +If you only have a single (or just a few) passwords that you'd like to apply some typos to, you can use the following table to pick a set of [typos command-line options](TUTORIAL.md#typos) with which to run *btcrecover*. The leftmost column contains the command-line options, with a full set of options on each row (if the options for a row is blank, they're the same as the row above). As a general rule, each successive row of options will try a larger set of typos than the preceding row. You should select a row which includes the same type of wallet you intend to test, along with a password length that's similar to your password(s). The columns on the right will give you a rough estimate of how many password variations there are and of how long *btcrecover* will take to check the variations (per input password to be tested). @@ -27,7 +27,7 @@ The leftmost column contains the command-line options, with a full set of option ### Typos Maps ### -The *btcrecover* package includes a few [typos-map](../TUTORIAL.md#typos-map) example files in this directory. One of them, `us-with-shifts-map.txt`, is used in the Quick Start suggestions above. +The *btcrecover* package includes a few [typos-map](TUTORIAL.md#typos-map) example files in this directory. One of them, `us-with-shifts-map.txt`, is used in the Quick Start suggestions above. * `us-map.txt` - For each key on a standard US ANSI ASCII keyboard, this typos-map file will try an adjacent key to test the case where your finger may have slipped one position while typing a password. This typos-map is only intended for testing passwords which do not contain any shifted letters or symbols. diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/Example_Descrambling_a_12_word_seed.md b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/Example_Descrambling_a_12_word_seed.md new file mode 100644 index 000000000..1cc939072 --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/Example_Descrambling_a_12_word_seed.md @@ -0,0 +1,85 @@ +# Descrambling 12 Word Seeds +[YouTube Video can be found here: https://youtu.be/ruSF8OKwBRk](https://youtu.be/ruSF8OKwBRk) + +Three types of token files are provided for these tests. Token files that will find the result on their first check, token files that will find the result as the last possible combination and those which will find it at some point inbetween. + +The idea is that these allow you to quickly verify that things are working (finding on the first result), get an idea for how long a full run might take (last result) and also try something representative of a real world situation. + +If you are just descrambing a 12 word seed, there isn't much point running without --no-eta, as the number of seeds to be tested can be easily worked out and the seed generator can easily keep up with at least 48 threads. + +**Note:** The YouTube video and examples were made before OpenCL acceleration was added to Blockchain.com wallets and can give a 2x performance improvement. (See [GPU Accleration](../../GPU_Acceleration.md) for more info) + +**Performance** + +On a 48 core Linode you can expect to... +* Descramble a 12 word Electrum seed in less than 15 minutes… +* Descramble a 12 word BIP39 seed in less than 50 minutes… + +_You can expect things to take about 5 times this long on a current (mid 2020), mid-range CPU._ + +## Electrum +### Legacy Wallet (Last Result) +**Using Tokenlist lastcombination_electrum.txt** +``` linenums="1" +{% include "./lastcombination_electrum.txt" %} +``` + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --addr-limit 1 --addrs 1CU62HPowYSxhHiiNu1ukSbMjrkGj4x52i --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_electrum.txt --wallet-type electrum2 +``` + +### Segwit Wallet +**Using Tokenlist randomcombination_electrum.txt** +``` linenums="1" +{% include "./randomcombination_electrum.txt" %} +``` + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type electrum2 --addr-limit 1 --addrs bc1qtylwmarke39nysxepdx5xzfatvrlel5z8m0jx2 --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_electrum.txt --bip32-path "m/0'/0" +``` + +## BIP39 +### Ethereum Address (Default derivation path for Trezor, MEW) +**Using Tokenlist randomcombination_bip39.txt** +``` linenums="1" +{% include "./randomcombination_bip39.txt" %} +``` + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type ethereum --addr-limit 1 --addrs 0x66F9C09118B1C726BC24811a611baf60af42070A --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_bip39.txt --bip32-path "m/44'/60'/0'/0" +``` + +### Legacy BTC Address (First Result) +**Using Tokenlist firstcombination_bip39.txt** +``` linenums="1" +{% include "./firstcombination_bip39.txt" %} +``` + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/firstcombination_bip39.txt +``` + +### Legacy BTC Address (Last Result) +**Using Tokenlist lastcombination_bip39.txt** +``` linenums="1" +{% include "./lastcombination_bip39.txt" %} +``` +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_bip39.txt +``` + +### Litecoin Native Segwit Address (Seed with Positional Anchors for known words, last word as any valid BIP39 word starting with 'B') +**Using Tokenlist fixedwords_bip39.txt** +``` linenums="1" +{% include "./fixedwords_bip39.txt" %} +``` + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs ltc1q9srpp39hev6dpsxjjp8t5g0m3z7509vc9llalv --tokenlist ./docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/fixedwords_bip39.txt --bip32-path "m/84'/2'/0'/0" +``` diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/firstcombination_bip39.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/firstcombination_bip39.txt new file mode 100644 index 000000000..0449bb5c5 --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/firstcombination_bip39.txt @@ -0,0 +1,12 @@ +boy +attitude +convince +spring +husband +gloom +season +rich +famous +kidney +hidden +ocean \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/fixedwords_bip39.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/fixedwords_bip39.txt new file mode 100644 index 000000000..83bc8ecbf --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/fixedwords_bip39.txt @@ -0,0 +1,12 @@ +^1^ocean ^2^ocean ^3^ocean +^1^hidden ^2^hidden ^3^hidden +^1^kidney ^2^kidney ^3^kidney +^4^famous +^5^rich +^6^season +gloom +husband +spring +convince +attitude +baby bachelor bacon badge bag balance balcony ball bamboo banana banner bar barely bargain barrel base basic basket battle beach bean beauty because become beef before begin behave behind believe below belt bench benefit best betray better between beyond bicycle bid bike bind biology bird birth bitter black blade blame blanket blast bleak bless blind blood blossom blouse blue blur blush board boat body boil bomb bone bonus book boost border boring borrow boss bottom bounce box boy bracket brain brand brass brave bread breeze brick bridge brief bright bring brisk broccoli broken bronze broom brother brown brush bubble buddy budget buffalo build bulb bulk bullet bundle bunker burden burger burst bus business busy butter buyer buzz \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_bip39.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_bip39.txt new file mode 100644 index 000000000..1fa2e85fd --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_bip39.txt @@ -0,0 +1,12 @@ +ocean +hidden +kidney +famous +rich +season +gloom +husband +spring +convince +attitude +boy \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_electrum.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_electrum.txt new file mode 100644 index 000000000..cccc382c3 --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/lastcombination_electrum.txt @@ -0,0 +1,12 @@ +book +fit +fly +ketchup +also +elevator +scout +mind +edit +fatal +where +rookie \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_bip39.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_bip39.txt new file mode 100644 index 000000000..96c111ee9 --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_bip39.txt @@ -0,0 +1,12 @@ +boy +hidden +kidney +famous +spring +convince +rich +season +gloom +ocean +husband +attitude \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_electrum.txt b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_electrum.txt new file mode 100644 index 000000000..228cabe85 --- /dev/null +++ b/docs/Usage_Examples/2020-05-02_Descrambling_a_12_word_seed/randomcombination_electrum.txt @@ -0,0 +1,12 @@ +social +shoe +spin +enlist +sponsor +resource +result +coffee +ocean +gas +file +paddle \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/Example_Recovering_Blockchain_Wallet_Passwords.md b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/Example_Recovering_Blockchain_Wallet_Passwords.md new file mode 100644 index 000000000..708e5bf25 --- /dev/null +++ b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/Example_Recovering_Blockchain_Wallet_Passwords.md @@ -0,0 +1,113 @@ +# Recovering Blockchain.com Wallets +#### _Previously known as blockchain.info_ +[YouTube Video can be found here: https://youtu.be/rkw23s7s4as](https://youtu.be/f63FpoTKwSw) + +**Note:** The YouTube video and examples were made before OpenCL acceleration was added to Blockchain.com wallets and can give at least a 10x performance improvement. (See [GPU Accleration](../../GPU_Acceleration.md) for more info) + +## Overview +[Firstly you download the wallet file as per the process here:](../../TUTORIAL.md#downloading-blockchaincom-wallet-files) + +Once you have that file, there are three ways that blockchain.com wallets can be recovered. + +**1) Using the wallet file directly** + +Running BTCRecover with a wallet.aes.json file downloaded from blockchain.com. This is the "normal" way of running BTCRecover where the person/PC running BTCRecover will have enough information to immediately use the recovered wallet file. (You will therefore want to take precautions around the environment you run BTCRecover in) + +**2) Using a wallet file "extract"** + +Extracting a small amount of data from the wallet file and running BTCRecover with that... What this means is that you can either give this portion of data to someone else to recover for you, or run it on some cloud based machine, without having to worry about it leaking info that would allow someone to steal your crypto. (You therefore don't need to worry as much about the security of the environmen in which you run BTCRecover) + +Using a wallet extract requires a few extra steps... [See here for more info about Extract Scripts...](../../Extract_Scripts.md) + +**3) Recover the wallet password from legacy wallet recovery mnemonic** + +Blockchain.info previously offered users a wallet recovery mnemonic phrase to [recover their wallet passwords](https://login.blockchain.com/wallet/recover-wallet) from different dictionaries [v2](https://github.com/blockchain/unused-My-Wallet/blob/master/mnemonic.js#L319), [v3](https://github.com/blockchain/unused-My-Wallet/blob/master/mnemonic_words_v3.html) + +The recovery mnemonic phase can contain between 6 - 25+ words, though v2 mnemonics will be a multiple of 3 + +Once you recover your seed phrase it will reveal your password, the password can be used with the wallet the "normal" way, if you changed your password it will only recover the first password. + +## Example 1 - Using a TokenList to recover wallet Main Password from a wallet file + +### Download the wallet file... + +[Download the wallet file as per the process here:](../../TUTORIAL.md#downloading-blockchaincom-wallet-files) + +After doing the process above, we will save the wallet data in a file called wallet.aes.json (Which can just be left in your BTCRecover folder be used instead of the wallet file in any of the examples below) + +### Create the TokenList File +**Example Tokenlist - tokenListTest.txt** +``` linenums="1" +{% include "./tokenListTest.txt" %} +``` + +This file contains some basic tokens that make up the password. (Useful if you re-use sentences, words or phrases in passwords) It has one anchored token (eg: We know which token we started with) as well as some examples of OR type tokens where it will only select one per row. (In this case, let's say you used one of these characters - _ ! = @ in between words and also tended to add the year in there somewhere) + +### Run BTCRecover on the Wallet File +**Command** + +`python btcrecover.py --wallet btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json --typos-capslock --tokenlist ./docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/tokenListTest.txt +` + +If you had downloaded the wallet file as above, you would have a file wallet.aes.json in your BTCRecover folder. (You can copy it from this example folder if you like) You would then just use the command: + +**Command** + +`python btcrecover.py --wallet wallet.aes.json --typos-capslock --tokenlist ./docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/tokenListTest.txt +` + +## Example 2 - Using a PasswordList+CommonTypos to recover a wallet Second Password from a wallet file + +### Download the Wallet File the same as in the previous example... + +Using the password that we found from the previous step... _btcr-test-password_ + +### Create the PasswordList File +**Example Passwordlist: passwordListTest_1.txt** + +``` linenums="1" +{% include "./passwordListTest_1.txt" %} +``` +This file contains the correct password with 4 typos in it + the first twenty options off the RockYou password list... + +### Run BTCRecover on the Wallet File +**Command** + +``` +python btcrecover.py --wallet btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json --blockchain-secondpass --typos-case --typos-delete --typos 4 --passwordlist docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/passwordListTest_1.txt +``` + +## Example 3 - Same as example 2 but using a wallet extract + +### Extract Sample Data from a Wallet File to solve a second password +**Command** + +`python ./extract-scripts/extract-blockchain-second-hash.py ./btcrecover/test/test-wallets/blockchain-v2.0-wallet.aes.json +` +We will then be prompted for the main wallet password which we know is btcr-test-password + +This script will then return the data: + +`Blockchain second password hash, salt, and iter_count in base64: +YnM6LeP7peG853HnQlaGswlwpwtqXKwa/1rLyeGzvKNl9HpyjnaeTCZDAaC4LbJcVkxaECcAACwXY6w=` + +### Run BTCRecover with the Extracted Script +**Command** + +`python btcrecover.py --data-extract --typos-case --typos-delete --typos 4 --passwordlist docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/passwordListTest_1.txt` + +You will be prompted to enter the data extract, so paste `YnM6LeP7peG853HnQlaGswlwpwtqXKwa/1rLyeGzvKNl9HpyjnaeTCZDAaC4LbJcVkxaECcAACwXY6w=` from the previous step. + +## Example 4 - Recovering the wallet password for a Blockchain Legacy Recovery Mnemonic + +**Note:** These are an older type of mnemonic that is not the same as a BIP39 mnemonic. These mnemonics came in various lengths and could be used to recover either the account password, or the account ID and password. + +The password will only be the password that was set when the mnemonic was created, if the password was changed after this then this recovery mnemonic is of no use to you... If you still have the wallet.aes.json file, then you can also use the password derived from this process to decrypt/dump the wallet file. + +### Run BTCRecover with your suspected seed phrase and length +BTCRecover will try different combinations and use a checksum to identify the correct seed phrase and password + +**Command** + +`python seedrecover.py --wallet-type blockchainpasswordv3 --mnemonic "carve witch manage yerevan yerevan yerevan yerevan yerevan yerevan yerevan yerevan hardly hamburgers insiders hamburgers ignite infernal" --mnemonic-length 17 +` \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/passwordListTest_1.txt b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/passwordListTest_1.txt new file mode 100644 index 000000000..7ea7645db --- /dev/null +++ b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/passwordListTest_1.txt @@ -0,0 +1,21 @@ +Btcr-Test-Passwords +123456 +12345 +123456789 +password +iloveyou +princess +1234567 +rockyou +12345678 +abc123 +nicole +daniel +babygirl +monkey +lovely +jessica +654321 +michael +ashley +qwerty \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/tokenListTest.txt b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/tokenListTest.txt new file mode 100644 index 000000000..3e077c200 --- /dev/null +++ b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/tokenListTest.txt @@ -0,0 +1,8 @@ +^1^btcr +test +pass +word +- _ ! = @ +- _ ! = @ +- _ ! = @ +2012 2013 2014 2015 2016 2017 2018 2019 2020 \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/wallet.aes.json b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/wallet.aes.json new file mode 100644 index 000000000..503d8ddd8 --- /dev/null +++ b/docs/Usage_Examples/2020-05-08_Recovering_Blockchain_Wallet_Passwords/wallet.aes.json @@ -0,0 +1 @@ +{"pbkdf2_iterations":5000,"version":3,"payload":"eR+t/EmiIOSVs6k9jsqaMDyAetcPrFFd8PIlpwC4QoDjDDWKQxYBm+Y3ou7PCiK1xvmV4E6sbvkRB5L6Q/IiPUqTCqQNVXxBDePUdYEW47sd2atxDEK1+Bmlx2Q0Io007yoUm8bj7GGz/0g0QeSaty2HwaqX5mnPEB4nNb+v9/YkCeCQp0XGGRmtKqEd6/ZyN1rfoodv74xki/XeBBME/k2B5UAeyCaNhRrCp1wOn6lPt88JwUT0k2WKRW/9jUdCyuyrI53+eexRWnP3dUehP/V2Dvb3gheyDmGvo7XHkRmnkCtGbPj/0oMBTbNH49gAplSbzCwGzqhMtTBAXH7vKhJzjOur3wfSxPO+lW0VppqL2zbMvcJcMXMmCbLto/nS7iEX72nQfax/nHgA8n4TLdHv5nS4E1NH0u2iYL1u13D/2C0sHcGCueDHwyvznQVehGRQUS/3GyrHIk2wVDqqFroqyV9TsFeff/Z/XkMy1Bc49faDfoapIyhBkrsWEO94o4QVVmB+jOiM9qGQfNtpioTsmPSb/59gU07i1rOuG0Lwc36aXnUM5VKnqjxYDKmR+Tf4LEhvtFGXoTkAl1ZeCYiMM0seQc9jbNU1VCWjs/9whrUa3Tt1/8VUxqrDeAapVaPtwjLwecnxLualrF9vEnLqpWtns+lCNxeM1Gy686zUOEPMhDZWEs3brROKL3sc07bW73CxjlCIP/zYkD2mk61MKJ64t8DYBrhPbCy/sP5bnBwo1LtnNlVC3FrGGg5CIXsJEDcfFAMJL7w9LRcSrahs61M9eR7gJJpoXuSv8gvjTwImg8wCMulfcpyuc5+Ft3XopE/58JS3n9/RQcQbv/tjDLq2EpSw7EOSNHBpTqP4xPTvUTBUjJ+llB76BiEpUusglxPysdE7/7HzVFR2cICmgqEVMZu4FUPu3fqlujk9Kf4RTyYCHHJheotewg5Xp8MsTtV4oFVteAnCFy2xQ/+oA71a28x6/dcuGLO07c/X5fBAaPo8+5IUGCAN78KcLXDzNgXMNgX9iV3hBW9f5+f2jWjpNXId06CaMcjmiez6o3/2mgMw6Kcmtno3G+36VXwsglPDVcBN/ueKEbin08gndHD8nlX7YqA3zjZ39rPo7lwovk8uH7l8A16L91zXPOFXa92XDA/ZMgoikaj578T0qLVOj4zbiu1+DQ//+uRsaJv6PgjZN/g+HzVlR6jgCQpZnN7YfQ3vroXtLwKODcb8XoX3eH7krHM3llypE1mcWmz43EPBxe5voyeMdE+2+IROxLCPXA+SiEPA5PevMSUN+OL+hCQ3acJwrkqZI3H68dF2wnfyCoi5q8u56ahT"} \ No newline at end of file diff --git a/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/Example_multi_device_descrambling_12_word_seed_with_extras.md b/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/Example_multi_device_descrambling_12_word_seed_with_extras.md new file mode 100644 index 000000000..6ce252f3b --- /dev/null +++ b/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/Example_multi_device_descrambling_12_word_seed_with_extras.md @@ -0,0 +1,63 @@ +# "Required" Anchors, "Positional" Anchors and Spreading Work Accross Multiple Devices +YouTube Video can be found here: TBC + +## **Background** + +In addition to being able to simply descramble a BIP39 seed using a tokenlist of potential words, BTCRecover can also be used in a situation where you may know the position of _some_ words while also having additional decoy words in a seed phrase. + +**Example tokenlist in use: tokenlist.txt** +``` linenums="1" +{% include "./tokenlist.txt" %} +``` + +For larger recovery jobs, BTCRecover also allows you to spread the workload over multiple devices. It's important to understand that the process that BTCRecover uses is **deterministic** what this means in simple terms is that every time you run BTCRecover, it will search through potential passwords/passphrases in exactly the same order, every single time. The implication of this is that simply running it on two devices at the same time, without using the --workers command, will simply be doing the identical work twice... + +Fortunately, you can split a search up into slices and assign these slices to different devices. The mechanism for this is extremely simple, basically assigning passwords in a "round robin" scheduling style. (eg: If the work is split up into three slices for three devices, each device will process every 3rd password) There is also the ability to assign multiple time slices to a single device in situations where one device may be multiple times faster than the others. + +So let's consider the example of two devices, PC1 and PC2. In this example, PC1 is twice as powerful as PC2, so will be assigned 2/3 of the work, while PC2 will be assigned 1/3. + +## Practical Limits +Working out how many potential passwords there are in a situation like this is quite straight forward. + +For example, if you have a list of 20 words and know the postition of 3 of them within the 12 word seed, then there will be: + +17 * 16 * 15 * 14 * 13 * 12 * 11 * 10 * 9 = 8,821,612,800 possible seeds (Very do-able in a few days depending on your CPU power available) + +## Running BTCRecover + +Both PCs will need BTCRecover installed and both use the identical command, with the exception of the --worker command which is different for each PC. (We can just ignore the fact that this tokenlist only produces a very small set to test) + +The devices don't need to be running the same operating system, nor do they need to communicate with each other in any way... + +### Command on PC 1 +So we want to assign work slices 1 and 2 to PC1 + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --tokenlist ./docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/tokenlist.txt + --no-eta --worker 1,2/3 +``` + +### Command on PC 2 +And work slice 3 to PC2 + +**Command** +``` +python seedrecover.py --no-dupchecks --mnemonic-length 12 --language EN --dsw --wallet-type BIP39 --addr-limit 1 --addrs 17GR7xWtWrfYm6y3xoZy8cXioVqBbSYcpU --tokenlist ./docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/tokenlist.txt + --no-eta --worker 3/3 +``` + +### The Outcome... + In this example, the correct seed phrase is found by PC2. Since there is no communication between the devices, PC1 will continue searching until it has exhausted the search space. + + This also highlights that you need to pay careful attention to what you are doing when using this command. If you accidentally forget to assign a work share to a PC, or assign the same shares twice, BTCrecover will have no way to know this has occured and no result will be found if the correct password would have occured in the untested share. + +### Closing Thoughts + Using the --no-eta command can be useful in situations like this as it will show you the current speed at which a device is checking passwords. It will also start immediately. (Which can be good if you are paying for cloud server time) + + One way that it an be useful to use this command is if you have already started a password search on your PC and know how many passwords will need to be checked, it can speed things up to run with --no-eta and just manually work out how long things will take. + + If you have started doing a seed recovery on a single device and want to move to a multi-device setup, but don't want to re-check the passwords already tested on a single device, you can take note of how many passwords the single device had tested and just use the --skip command when restarting the test across multiple devices. + + + diff --git a/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/tokenlist.txt b/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/tokenlist.txt new file mode 100644 index 000000000..3e028f788 --- /dev/null +++ b/docs/Usage_Examples/2020-05-23_multi_device_descrambling_12_word_seed_with_extras/tokenlist.txt @@ -0,0 +1,17 @@ ++ ^1^ocean ++ ^2^hidden ++ ^3^kidney +famous ++ ^5^rich ++ ^6^season ++ ^7^gloom +husband +spring ++ ^10^convince ++ ^11^attitude ++ ^12^boy +glove +section +chunk +brick +sauce \ No newline at end of file diff --git a/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/Example_Multi-GPU_with_vastai.md b/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/Example_Multi-GPU_with_vastai.md new file mode 100644 index 000000000..39eb86128 --- /dev/null +++ b/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/Example_Multi-GPU_with_vastai.md @@ -0,0 +1,288 @@ +# Multi-GPU Password Recovery using Vast.ai +[YouTube Video can be found here: https://youtu.be/8Zqc-2Te3zQ](https://youtu.be/8Zqc-2Te3zQ) + +## Background +Vast.ai is a service where users around the world can rent out their spare GPU power. It is often cheaper and faster than using rented services from commercial providers like Google or Amazon... This service is mostly used for training AIs but is also useful for running OpenCL processes like BTCRecover and Hashcat. + +This is great in that if you don't have a powerful GPU, it makes it possible to cheaply attempt password recovery in a matter of hours that might take weeks if run on your own hardware, particularly if you only have a CPU and not a powerful GPU... (Or are otherwise unable to run a process like this that might take several days) It is particularly useful with BTCRecover when run using a wallet extract, in that this allows you to securely recover the password without the possibility that the rented server owner can steal your funds. + +It is also significantly cheaper than renting CPU time with a commercial service like Linode, particularly if you can rent multiple powerful servers, complete the search quickly, while still paying a similar price/hashrate. (Eg: A system that is 10x as powerful is often about 10x the price, all billed in 1s increments, so easy to only use what you need) + +**This process is not secure for seed recovery, BIP39 seed recovery or where you upload the wallet file to the cloud server... At this time, BIP39 seed recovery also bottleknecks badly on CPU, so will see little benefit from this approach...** + +## Performance + +Blockchain.com Bechmark + +``` +python3 btcrecover.py --wallet ./btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json --performance --enable-opencl +``` + +Bitcoin Core Benchmark + +``` +python3 btcrecover.py --wallet ./btcrecover/test/test-wallets/bitcoincore-wallet.dat --performance --enable-gpu --global-ws 4096 --local-ws 256 +``` + +For the sake of comparison, I have run this benchmark on the following configurations. + +| GPU(s) | Blockchain.com Performance (OpenCL) (kP/s) | Bitcoin Core (JTR) (kP/s) | Lowest Price ($/h) | +|---|---|---|---| +| i7 8750H (Reference-Local CPU) | 1 | 0.07 | +| i5 4430 (Reference-Local CPU) | 0.7 | 0.05 | +| 1660ti (Reference-Local GPU) | 10 | 6.75 | +| RX570 (Reference-Local GPU) | 2.1 | 1.29 | +| 1x 1070 | 6.5 | 3.82 | 0.09 | +| 2x 1070 | 12.5 | 6.45 | 0.296 | +| 10x 1070 | 41 | | | +| 1070ti | 6 | 3.2 | 0.127 | +| 10x 1080 | 46 | 13.5 | 1.64 | +| 1080ti | 6 | 3.5 | 0.1 | 0.1 | +| 2x 1080ti | 10.1 | 6.1 | 0.3 | +| 6x 1080ti | 28 | 9.75 | 1.02 | +| 2x 2070 | 16.6 | 12 | 0.48 | +| 10x 2070 Super | 63 | 16 | 1.6 | +| 2080ti | 9.4 | 6.4 | 0.2 | 0.2 | +| 2x 2080ti | 19.5 | 10.8 | 0.4 | +| 4x 2080ti | 37 | 16 | 1 | + +_It's worth looking at the price/hour for different machines based on your time preference... Often a 2x 2080 machine will cost twice as much, to rent, but only require half as much rental time... Just be aware that the JTR kernel doesn't scale as well once you get past ~2x GPUs..._ + +## What you will need +* Secure Shell (SSH) software client like Putty (on Windows) +* (Optional) A Secure File Transfer tools like WinSCP (on Windows) - You will need this if you use a vast.ai instance to create your extract script, or if you try to copy autosave files. +* A Credit Card (To pay for Vast.ai time) + +## Vast.ai Instance Settings + +**OS Image** + +Select the option for a custom image and enter the following. +``` +dceoy/hashcat +``` + +**On-start script** +``` +apt update +apt install python3 python3-pip python3-dev python3-pyopencl nano mc git python3-bsddb3 -y +apt install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev libsecp256k1-dev -y +git clone https://github.com/3rdIteration/btcrecover.git +pip3 install -r ~/btcrecover/requirements-full.txt +update-locale LANG=C.UTF-8 +echo "set -g terminal-overrides \"xterm*:kLFT5=\eOD:kRIT5=\eOC:kUP5=\eOA:kDN5=\eOB:smkx@:rmkx@\"" > ~/.tmux.conf +``` + +_This will download all updates, clone BTCRecover in to the home folder, install all dependancies and get the environment ready to use BTCRecover. It normally finishes running within a few minutes of the vast.ai host reaching the "Successfully Loaded" status..._ + +**Disk Space to Allocate** + +1GB is fine unless you are trying to use an AddressDB file... (In which case you will need to allocate sufficient space for the uncompressed AddressDB file + 1GB) + +## Common Issues +Requirements not correctly installed... + +### Incorrect Locale +Depending on whether you connected before the onStart script had finished running you might get an error like: + + OSError: Locale is currently set to XXXXX. This library needs the locale set to UTF-8 to function properly. + +If you get this error, you basically just need to type in `exit` in the command prompt. This will terminate your SSH session. Once you reconnect via Putty, the locale issue will be resolved. (If not, wait a few minutes, type `exit` and reconnect) + +### Connection Refused +Double check the connection IP and port, if you still can't connect, click "destroy" and try a different host... + +### OpenCL Program Build Failed + +Somewhere in your terminal output you will see: +``` +`clBuildProgram failed: BUILD_PROGRAM_FAILURE - clBuildProgram failed: BUILD_PROGRAM_FAILURE - clBuildProgram failed: BUILD_PROGRAM_FAILURE + +Build on : + +=========================================================================== +Build on : + + +(options: -I /usr/local/lib/python3.6/dist-packages/pyopencl/cl) +(source saved as /tmp/tmpqqq0xe7b.cl)` +``` + +_This is an issue on this particular vast.ai host you have rented, destroy it and try a different one..._ + +### No BTCRecover folder... + +type +``` +cat onstart.log +``` +to see how the on-start script is going... It might be stuck, may have hit an error, but simply giving it some more time may help... + +In this situation, you can either manually run the start commands one at a time, but if they have failed, there are probably other issues with the host... If in doubt, just destroy the server and rent a different one... + +### Anything else... +Destroy the vast.ai host you have rented and rent another one... It's possible to get two faulty servers in a row, so try a new server at least 3 times... + +## Step-By Step Process +1) Create a wallet extract for your wallet. (Optionally: Start the process on your PC through to the password counting step, then copy the autosave file to the Vast.ai host) + +2) Create your token file and work out what sort of CPU/GPU power you will need + +3) Create an account on https://vast.ai/ + +4) Select a server, add the server settings above and create it + +5) Connect to the server via SCP and copy required files (Possibly including autosave files) + +6) Connect and check that everything works... (Running one of the benchmark commands above is a good bet) + +7) Run your BTCRecover command. + +8) Destroy the server once complete. + +**Make sure that you allocate at least one thread per GPU...** + +## Usage example (Bitcoin Core wallet) 10x GPUs spread over 5 vast.ai instances... ~1000x faster than i7 CPU... + +### 1) Create wallet extract on your home PC (or another vast.ai instance) + +Creating Bitcoin Core wallet extracts requires the bsddb3 module. The above startup script installs the require package automatically on each vast.ai instance you create, on Windows, you can download and install a prebuilt module by [following the instructions here.](../../Extract_Scripts.md) + +Once bsddb3 is installed, you can use the command: +``` +python extract-bitcoincore-mkey.py ../btcrecover/test/test-wallets/bitcoincore-wallet.dat +``` + +This will produce + +``` +Partial Bitcoin Core encrypted master key, salt, iter_count, and crc in base64: +YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR +``` + +### 2) Create your tokenlist file and work out if a server is required +**The tokenlist used in this example is tokenListTest.txt** + +``` linenums="1" +{% include "./tokenListTest.txt" %} +``` + +We will run this command locally to work out the number of possibilities, fix any errors in or Tokenlist and see if it's worth running on a cloud system... (Though you can just do all this on a vast.ai instance if you like) + +``` +python btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt +``` + +The tokenlist in this example is very simple, has 11 rows with one token per row. It will test every possible combination of these tokens to find the password, testing about 50 million possible passwords. (No anchors of any kind in this example) This tokenlist is deliberately structured to find the correct password right towards the end of the run... + +If run on my CPU, it would take 15 hours, on a 1660ti, ~1.5 hours and 10 minutes on 10x 2080ti... (5 2x2080ti vast.ai instances) + +### Steps 3-6 covered in YouTube video + +### 7) Run BTCRecover command + +Copy the tokenlist to the server using using WinSCP, for the sake of simplicity and easy or reproducibility, lets say it is placed in the ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/ folder + +Once connected to the server, change to the btcrecover folder + +``` +cd btcrecover +``` + +So the commands will be: +Server 1: +``` +python3 btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt --dsw --no-eta --no-dupchecks --enable-gpu --global-ws 4096 --local-ws 256 --autosave autosave.file --worker 1/5 +``` + +Server 2: +``` +python3 btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt --dsw --no-eta --no-dupchecks --enable-gpu --global-ws 4096 --local-ws 256 --autosave autosave.file --worker 2/5 +``` + +Server 3: +``` +python3 btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt --dsw --no-eta --no-dupchecks --enable-gpu --global-ws 4096 --local-ws 256 --autosave autosave.file --worker 3/5 +``` + +Server 4: +``` +python3 btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt --dsw --no-eta --no-dupchecks --enable-gpu --global-ws 4096 --local-ws 256 --autosave autosave.file --worker 4/5 +``` + +Server 5: +``` +python3 btcrecover.py --data-extract-string YmM65iRhIMReOQ2qaldHbn++T1fYP3nXX5tMHbaA/lqEbLhFk6/1Y5F5x0QJAQBI/maR --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt --dsw --no-eta --no-dupchecks --enable-gpu --global-ws 4096 --local-ws 256 --autosave autosave.file --worker 5/5 +``` +_Same command on each server, with the exception of the worker argument_ + +Autosave files will also need to be copied to/from the instance via something like WinSCP, as they aren't just plan text. + +### 8) Once you have your password, you can destroy all the instances. (Alternatively, you can just stop it, but just be aware that re-starting it might take some time depending on whether the instance is available) + + +## Usage example (Blockchain.com wallet) 2x 10 GPU Instances ~100x faster than i7 CPU + +### 1) Create wallet extract on your home PC (or another vast.ai instance) + +``` +python extract-blockchain-main-data.py ../btcrecover/test/test-wallets/blockchain-v3.0-MAY2020-wallet.aes.json +``` + +This will produce + +``` +Blockchain first 16 encrypted bytes, iv, and iter_count in base64: +Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q== +``` + +### 2) Create your tokenlist file and work out if a server is required +We will run this command locally to work out the number of possibilities, fix any errors in or Tokenlist and see if it's worth running on a cloud system... (Though you can just do all this on a vast.ai instance if you like) + +``` +python btcrecover.py --data-extract-string Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q== --tokenlist ./docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt +``` + +The tokenlist in this example is very simple, has 11 rows with one token per row. It will test every possible combination of these tokens to find the password, testing about 50 million possible passwords. (No anchors of any kind in this example) This tokenlist is deliberately structured to find the correct password right towards the end of the run... + +If run on my CPU, it would take 15 hours, on a 1660ti, ~1.5 hours and 10 minutes on 20x 1080s... (2x 10x1080 vast.ai instances) + +Once you are happy with your tokenlist and BTCRecover command, you can run it on a server. + +### Steps 3-6 covered in YouTube video + + +### 7) Run BTCRecover command + +In this example, we want to use at 20 GPUs (for the sake of illustration), so need to have at least 10 threads per server (2 threads per GPU is ideal) and use the worker command to spread the load. If you want to save money and try and use "interruptable" instances, or make sure that you don't lose your progress if your run out of credit and the instance pauses you can use autosave files via the autosave parameter. + +Once connected to the server, change to the btcrecover folder + +``` +cd btcrecover +``` +We will also just copy/paste the token file using Nano on the vast.ai instance and something like notepad on our home PC. (As opposed to using WinSCP like in the previous demo) + +``` +nano tokenlist.txt +``` + +(You could also copy the tokenlist file directly using something like WinSCP) + +So the commands will be: +Server 1: +``` +python3 btcrecover.py --data-extract-string Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q== --tokenlist tokenlist.txt --dsw --no-eta --no-dupchecks --enable-opencl --threads 20 --autosave autosave.file --worker 1/2 +``` + +Server 2: +``` +python3 btcrecover.py --data-extract-string Yms6A6G5G+a+Q2Sm8GwZcojLJOJFk2tMKKhzmgjn28BZuE6IEwAA2s7F2Q== --tokenlist tokenlist.txt --dsw --no-eta --no-dupchecks --enable-opencl --threads 20 --autosave autosave.file --worker 2/2 +``` + +_Same command on each server, with the exception of the worker argument_ + +Autosave files will also need to be copied to/from the instance via something like WinSCP, as they aren't just plan text. + +### 8) Once you have your password, you can destroy all the instances. (Alternatively, you can just stop it, but just be aware that re-starting it might take some time depending on whether the instance is available) + diff --git a/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt b/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt new file mode 100644 index 000000000..0dea70451 --- /dev/null +++ b/docs/Usage_Examples/2020-10-06_Multi-GPU_with_vastai/tokenListTest.txt @@ -0,0 +1,11 @@ +b +t +c +r +- +test +- +pa +ss +wo +rd \ No newline at end of file diff --git a/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/example_seed_tokenlist_tokenblocks.md b/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/example_seed_tokenlist_tokenblocks.md new file mode 100644 index 000000000..3628018d2 --- /dev/null +++ b/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/example_seed_tokenlist_tokenblocks.md @@ -0,0 +1,31 @@ +# Grouping words together in tokenlist based seed recoveries + +## Background +Sometimes there are recoveries where someone may have either written their seed in such a way that they are unsure of the order, or split the backup in to multiple parts. Either way, this can mean that are words grouped together in such a way that their relative ordering is known, but the position of these words within the larger seed is not. + +In situations like this, you can use a single comma character `,` (No space, just a single comma) for words within a tokenlist to indicate that words must be used together, in the provided order. This is similar to the "relative anchor" feature, but is both far more efficient and also allows for multiple word groupings. + +Grouped word tokens can also be used in conjunction with other types of anchors, positional, etc. + +Using these tokens also means that the number of tokens will no longer equal the mnemonic length (as it typically does for descrambling with single word tokens) so you can also make use of the `--min-tokens` and `--max-tokens` arguments to specify the minimum number of tokens that should be tried for any given seed guess. (A comma separated group of words counts as a single token) + +Like with seed descrambling, the ceiliing for this sort of recovery is based off the number of tokens, generally (without positional anchors) there will be max-tokens! (max-tokens factorial) possible seeds to check. + +## Example + +The following example uses the following tokenlist: +``` linenums="1" +{% include "./tokengroups_tokenlist.txt" %} +``` + +You can see in this tokenlist that there are a few blocks of tokens that we are sure are in the correct order, (Including a positional anchor for one of the groups of seed words) as well as a few extra/single words. + +And is run with the following command (Will find a result in a few seconds) +``` +python seedrecover.py --tokenlist ./docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/tokengroups_tokenlist.txt --mnemonic-length 24 --language en --wallet-type bip39 --addrs 1PTcESpqrmWePYB5h18Ni11QTKNfMkdSYJ --dsw --addr-limit 10 --max-tokens 9 --min-tokens 8 +``` + +The correct seed is: +`basic dawn renew punch arch situate resist indicate call lens group empty brother damp this verify eternal injury arrest question armor hole lounge practice` + +And as you can see, this is made up of some of the token-groups included in the tokenlist file above. \ No newline at end of file diff --git a/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/tokengroups_tokenlist.txt b/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/tokengroups_tokenlist.txt new file mode 100644 index 000000000..ddb707044 --- /dev/null +++ b/docs/Usage_Examples/2022-04-02_Seed_Tokenlist_TokenBlocks/tokengroups_tokenlist.txt @@ -0,0 +1,12 @@ +^basic,dawn,renew,punch,arch,situate +arrest,question,armor +hole,lounge,practice +resist +zoo,zoo,zoo +indicate,call +lens,group,empty +zoo +husband +verify,eternal,injury +battle,satoshi +brother,damp,this \ No newline at end of file diff --git a/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/example_checking_swapped_seed_words.md b/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/example_checking_swapped_seed_words.md new file mode 100644 index 000000000..3105a02f3 --- /dev/null +++ b/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/example_checking_swapped_seed_words.md @@ -0,0 +1,54 @@ +# Checking for Swapped Seed Words + +## Background +Sometimes someone has swapped words within their mnemonic in an attempt to add a layer of security to their seed backup, but has since forgotten exactly which words they swapped. + +seedrecover.py has the ability to check mnemonics in the situation where a number of words have been swapped within the mnemonic. + +This is done using the `--transform-wordswaps` argument and specifying how many swaps you want to check for each generated seed. + +In terms of working out the practical limits to this type of recovery, for a given number of swaps N in a mnemonic of length L, there will generally be (L/2 * (L-1))^N additional possible seeds. (Not accounting for skipped duplicates, etc) + +You can see what this works out to for 12 and 24 word seeds when swapping up to three words below. + +| Mnemonic Length | Possibilities with 1 Swap | Possibilities with 2 Swaps | Possibilities with 3 Swaps | +|---|---|---|---| +| 12 | 67 | 4423 | 291919 | +| 24 | 277 | 76453 | 21101029 | + +As you can see, recoveries for up to four swaps are quite straightforward and the complexity grows exponentially beyond this. + +## Standard Recovery Example +You can simply use the standard seedrecover.py commands in conjunction with this argument, in both situations where you have all the words correct as well as situations where you think there may be additional erros within the mnemonic. + +In the case that you don't believe that there are any additional errors, you can also set `--typos 0` + +For example, the command below can be used to recover a mnemonic where there were two pairs of words that have been swapped, but we believe that we have all the correct words as well as the first address from the wallet. +``` +python seedrecover.py --mnemonic "harvest enrich pave before correct dish busy one bulk chat mean biology" --typos 0 --dsw --addr-limit 1 --addrs 1E7LSo4WS8sY75tdZLvohZJTqm3oYGWXvC --wallet-type bip39 --transform-wordswaps 2 +``` + +You will get the correct seed within a few seconds... + +Correct Seed: harvest bulk pave before enrich dish busy one correct chat mean biology + +## Seedlist Recovery Example +You can also use this feature when using seedrecover.py with either a seedlist or tokenlist. + +Let's say that you had a number of seeds where you knew that you have swapped pairs of words up to three times, have forgotten which were swapped and also forgotten which seed is associated with the wallet you are trying to find. + +You might create a seedlist like the one below: + +The following example uses the following seedlist: +``` linenums="1" +{% include "./seed_swaps_example_seedlist.txt" %} +``` + +You would then run seedrecover using this seedlist using the command below: +``` +python seedrecover.py --seedlist ./docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/seed_swaps_example_seedlist.txt --dsw --addr-limit 1 --addrs 175s5ftTjfoPzyNLM2CnNLBFeE1zXHuApU --wallet-type bip39 --transform-wordswaps 3 --mnemonic-length 12 --language en +``` + +You will get the correct result within about a minute. + +Correct Seed: oil debate panther dismiss width flame grocery style vault belt brisk food diff --git a/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/seed_swaps_example_seedlist.txt b/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/seed_swaps_example_seedlist.txt new file mode 100644 index 000000000..fe9b31fa9 --- /dev/null +++ b/docs/Usage_Examples/2023-04-29_Checking_Swapped_Seed_Words/seed_swaps_example_seedlist.txt @@ -0,0 +1,4 @@ +vote skirt walk prize addict conduct pact base thumb luxury solar layer +ceiling game toast pizza spray swap side embrace album leg club arrange +oil dismiss panther debate width flame vault style grocery belt food brisk +excess body garage unknown breeze regret rude perfect buddy muscle virtual region \ No newline at end of file diff --git a/docs/Usage_Examples/UsageExamples.md b/docs/Usage_Examples/UsageExamples.md new file mode 100644 index 000000000..d7ed68682 --- /dev/null +++ b/docs/Usage_Examples/UsageExamples.md @@ -0,0 +1,33 @@ +# Usage Examples + +This page provides links to a number of examples tht demonstrate how the setup and syntax to use BTCRecover in a number of different recovery scenarios. (Generally with a corresponding YouTube video) + +The commands given can be pretty much copy/pasted as-is to allow you to recreate the examples given in the YouTube video. + +**Note:** Some changes may be required based on your platform, often simple things like using "python" vs "python3". + +**Python Version:** Many of these videos were created using different forks of BTCRecover. The resources and commands here may differ slightly, but are designed to work with this fork under Python3. + +## Seed Recovery + +[Basic Seed Recoveries](basic_seed_recoveries.md) (Demonstrates the basic commands that you would use for a recovery with various types of wallets) + +[Descrambling 12 word BIP39 Seeds](Example_Descrambling_a_12_word_seed.md) (Demonstrates Using a TokenList for unscramblng seeds for Electrum Legacy, Electrum Segwit, BIP39 Bitcoin and BIP39 Ethereum) + +[Multi-Device Descrambling 12 word Seed with Extra words](Example_multi_device_descrambling_12_word_seed_with_extras.md) (Demonstrates "Required" Anchors, "Positional" Anchors and Spreading Work Accross Multiple Devices) + +[Grouping words together in tokenlist based seed recoveries](example_seed_tokenlist_tokenblocks.md) (Demonstrates descrambling a 24 word seed where there several groups of words known to follow one another, but the position of these groups within the seed is unknown) + +[Checking Swapped Words](example_checking_swapped_seed_words.md) (Demonstrates seed recovery where there have been two word swaps applied to words within the mnemonic) + +## Password Recovery + +[Basic Password Recoveries](basic_password_recoveries.md) (Demonstrates the basic commands that you would use for a recovery with various types of wallets) + +[Recovering Blockchain.com Wallet Passwords](Example_Recovering_Blockchain_Wallet_Passwords.md) (Demonstrates recovering Blockchain.com wallet, using an Extract script, tokenLists, passwordlists and different types or typos) + +[Multi-GPU Accelerated Recovery using Vast.ai](Example_Multi-GPU_with_vastai.md) (Demonstrates use of the vast.ai service with wallet extracts and also arguments required for mutli-gpu accelerated recovery of Blockchain.com and Bitcoin Core Wallets) + +## AddressDB Creation + + diff --git a/docs/Usage_Examples/basic_password_recoveries.md b/docs/Usage_Examples/basic_password_recoveries.md new file mode 100644 index 000000000..30bb44069 --- /dev/null +++ b/docs/Usage_Examples/basic_password_recoveries.md @@ -0,0 +1,421 @@ +# Basic Password/Passphrase Recoveries + +None of these examples are concerned with arguments that you would use for different types of typos, tokenlists, etc. + +The idea is that, if you are running this tool on Windows, you can directly copy/paste any of these examples. (They all use the same seeds and addresses that are in the automatic tests) + +They will all find a result almost straight away. + +**Basic Passwordlist used in basic examples below** +``` linenums="1" +{% include "./common_passwordlist.txt" %} +``` + +## BIP38 Encrypted Paper Wallet Recovery. +**Notes** +BIP38 wallets are encrypted via sCrypt, so will be very slow to brute-force. GPU acceleration for these wallets is available, but doesn't offer much of a performance boost unless you have multiple GPUs or a particularly powerful GPU relative to your CPU... (Or some kind of dedicated OpenCL accelerator) + +**Supported wallets** + +* [bitaddress.org](https://www.bitaddress.org/) +* [liteaddress.org](https://liteaddress.org/) +* [paper.dash.org](https://paper.dash.org/) + +And just about any other BIP38 encrypted private keys. + +**Commands** + +For Bitcoin (No coin needs to be specified, Bitcoin is checked by default) +``` +python btcrecover.py --bip38-enc-privkey 6PnM7h9sBC9EMZxLVsKzpafvBN8zjKp8MZj6h9mfvYEQRMkKBTPTyWZHHx --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+For Litecoin +``` +python btcrecover.py --bip38-enc-privkey 6PfVHSTbgRNDaSwddBNgx2vMhMuNdiwRWjFgMGcJPb6J2pCG32SuL3vo6q --bip38-currency litecoin --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+For Dash +``` +python btcrecover.py --bip38-enc-privkey 6PnZC9Snn1DHyvfEq9UKUmZwonqpfaWav6vRiSVNXXLUEDAuikZTxBUTEA --bip38-currency dash --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +## BIP39 Passphrase Protected Wallets & Electrum "Extra Words" +**Notes** +The language used to refer to a BIP39 passphrase can vary between vendors. Sometimes it is talked about as a "25th word", other times a "plausible deniability passphrase" or sometimes just as "passphrase". Just note that this is different from your wallet password or PIN. + +The most common symptom of you having an error in your BIP39 passphrase is that your seed+passphrase will produce a set of completely empty accounts, with no balance or transaction history. (Every BIP39 passphrase is valid, so you will not get any kind of error message) + +While BIP39 seed recovery can benefit from GPU acceleration, this is currently not the case for recovering a BIP39 passphrase. + +All of the example commands below have the address generation limit set to 10, so the address they are searching for needs to be within the first 10 addresses in the wallet. + +**Supported wallets** + +* Most hardware wallets that support BIP39/44 + * Trezor (One and T) + * Ledger Nano (S and X) + * Keepkey + * Coldcard + * Bitbox02 + * Cobo Vault Pro +* Most Software Wallets that support BIP39/44 + * Wasabi Wallet (Wasabi refers to this as your wallet password) + * Samourai Wallet + * Coinomi + * Mycelium + * Zillet (Refers to BIP39 passphrase as a "password based" wallet type) + * Electrum + * Exodus + +**Commands** + +### Basic Bitcoin Command + +No need to specify `--wallet-type` This will support all Bitcoin address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --bip39 --addrs 1AmugMgC6pBbJGYuYmuRrEpQVB9BBMvCCn --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "certain come keen collect slab gauge photo inside mechanic deny leader drop" +``` +
+ +### Basic Bitcoin Electrum Wallet Command. + +These aren't BIP39, so need to use `--wallet-type electrum2` This will support both Legacy and Segwit Electrum wallets without any additional parameters. (It will also work with most Electrum Altcoin clones) +``` +python btcrecover.py --wallet-type electrum2 --addrs bc1q6n3u9aar3vgydfr6q23fzcfadh4zlp2ns2ljp6 --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "quote voice evidence aspect warfare hire system black rate wing ask rug" +``` +
+ +### Basic Ethereum Command + +Need to specify the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --wallet-type ethereum --addrs 0x4daE22510CE2fE1BC81B97b31350Faf07c0A80D2 --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` + +
+ +### Ethereum Validator passphrase recovery. + +Just like with seed recovery, this works basically the same as a standard BIP39 passphrase and uses the validator pubkey (Aka Signing Key) as the address + +``` +python btcrecover.py --mnemonic "spatial evolve range inform burst screen session kind clap goat force sort" --addrs b08ef0d03052945b6c4207e9b64a41e7773bd7b635e5140e8d38c290b11959bbcbe218850c5a478394654d094094d175 --wallet-type ethereumvalidator --addr-limit 1 --tokenlist ./btcrecover/test/test-listfiles/tokenListTe +st.txt +``` + +
+ +### Basic Zilliqa Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied)This will support all address types (Base16 and Bech32) without the need to add any additional parameters. + +Note: Zilliqa seed recovery can't be used as the basis for recovering a Ledger Nano seed/passphrase at this time. + +``` +python btcrecover.py --wallet-type zilliqa --addrs zil1dcsu2uz0yczmunyk90e8g9sr5400c892yeh8fp --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Bitcoin Cash Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will accept either Cashaddres or Legacy style addresses... This will also work for BCH forks like BSV... +``` +python btcrecover.py --wallet-type bch --addrs bitcoincash:qqv8669jcauslc88ty5v0p7xj6p6gpmlgv04ejjq97 --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Cardano + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) For Cardano recovers, [see the notes here as well.](bip39-accounts-and-altcoins.md) This will accept either base or stake addresses... (Byron-Era addresses are not supported)) + +``` +python btcrecover.py --wallet-type cardano --addrs addr1q90kk6lsmk3fdy54mqfr50hy025ymnmn5hhj8ztthcv3qlzh5aynphrad3d26hzxg7xzzf8hnmdpxwtwums4nmryj3jqk8kvak --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" +``` +
+ +### Basic Dash Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --wallet-type dash --addrs XuTTeMZjUJuZGotrtTPRCmHCaxnX44a2aP --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Dogecoin Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --wallet-type dogecoin --addrs DSTy3eptg18QWm6pCJGG4BvodSkj3KWvHx --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Vertcoin Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will support all address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --wallet-type vertcoin --addrs Vwodj33bXcT7K1uWbTqtk9UKymYSMeaXc3 --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Litecoin Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will support all address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --wallet-type litecoin --addrs LdxLVMdt49CXcrnQRVJFRs8Yftu9dE8xxP --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Monacoin Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will support all address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --wallet-type monacoin --addrs MHLW7WdRKE1XBkLFS6oaTJE1nPCkD6acUd --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic DigiByte Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will support all address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --wallet-type digibyte --addrs DNGbPa9QMbLgeVspu9jb6EEnXjJASMvA5r --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic GroestleCoin Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This will support all address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. + +Note: This needs the groestlecoin_hash module to be installed... +``` +python btcrecover.py --wallet-type groestlecoin --addrs FWzSMhK2TkotZodkApNxi4c6tvLUo7MBWk --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Ripple Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --wallet-type ripple --addrs rwv2s1wPjaCxmEFRm4j724yQ5Lh161mzwK --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "cable top mango offer mule air lounge refuse stove text cattle opera" +``` +
+ +### Basic Tron Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --wallet-type tron --addrs TGvJrj5D8qdzhcppg9RoLdfbEjDYCne8xc --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch" +``` +
+ +### Basic Polkadot(Substrate) Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) + +This command will search for the correct "secret derivation path" +``` +python btcrecover.py --wallet-type polkadotsubstrate --addrs 12uMBgecqfkHTYZE4GFRx847CwR7sfs2bTdPbPLpzeMDGFwC --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "toilet assume drama keen dust warrior stick quote palace imitate music disease" --substrate-path "//hard/soft" +``` +
+ +### Basic Stacks Command + +Need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) This example also has the address generation limit set to 10, so will check the first 10 "accounts" for a given seed+passphrase. +``` +python btcrecover.py --wallet-type stacks --addrs SP2KJB4F9C91R3N5XSNQE0Z3G34DNJWQYTP3PBJTH --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --mnemonic "ocean hidden kidney famous rich season gloom husband spring convince attitude boy" --addr-limit 10 +``` +
+ +## Brainwallets +**Notes** +Brainwallets are a very old (**and very unsafe**) type of wallet. Given this, most of them still produce addresses based on "uncompressed" + +**Supported wallets** + +* Sha256(Passphrase) Wallets + * [bitaddress.org](https://www.bitaddress.org/) + * [segwitaddress.org](https://segwitaddress.org/) + * [liteaddress.org](https://liteaddress.org/) + * [paper.dash.org](https://paper.dash.org/) +* Warpwallet Wallets + * [WarpWallet](https://keybase.io/warp/) + * [Memwallet](https://dvdbng.github.io/memwallet/) + * [Mindwallet](https://patcito.github.io/mindwallet/) + +### Sha256(Passphrase) Wallets +**Commands** + +Basic Bitcoin Command (Will check both compressed and uncompressed address types, even though in this example this is a compressed address) +``` +python btcrecover.py --brainwallet --addrs 1BBRWFHjFhEQc1iS6WTQCtPu2GtZvrRcwy --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +Bitcoin Wallet, but set to only check uncompressed addresses. (Only use this for VERY old wallets that you are sure aren't a compressed address, though also consider that uncompressed is the default... Only gives a small speed boost) + +``` +python btcrecover.py --brainwallet --addrs 1MHoPPuGJyunUB5LZQF5dXTrLboEdxTmUm --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +P2SH Bitcoin Wallet (Like the kind you would get of something like segwitaddress.org, as of 2021, these are all compressed type addresses, so can skip checking uncomrpessed ones...) +``` +python btcrecover.py --brainwallet --addrs 3C4dEdngg4wnmwDYSwiDLCweYawMGg8dVN --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +Bech32 Bitcoin Wallet. (From segwitaddress.org) +``` +python btcrecover.py --brainwallet --addrs bc1qth4w90jmh0a6ug6pwsuyuk045fmtwzreg03gvj --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +Litecoin Wallet (From liteaddress.org - These are all uncompressed with no option to use compressed) No extra arguments are needed for these types of wallets. +``` +python btcrecover.py --brainwallet --addrs LfWkecD6Pe9qiymVjYENuYXcYpAWjU3mXw --skip-compressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +Dash Wallet (From paper.dash.org) - No compression parameters specificed, so it will just check both +``` +python btcrecover.py --brainwallet --addrs XvyeDeZAGh8Nd7fvRHZJV49eAwNvfCubvB --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ + +Dash Wallet (From paper.dash.org - Or if you know you used a compressed one... (Though Uncompressed is the default) +``` +python btcrecover.py --brainwallet --addrs XksGLVwdDQSzkxK1xPmd4R5grcUFyB3ouY --skip-uncompressed --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+ +### Warpwallets +Note: At this time, only Bitcoin and Litecoin are supported... (Eth could be easily added) + +**Commands** + +Basic Bitcoin Wallet with "btcr-test-password" as the salt. (Warpwallet suggested using your email address) These wallets are all "uncompressed" type, but the performance gain for this is so small compared to how long the sCrypt operation takes, it isn't worth not checking both types... +``` +python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --addrs 1FThrDFjhSf8s1Aw2ed5U2sTrMz7HicZun --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +
+Basic Litecoin Wallet with "btcr-test-password" as the salt. (Like what memwallet or mindwallet produces, so you need to add the --crypto argment and specify litecoin) These wallets are all "uncompressed" type, but the performance gain for this is so small compared to how long the sCrypt operation takes, it isn't worth not checking both types... + +``` +python btcrecover.py --warpwallet --warpwallet-salt btcr-test-password --crypto litecoin --addrs LeBzGzZFxRUzzRAtm8EB2Dw74jRfQqUZeq --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## Block.io Wallets +You would first download the wallet file using the instructions in the extract scripts section of the documentation. + +You would then do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/block.io.request.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## Dogechain.info Wallets +You would first download the wallet file using the instructions in the extract scripts section of the documentation. You can also use an extract script to securely run dogechain.info wallets on rented hardware. [See here for more info about Extract Scripts...](Extract_Scripts.md#usage-for-dogechaininfo) + +You would then do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) + +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/dogechain.wallet.aes.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## Ethereum Keystores +Do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) + +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/utc-keystore-v3-scrypt-myetherwallet.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## imToken Wallet Keystores +Do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) + +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/imtoken-identity.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## Bitgo KeyCard User Key +Do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/bitgo_keycard_userkey.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` + +## Toastwallet Passphrase +Do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover) +``` +python btcrecover.py --wallet ./btcrecover/test/test-wallets/toastwallet.txt --passwordlist ./docs/Usage_Examples/common_passwordlist.txt +``` +_You can also dump the private keys from the wallet file by adding the `--dump-privkeys` argument_ + +## SLIP39 Passphrases +This uses much of the same syntax as recovering BIP39 passphrases. BTCRecover currently supports most of the coins that are supported by the Trezor T. + +The main difference is that instead of entering a single mnemonic, you can either enter the SLIP39 shares via the command line as below, or you will be promtpted for them. You need to have a quorum of SLIP39 shares to be able to do a passphrase recovery... + +Basic Bitcoin Command, so no need to specify `--wallet-type` This will support all Bitcoin address types (Legacy, Segwit or Native Segwit) without the need to add any additional parameters. +``` +python btcrecover.py --slip39 --addrs bc1q76szkxz4cta5p5s66muskvads0nhwe5m5w07pq --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --slip39-shares "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing" "hearing echo academic agency deliver join grant laden index depart deadline starting duration loud crystal bulge gasoline injury tofu together" +``` +
+ +Basic Ethereum Command, so need to specifcy the `--wallet-type` (But can leave off the `--bip39` argument, as it is implied) +``` +python btcrecover.py --slip39 --wallet-type ethereum --addrs 0x0Ef61684B1E671dcBee4D51646cA6247487Ef91a --addr-limit 10 --passwordlist ./docs/Usage_Examples/common_passwordlist.txt --slip39-shares "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing" "hearing echo academic agency deliver join grant laden index depart deadline starting duration loud crystal bulge gasoline injury tofu together" +``` +## Raw Private Keys## +BTCRecover an also be used to recover from situations where you have a damaged private key. + +This is handled in a similar way to a password recovery, so your private key guesses go in a tokenlist, using the %H wildcard to substitute hexidecimal characters or %B to substitute base58 characters. You can use either a tokenlist or a passwordlist, depending on your situation, as well as the standard typos. If you are using a tokenlist, you will just need to ensure that the private keys being produced match the length and characters required for a private key... + +If you know the address that the private key corresponds to, you can supply that, alternatively you can use an AddressDB. + +### Raw Eth Private Keys ### +You will also notice that the leading "0x" needs to be removed from the private key. + +**Example tokenlist** +``` linenums="1" +{% include "./eth_privkey_tokenlist.txt" %} +``` + +The tokenlist above is an example is a standard Eth private key (with the leading 0x removed) where there are three damanged parts. One single character (%H), one two-character (%2H) and one three-character (%3H) It will take about 20 mintes to run... + +``` +python btcrecover.py --rawprivatekey --addrs 0xB9644424F9E639D1D0F27C4897e696CC324948BB --wallet-type ethereum --tokenlist ./docs/Usage_Examples/eth_privkey_tokenlist.txt +``` + +## Raw Bitcoin Private Keys ## +Bitcoin private keys are supported in both Compressed and Uncompressed formats in Base58 and also as raw Hexidecimal keys. + +If you are using a tokenlist (as in the examples below) with multiple private keys, one per line, you will also want to specify the "--max-tokens 1" argument. + +**Example tokenlist** +``` linenums="1" +{% include "./eth_privkey_tokenlist.txt" %} +``` + +The command below will attempt a recovery for an old-style, uncompressed private key with one missing character, using a tokenlist containing three possible private keys. + +``` +python btcrecover.py --rawprivatekey --addrs 1EDrqbJMVwjQ2K5avN3627NcAXyWbkpGBL --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt +``` + +The command below will attempt a recovery for a more modern (compresseed, native-segwit address) private key with one missing character, using a tokenlist containing three possible private keys. + +``` +python btcrecover.py --rawprivatekey --addrs bc1qafy0ftpk5teeayjaqukyd244un8gxvdk8hl5j6 --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt +``` + +You can also do raw private key repair, even if you don't have a record of the corresponding address, through using an AddressDB. (Also works for Eth, BCH, etc...) + +``` +python btcrecover.py --rawprivatekey --addressdb ./btcrecover/test/test-addressdbs/addresses-BTC-Test.db --wallet-type bitcoin --max-tokens 1 --tokenlist ./docs/Usage_Examples/btc_privkey_tokenlist.txt +``` + +## XLM Wallets ## +Basic XLM Wallet with the passphrase 'btcr-test-password' +``` +python btcrecover.py --mnemonic "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse season" --addrs GBPYX2ELQ6YTAF7DXER7RCQJR2HXXFX6HUZKWEZD3B6RKOLDSJF7UGXK --addr-limit 2 --no-eta --wallet-type xlm --passwordlist ".\docs\Usage_Examples\common_passwordlist.txt" +``` \ No newline at end of file diff --git a/docs/Usage_Examples/basic_seed_recoveries.md b/docs/Usage_Examples/basic_seed_recoveries.md new file mode 100644 index 000000000..8caec9c2d --- /dev/null +++ b/docs/Usage_Examples/basic_seed_recoveries.md @@ -0,0 +1,175 @@ +# Basic Password/Passphrase Recoveries + +The idea is that, if you are running this tool on Windows, you can directly copy/paste any of these examples. (They all use the same seeds and addresses that are in the automatic tests) + +They will all find a result almost straight away. + +## Seed Based Recovery Notes +**Notes** +Seedrecover.py has been set up so that the defaults should get a result for the majorty of simple "invalid menmonic" or "invalid seed" type errors. (Eg: Where you have an intact seed backup that has a typo in it) + +It will search all account types for the supported cryptocurrencies, on all common derivation paths. + +It will automatically run through four search phases that should take a few hours at most. +1. Single typo +2. Two typos, including one where you might have a completely different BIP39 word +3. Three typos, including one where you might have a completely different BIP39 word +4. Two typos that could be completely different words. + +**Fully Supported wallets** (For supported cryptocurrencies) + +* Hardware Wallets + * Ledger Nano X and S + * Trezor One and T + * Keepkey + * Safepal + * Coldcard + * Bitbox02 + * Keystone + * Cobo Vault + * Ellipal + * CoolWallet S (You will need both convert the seed numbers to BIP39 seed words and to use the --force-p2sh argument for Bitcoin and Litecoin...) +* Software Wallets + * Electrum - Both V1 and V2 Seeds (This includes forks like Electrum-LTC, Electron-Cash, etc) + * Coinomi + * Wasabi + * Edge Wallet + * Mycelium + * Exodus + * Trust Wallet + * Metamask (Including clones like Binance Chain Wallet Extension) + +**Wallets with Compatibility Issues**(Due to not following derivation standards...) + +* Atomic Wallet. (Non-Standard derivation for ETH (And all ERC20 tokens), needs to be used with the `--checksinglexpubaddress`, XRP) +* Abra Wallet. (Non-Standard seed format, first word is Non-BIP39 "at", the last 12 are BIP39 (and checksum) but unable to reproduce derivation) + +## Examples +### Basic Bitoin Recoveries +**Note:** Most of the time you can just run seedrecover.py, even simply double click it and follow the graphical interface. + +With a Native Segwit Address - One missing word, address generation limit of 5. (So address needs to be in the first 5 addresses in that account) +``` +python seedrecover.py --wallet-type bip39 --addrs bc1qv87qf7prhjf2ld8vgm7l0mj59jggm6ae5jdkx2 --mnemonic "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect" --addr-limit 5 +``` + +With a P2SH Segwit Address - One missing word, address generation limit of 5. (So address needs to be in the first 5 addresses in that account) +``` +python seedrecover.py --wallet-type bip39 --addrs 3NiRFNztVLMZF21gx6eE1nL3Q57GMGuunG --mnemonic "element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect" --addr-limit 5 +``` + +### Basic LND aezeed Recoveries +One missing word, address generation limit of 5. (Leaves out the final word from the original 24 word seed.) If your seed uses a custom passphrase, add `--passphrase-arg "YOUR PASSPHRASE"` as well. +``` +python seedrecover.py --wallet-type aezeed --addrs 1Hp6UXuJjzt9eSBa9LhtW97KPb44bq4CAQ --mnemonic "absorb original enlist once climb erode kid thrive kitchen giant define tube orange leader harbor comfort olive fatal success suggest drink penalty chimney" --addr-limit 5 +``` + +If you no longer have an address or xpub to test against, omit `--addrs` to run in checksum-only mode. BTCRecover will still try candidate seeds that satisfy the aezeed checksum and will warn that the result must be manually verified to avoid false positives. + +### Basic Cardano Recoveries +For Cardano recovers, [see the notes here as well.](bip39-accounts-and-altcoins.md) You can use any Shelley-Era base or stake addresses. (Byron-Era not supported) + +Seed from a Ledger Nano, missing one word, using a standard base address. (Address generation limit isn't appliable in Cardano) +``` +python seedrecover.py --wallet-type cardano --addrs addr1qyr2c43g33hgwzyufdd6fztpvn5uq5lwc74j0kuqr7gdrq5dgrztddqtl8qhw93ay8r3g8kw67xs097u6gdspyfcrx5qfv739l --mnemonic "wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon basket fine culture boil render special enforce dish middle antique" +``` + +Seed from a Trezor, missing one word, using a standard base address. (Address generation limit isn't appliable in Cardano) +``` +python seedrecover.py --wallet-type cardano --addrs addr1q8k0u70k6sxkcl6x539k84ntldh32de47ac8tn4us9q7hufv7g4xxwuezu9q6xqnx7mr3ejhg0jdlczkyv3fs6p477fqxwz930 --mnemonic "ocean kidney famous rich season gloom husband spring convince attitude boy" +``` + +Seed from Yoroi, Adalite or Daedalus (Working as a software wallet), using a standard stake address +``` +python seedrecover.py --wallet-type cardano --addrs stake1uxztdzzm4ljw9a0qmgregc8efgg56p2h3kj75kc6vmhfj2cyg0jmy --mnemonic "cave table seven there limit fat decorate middle gold ten battle trigger luggage demand" +``` + +### Basic Ethereum Validator Recoveries +Recovery for Ethereum Validator seeds is the same as a standard seed recovery, but uses the validators public key (Also known as Signing Key) in the place of an address. + +Example command for a seed with one missing word +``` +python seedrecover.py --mnemonic "spatial evolve range inform burst screen session kind clap goat force x" --addrs 869241e2743379b6aa5e01138d410851fb2e2f3923ccc19ca78e8b14b01d861f67f95e2e6b3be71a11b251680b42dd81 --wallet-type ethereumvalidator --addr-limit 1 +``` + +### Basic Helium Recoveries +One missing word +``` +python seedrecover.py --wallet-type helium --addrs 13hP2Vb1XVcMYrVNdwUW4pF3ZDj8CnET92zzUHqYp7DxxzVASbB --mnemonic "arm hundred female steel describe tip physical weapon peace write advice" +``` + +### Basic Hedera Ed25519 Recoveries +Recover a Hedera account using its Solidity-style EVM address. Hedera aliases +are deterministic on the HIP-32 derivation path used by +`--wallet-type hederaed25519`, so the search can be limited to the first +account. + +``` +python seedrecover.py --wallet-type hederaed25519 --addrs 0x000000000000000000000000000000000098d10f --mnemonic "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup" --addr-limit 1 +``` + +The same mnemonic can be recovered with a Hedera account identifier instead of +the Solidity address: + +``` +python seedrecover.py --wallet-type hederaed25519 --addrs 0.0.10014991 --mnemonic "edit bean area disagree subway group reunion garage egg pave endless outdoor now egg alien victory metal staff ship surprise winter birth source cup" --addr-limit 1 +``` + +### Basic Polkadot(Substrate) Recoveries +One missing word, blank secret derivation path +``` +python seedrecover.py --wallet-type polkadotsubstrate --addrs 13SsWBQSN6Se72PCaMa6huPXEosRNUXN3316yAycS6rpy3tK --mnemonic "toilet assume drama keen dust warrior stick quote palace imitate music disease" +``` + +One missing word, secret derivation path of "//hard/soft///btcr-test-password" The soft/hard derivation path is passed to the program via the --substrate-path argument and the password is treated the same as a passphrase (Without the leading ///) +``` +python seedrecover.py --wallet-type polkadotsubstrate --addrs 12uMBgecqfkHTYZE4GFRx847CwR7sfs2bTdPbPLpzeMDGFwC --mnemonic "toilet assume drama keen dust warrior stick quote palace imitate music disease" --passphrase-arg btcr-test-password --substrate-path //hard/soft +``` + +### Basic Stacks Recoveries +One missing word, address generation limit of 10. (So will check the first 10 "accounts" for a given seed) +``` +python seedrecover.py --wallet-type stacks --addrs SP11KHP08F4KQ06MWESBY48VMXRBK5NB0FSCRP779 --mnemonic "hidden kidney famous rich season gloom husband spring convince attitude boy" --addr-limit 10 +``` + +### Basic Tron Recoveries +One missing word, address generation limit of 1. (So address needs to be in the first account) +``` +python seedrecover.py --wallet-type tron --addrs TLxkYzNpMCEz5KThVuZzoyjde1UfsJKof6 --mnemonic "have hint welcome skate cinnamon rabbit cable payment gift uncover column duck scissors wedding decorate under marine hurry scrub rapid change roast print arch" --addr-limit 1 +``` + +### Basic Elrond Recoveries +One missing word, address generation limit of 2. (So address needs to be in the first account) +``` +python seedrecover.py --wallet-type elrond --addrs erd16jn439kmwgqj9j0xjnwk2swg0p7j2jrnvpp4p7htc7wypnx27ttqe9l98m --mnemonic "agree process hard hello artefact govern obtain wedding become robust fish bar alcohol about speak unveil mind bike shift latin pole base ugly artefact" --addr-limit 2 +``` + +### Basic Electrum Legacy Recoveries +One wrong word, address generation limit of 2. (So address needs to be in the first account) +``` +python seedrecover.py --wallet-type electrum1 --addrs 1Pw1yjF5smzg6eWbE2LbFm7fr1zq7WUYc7 --mnemonic "milk hungry group sound Lift Connect throw rabbit gift leg new lady pie government swear flat dove imagination sometime prepare lot trembl alone bus" --addr-limit 2 +``` + +### Basic Stellar(XLM) Recoveries +One wrong word, address generation limit of two. +``` +python seedrecover.py --mnemonic "doctor giant eternal huge improve suit service poem logic dynamic crane summer exhibit describe later suit dignity ahead unknown fall syrup mirror nurse" --addrs GAV7E2PHIPDS3PM3BWN6DIHC623ONTZUDGXPJ7TT3EREYJRLTMENCK6Z --addr-limit 2 --no-eta --wallet-type xlm +``` + +## SLIP39 Share Recovery +`seedrecover.py` can also help fix a damaged SLIP39 share. Supply your best guess of the share and allow the tool to search for close matches. + +``` +python seedrecover.py --slip39 --mnemonic "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion losing" --typos 2 +``` + +The tool can also recover shares with missing words. For example, omitting the last +word of the same share still succeeds: + +``` +python seedrecover.py --slip39 --mnemonic "hearing echo academic acid deny bracelet playoff exact fancy various evidence standard adjust muscle parcel sled crucial amazing mansion" --big-typos 2 +``` + +If the share is longer than twenty-eight words, `seedrecover.py` assumes it is a +thirty-three word share. You can override this by supplying +`--share-length WORDS`. \ No newline at end of file diff --git a/docs/Usage_Examples/btc_privkey_tokenlist.txt b/docs/Usage_Examples/btc_privkey_tokenlist.txt new file mode 100644 index 000000000..aafcca99e --- /dev/null +++ b/docs/Usage_Examples/btc_privkey_tokenlist.txt @@ -0,0 +1,4 @@ +1ADF94484E9C820D69BC9770542B678DB677E7C354DC4BD27D7E9AC351698CB7 +KzTgU27AYQSojvpXaJHS5rm%BoB7k83Q3oCDGAeoD3Rz2BbLFoP19 +5JYsdUthE1KzGA%BXwfomeocw6vwzoTNXzcbJq9e7LcAyt1Svoo8 +KzPyZwq13F8tyXJ9si1ovGYo%BbtQADi6qD9XbDHRWtfWud7PriHV \ No newline at end of file diff --git a/docs/Usage_Examples/common_passwordlist.txt b/docs/Usage_Examples/common_passwordlist.txt new file mode 100644 index 000000000..2b1a8995d --- /dev/null +++ b/docs/Usage_Examples/common_passwordlist.txt @@ -0,0 +1,7 @@ +btcr-test-password +btcr-test-password:p2pkh +btcr-test-password:p2wpkh +btcr-test-password:p2wpkh-p2sh +btcrtestpassword2022 +Btcr-test-passw0rd +Btcr-test-passw0rd! \ No newline at end of file diff --git a/docs/Usage_Examples/eth_privkey_tokenlist.txt b/docs/Usage_Examples/eth_privkey_tokenlist.txt new file mode 100644 index 000000000..6cafff74a --- /dev/null +++ b/docs/Usage_Examples/eth_privkey_tokenlist.txt @@ -0,0 +1 @@ +5db77aa7aea5%2H7d6b4c64dab21%H972cf4763d4937d3e6e17f580436dcb10%3H \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 000000000..c4192631f --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/bip39-accounts-and-altcoins.md b/docs/bip39-accounts-and-altcoins.md new file mode 100644 index 000000000..b87eb88aa --- /dev/null +++ b/docs/bip39-accounts-and-altcoins.md @@ -0,0 +1,111 @@ +# Accounts and Derivation Paths +## Accounts Checked by Default +By default, seedrecover.py will check **the first account of each address type** using common derivation paths for BIP39 wallets. (BIP39 will default to Bitcoin if no coin is specified) You can select to have seedrecover check for altcoins if selected via the gui or specified via --wallet-type in the command line. + +To use the 2nd account, etc, you typically increment the last digit of the derivation path from /0 to /1. + +** The currencies and derivation paths supported by default (These files can all be found in the /derivationpath-lists/ folder of the repository)** +### Bitcoin +**BIP39 (BTC.txt)** +``` linenums="1" +{% include "../derivationpath-lists/BTC.txt" %} +``` + +**Electrum (Electrum.txt)** +``` linenums="1" +{% include "../derivationpath-lists/Electrum.txt" %} +``` + + +### Bitcoin Cash +**BCH.txt** +``` linenums="1" +{% include "../derivationpath-lists/BCH.txt" %} +``` + +### Cardano +Cardano handles this a bit differently as there are major differences between how different vendors implement Cardano account derivation. +As such, you will notice that derivation paths for Cardano have an additional prefix for the derivation type. +By default, BTCRecover will check all three "Shelly" derivation types and this can have a major performance impact. As such, if you know which wallet the seed came from, you should disable the other unnecessary derivation paths. + +**ADA.txt** +``` linenums="1" +{% include "../derivationpath-lists/ADA.txt" %} +``` + +### Dash +**Dash.txt** +``` linenums="1" +{% include "../derivationpath-lists/DASH.txt" %} +``` + + +### DigiByte +**DGB.txt** +``` linenums="1" +{% include "../derivationpath-lists/DGB.txt" %} +``` + + +### Ethereum +**ETH.txt** +``` linenums="1" +{% include "../derivationpath-lists/ETH.txt" %} +``` + + +### Groestlcoin +**GRS.txt** +``` linenums="1" +{% include "../derivationpath-lists/GRS.txt" %} +``` + + +### Litecoin +**LTC.txt** +``` linenums="1" +{% include "../derivationpath-lists/LTC.txt" %} +``` + + +### Monacoin +**MONA.txt** +``` linenums="1" +{% include "../derivationpath-lists/MONA.txt" %} +``` + + +### Ripple +**XRP.txt** +``` linenums="1" +{% include "../derivationpath-lists/XRP.txt" %} +``` + + +### Vertcoin +**VTC.txt** +``` linenums="1" +{% include "../derivationpath-lists/VTC.txt" %} +``` + + +### Zilliqa +**ZIL.txt** +``` linenums="1" +{% include "../derivationpath-lists/ZIL.txt" %} +``` + + +## Custom Derivation Paths +You can also edit the files in the common-derivation-pathslists folder to either add, or remove derivation paths that will be searched, as removing those that aren't required will speed things up a bit... + +You can also use the --bip32-path argument to specify a derivation path (or paths) via the commandline. + +## Altcoins, forks,clones or custom derivation paths +You can also try to specifiy a custom derivation path for altcoins/forks which share the same address format as any supported coins. (Though this doesn't necessarily mean the coin uses the same derivation function.) + +## Adding Unsupported Coins + +If you want the tool to support a crypto that isn't listed above, please test that it works and submit a PR which includes a unit test for that coin and also any required code to accept the address format. + +**_If you are trying to do a recovery for a coin that isn't listed above, feel free to contact me as it may be possible for you to sponsor the addition of that crypto as part of an assisted recovery fee._** diff --git a/docs/custom-wildcard-lists/custom_wildcards.md b/docs/custom-wildcard-lists/custom_wildcards.md new file mode 100644 index 000000000..a80ebbf5c --- /dev/null +++ b/docs/custom-wildcard-lists/custom_wildcards.md @@ -0,0 +1,45 @@ + +#Custom Wildcard Lists +BTCRecover allows you to pass an argument to a standard text file containing a list of items that can be used as an expanding wildcard. + +_If you attempt to use a custom wildcard without first having passed an argument to a list file, that wildcard will simply return a single blank string. (So will be ignored)_ + +If you make use of this feature, it is wise to check that your custom wildcards are being correctly loaded and behave as you expect through use of the --listpass argument. + +##Repeating String Wildcards +###Repetition Behaviour +The `%e` and `%f` wildcards are special in that when they appear multiple times in a given candidate password, they will have the same value. + +What this means is that they are useful for situations where you may have used some character (or string) which is repeated multiple times throughout a password. + +**Example 1:** + +You can see an example of this behaviour with the following command which loads a demo file for the `%e` wildcard + + python btcrecover.py --listpass --passwordlist ./docs/custom-wildcard-lists/demo_passwordlist.txt --wildcard-custom-list-e ./docs/custom-wildcard-lists/strings.txt --has-wildcards + +**Example 2** + +Likewise this works file for full strings, as you can see with a command like this: + + python btcrecover.py --listpass --passwordlist ./docs/custom-wildcard-lists/demo_passwordlist.txt --wildcard-custom-list-e ./docs/custom-wildcard-lists/string.txt --has-wildcards + +###Wildcards within Wildcards +These two types of wildcards are also **extra** special in that the strings used for them can also include _other_ expanding wildcards. + +**Example 3** + +You can see an example of this behaviour with the following command which loads a demo file for the `%f` wildcard + + python btcrecover.py --listpass --passwordlist ./docs/custom-wildcard-lists/demo_passwordlist.txt --wildcard-custom-list-f ./docs/custom-wildcard-lists/wildcard.txt --has-wildcards + +##Standard String Wildcards +The `%j` and `%k` wildcards behave in a simmilar manner to normal expanding wildcards. + +**Example 4** + +You can see an example of this behaviour with the following command which loads a demo file for the `%j` wildcard + + python btcrecover.py --listpass --passwordlist ./docs/custom-wildcard-lists/demo_passwordlist.txt --wildcard-custom-list-j ./docs/custom-wildcard-lists/strings.txt --has-wildcards + +If you compare the output of example 4 to that of example 2, you can see how these two different types of wildcards are handled. diff --git a/docs/custom-wildcard-lists/demo_passwordlist.txt b/docs/custom-wildcard-lists/demo_passwordlist.txt new file mode 100644 index 000000000..2d1349634 --- /dev/null +++ b/docs/custom-wildcard-lists/demo_passwordlist.txt @@ -0,0 +1,4 @@ +e:[%e-%e] +f:[%f-%f] +j:[%j-%j] +k:[%k-%k] \ No newline at end of file diff --git a/docs/custom-wildcard-lists/spacers.txt b/docs/custom-wildcard-lists/spacers.txt new file mode 100644 index 000000000..5f14be1d0 --- /dev/null +++ b/docs/custom-wildcard-lists/spacers.txt @@ -0,0 +1,7 @@ +/ +\ +- +| +%s +_ +- \ No newline at end of file diff --git a/docs/custom-wildcard-lists/strings.txt b/docs/custom-wildcard-lists/strings.txt new file mode 100644 index 000000000..344279d7d --- /dev/null +++ b/docs/custom-wildcard-lists/strings.txt @@ -0,0 +1,5 @@ +alpha +bravo +charlie +delta +echo \ No newline at end of file diff --git a/docs/custom-wildcard-lists/wildcard.txt b/docs/custom-wildcard-lists/wildcard.txt new file mode 100644 index 000000000..58b280d27 --- /dev/null +++ b/docs/custom-wildcard-lists/wildcard.txt @@ -0,0 +1 @@ +%d \ No newline at end of file diff --git a/docs/donate.md b/docs/donate.md new file mode 100644 index 000000000..6062446b4 --- /dev/null +++ b/docs/donate.md @@ -0,0 +1,26 @@ +# If this tool or other content on my YouTube channel was helpful, feel free to send a tip to: # +## Donate Bitcoin ## +![Donate Bitcoin](donate-btc-qr.png) + +BTC: 37N7B7sdHahCXTcMJgEnHz7YmiR4bEqCrS +## Donate Bitcoin Cash ## +![Donate Bitcoin Cash](donate-bch-qr.png) + +BCH: qpvjee5vwwsv78xc28kwgd3m9mnn5adargxd94kmrt +## Donate Litecoin ## +![Donate Litecoin](donate-ltc-qr.png) + +LTC: M966MQte7agAzdCZe5ssHo7g9VriwXgyqM +## Donate Ethereum ## +![Donate Ethereum](donate-eth-qr.png) + +ETH: 0x72343f2806428dbbc2C11a83A1844912184b4243 + +## Donate Bitcoin to Gurnec ## +This tool builds on the original work of Gurnec who created it and maintained it until late 2017. If you find *btcrecover* helpful, please consider a small donation to them too. (I will also be passing on a portion of any tips I recieve at the addys above to them too) + +![Donate Bitcoin](gurnec-donate-btc-qr.png) + +BTC: 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 + +**Thank You!** \ No newline at end of file diff --git a/docs/example_openssl.cnf b/docs/example_openssl.cnf new file mode 100644 index 000000000..981a09191 --- /dev/null +++ b/docs/example_openssl.cnf @@ -0,0 +1,394 @@ +# +# OpenSSL example configuration file. +# See doc/man5/config.pod for more info. +# +# This is mostly being used for generation of certificate requests, +# but may be used for auto loading of providers + +# Note that you can include other files from the main configuration +# file using the .include directive. +#.include filename + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . + + # Use this in order to automatically load providers. +openssl_conf = openssl_init + +# Comment out the next line to ignore configuration errors +config_diagnostics = 1 + +# Extra OBJECT IDENTIFIER info: +# oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] +# We can add new OIDs in here for use by 'ca', 'req' and 'ts'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +# Policies used by the TSA examples. +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +# For FIPS +# Optionally include a file that is generated by the OpenSSL fipsinstall +# application. This file contains configuration data required by the OpenSSL +# fips provider. It contains a named section e.g. [fips_sect] which is +# referenced from the [provider_sect] below. +# Refer to the OpenSSL security policy for more information. +# .include fipsmodule.cnf + +[openssl_init] +providers = provider_sect + +# List of providers to load +[provider_sect] +default = default_sect +legacy = legacy_sect + +# The fips section name should match the section name inside the +# included fipsmodule.cnf. +# fips = fips_sect + +# If no providers are activated explicitly, the default one is activated implicitly. +# See man 7 OSSL_PROVIDER-default for more details. +# +# If you add a section explicitly activating any other provider(s), you most +# probably need to explicitly activate the default provider, otherwise it +# becomes unavailable in openssl. As a consequence applications depending on +# OpenSSL may not work correctly which could lead to significant system +# problems including inability to remotely access the system. +[default_sect] +activate = 1 + +[legacy_sect] +activate = 1 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certs with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key + +x509_extensions = usr_cert # The extensions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extensions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = AU +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Some-State + +localityName = Locality Name (eg, city) + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Internet Widgits Pty Ltd + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +# This is required for TSA certificates. +# extendedKeyUsage = critical,timeStamping + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer + +basicConstraints = critical,CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + +#################################################################### +[ tsa ] + +default_tsa = tsa_config1 # the default TSA section + +[ tsa_config1 ] + +# These are used by the TSA reply generation only. +dir = ./demoCA # TSA root directory +serial = $dir/tsaserial # The current serial number (mandatory) +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate + # (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply + # (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) +signer_digest = sha256 # Signing digest to use. (Optional) +default_policy = tsa_policy1 # Policy if request did not specify it + # (optional) +other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) +digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory) +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? + # (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? + # (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? + # (optional, default: no) +ess_cert_id_alg = sha1 # algorithm to compute certificate + # identifier (optional, default: sha1) + +[insta] # CMP using Insta Demo CA +# Message transfer +server = pki.certificate.fi:8700 +# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080 +# tls_use = 0 +path = pkix/ + +# Server authentication +recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer +ignore_keyusage = 1 # potentially needed quirk +unprotected_errors = 1 # potentially needed quirk +extracertsout = insta.extracerts.pem + +# Client authentication +ref = 3078 # user identification +secret = pass:insta # can be used for both client and server side + +# Generic message options +cmd = ir # default operation, can be overridden on cmd line with, e.g., kur + +# Certificate enrollment +subject = "/CN=openssl-cmp-test" +newkey = insta.priv.pem +out_trusted = insta.ca.crt +certout = insta.cert.pem + +[pbm] # Password-based protection for Insta CA +# Server and client authentication +ref = $insta::ref # 3078 +secret = $insta::secret # pass:insta + +[signature] # Signature-based protection for Insta CA +# Server authentication +trusted = insta.ca.crt # does not include keyUsage digitalSignature + +# Client authentication +secret = # disable PBM +key = $insta::newkey # insta.priv.pem +cert = $insta::certout # insta.cert.pem + +[ir] +cmd = ir + +[cr] +cmd = cr + +[kur] +# Certificate update +cmd = kur +oldcert = $insta::certout # insta.cert.pem + +[rr] +# Certificate revocation +cmd = rr +oldcert = $insta::certout # insta.cert.pem diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..dd7a97cb2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,133 @@ +# BTCRecover +[![Last Push - All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Last%20Push%20-%20All%20Tests%20(Base%20Modules)/badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Last+Push+-+All+Tests+%28Base+Modules%29%22) [![Weekly - All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Weekly%20-%20All%20Tests%20(Base%20Modules)/badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Weekly+-+All+Tests+%28Base+Modules%29%22) [![Weekly Run All Tests (Base Modules)](https://github.com/3rdIteration/btcrecover/workflows/Weekly%20-%20All%20Tests%20(+Optional%20Modules)//badge.svg)](https://github.com/3rdIteration/btcrecover/actions?query=workflow%3A%22Weekly+-+All+Tests+%28%2BOptional+Modules%29%22) [![Documentation Status](https://readthedocs.org/projects/btcrecover/badge/?version=latest)](https://btcrecover.readthedocs.io/en/latest/?badge=latest) ![license](https://img.shields.io/badge/license-GPLv2-blue.svg) + +*BTCRecover* is an open source wallet password and seed recovery tool. + +For seed based recovery, this is primarily useful in situations where you have lost/forgotten parts of your mnemonic, or have made an error transcribing it. (So you are either seeing an empty wallet or gettign an error that your seed is invalid) + +For wallet password or passphrase recovery, it is primarily useful if you have a reasonable idea about what your password might be. + +## Getting Started +Your best bet is to follow the [Installing BTCRecover guide](INSTALL.md), then read the "Quick Start" for the recovery type you want. [(Or look at some usage examples)](UsageExamples.md) + +## Getting Support + +If you need help, [your best bet is to look at my BTCRecover playlist on YouTube](https://www.youtube.com/playlist?list=PL7rfJxwogDzmd1IanPrmlTg3ewAIq-BZJ) and ask a question in the comments section for any of video closest to your situation. + +If you have found a bug, please open an issue on Github here: [https://github.com/3rdIteration/btcrecover/issues](https://github.com/3rdIteration/btcrecover/issues) + +## Features ## +* Seed/Passphrase Recovery when for: (Recovery without a known address requires an [Address Database](Creating_and_Using_AddressDB.md)) + * Avalanche + * Bitcoin + * Bitcoin Cash + * Cardano (Shelley Era Addresses) + * Cosmos (Atom) Any many other Cosmos Chains (Nym, GravityBridge, etc) + * Dash + * DigiByte + * Dogecoin + * Ethereum + * Groestlcoin + * Helium + * Litecoin + * Monacoin + * MultiversX + * Polkadot (sr25519, like those produced by polkadot.js) + * Ripple + * Secret Network + * Solana + * Stacks + * Stellar + * Tezos + * Tron + * Vertcoin + * Zilliqa + * And many other 'Bitcoin Like' cryptos + * SLIP39 Passphrase Recovery for most coins supported by the Trezor T + * SLIP39 Seed Share recovery with `seedrecover.py` + * Bitcoin + * Bitcoin Cash + * Dash + * Digibyte + * Dogecoin + * Ethereum + * Litecoin + * Ripple + * Vertcoin + * [Descrambling 12 word seeds](BIP39_descrambling_seedlists.md) (Using Tokenlist feature for BIP39 seeds via seedrecover.py) + * Wallet File password recovery for a range of wallets + +* Seed Phrase (Mnemonic) Recovery for the following wallets + * [Electrum](https://electrum.org/) (1.x, 2.x, 3.x and 4.x) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) + * [Electron-Cash](https://www.electroncash.org/) (2.x, 3.x and 4.x) + * BIP-32/39 compliant wallets ([bitcoinj](https://bitcoinj.github.io/)), including: + * [MultiBit HD](https://multibit.org/) + * [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schildbach.wallet) (with seeds previously extracted by [decrypt\_bitcoinj\_seeds](https://github.com/gurnec/decrypt_bitcoinj_seed)) + * [Hive for Android](https://play.google.com/store/apps/details?id=com.hivewallet.hive.cordova), [for iOS](https://github.com/hivewallet/hive-ios), and [Hive Web](https://hivewallet.com/) + * [Breadwallet](https://brd.com/) + * BIP-32/39/44 Bitcoin & Ethereum compliant wallets, including: + * [Mycelium for Android](https://wallet.mycelium.com/) + * [TREZOR](https://www.bitcointrezor.com/) + * [Ledger](https://www.ledgerwallet.com/) + * [Keepkey](https://shapeshift.io/keepkey/) + * [Blockstream Jade](https://blockstream.com/jade/) + * [Jaxx](https://jaxx.io/) + * [Coinomi](https://www.coinomi.com/) + * [Exodus](https://www.exodus.io/) + * [MyEtherWallet](https://www.myetherwallet.com/) + * [Bither](https://bither.net/) + * [Blockchain.com](https://blockchain.com/wallet) + * [Trust Wallet](https://trustwallet.com/) + * [Encrypted (BIP-38) Paper Wallet Support (Eg: From Bitaddress.org)](https://bitaddress.org) Also works with altcoin forks like liteaddress.org, paper.dash.org, etc... + * Brainwallets + * Sha256(Passphrase) brainwallets (eg: Bitaddress.org, liteaddress.org, paper.dash.org) + * sCrypt Secured Brainwallets (Eg: Warpwallet, Memwallet) + * Ethereum Validator BIP39 Seed Recovery + * [Blockchain.com legacy wallet recovery mnemonic](https://login.blockchain.com/wallet/recover-wallet) + * Bitcoin wallet password recovery support for: + * [Bitcoin Core](https://bitcoincore.org/) + * [MultiBit HD](https://multibit.org/) and [MultiBit Classic](https://multibit.org/help/v0.5/help_contents.html) + * [Electrum](https://electrum.org/) (1.x, 2.x, 3.x and 4.x) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) + * Most wallets based on [bitcoinj](https://bitcoinj.github.io/), including [Hive for OS X](https://github.com/hivewallet/hive-mac/wiki/FAQ) + * BIP-39 passphrases (Also supports all cryptos supported for seed recovery, as well as recovering "Extra Words" for Electrum seeds) + * [mSIGNA (CoinVault)](https://ciphrex.com/products/) + * [Blockchain.com](https://blockchain.com/wallet) + * [block.io](https://block.io/) (Recovery of wallet "Secret PIN") + * [btc.com (aka blocktrail)](https://btc.com) (Recovery of wallet password, needed to decrypt the PDF backup sheets) + * [pywallet --dumpwallet](https://github.com/jackjack-jj/pywallet) o*f Bitcoin Unlimited/Classic/XT/Core wallets + * [Bitcoin Wallet for Android/BlackBerry](https://play.google.com/store/apps/details?id=de.schil*dbach.wallet) spending PINs and encrypted backups + * [KnC Wallet for Android](https://github.com/kncgroup/bitcoin-wallet) encrypted backups + * [Bither](https://bither.net/) + * Altcoin password recovery support for most wallets derived from one of those above, including: + * [Coinomi](https://www.coinomi.com/en/) (Only supports password protected wallets) + * [imToken](https://token.im/) + * [Metamask](https://metamask.io/) (And Metamask clones like Binance Chain Wallet, Ronin Wallet, etc.) + * [Litecoin Core](https://litecoin.org/) + * [Electrum-LTC](https://electrum-ltc.org/) (For Legacy and Segwit Wallets. Set --bip32-path "m/0'/0" for a Segwit wallet, leave bip32-path blank for Legacy... No support for 2fa wallets...) + * [Electron-Cash](https://www.electroncash.org/) (2.x, 3.x and 4.x) + * [Litecoin Wallet for Android](https://litecoin.org/) encrypted backups + * [Dogecoin Core](http://dogecoin.com/) + * [MultiDoge](http://multidoge.org/) + * [Dogechain.info](https://dogechain.info/) + * [Dogecoin Wallet for Android](http://dogecoin.com/) encrypted backups + * [Yoroi Wallet for Cardano](https://yoroi-wallet.com/#/) Master_Passwords extracted from the wallet data (In browser or on rooted/jailbroken phones) + * [Damaged Raw Eth Private Keys]() Individual Private keys that are missing characters. + * [Ethereum UTC Keystore Files](https://myetherwallet.com) Ethereum Keystore files, typically used by wallets like MyEtherWallet, MyCrypto, etc. (Also often used by Eth clones like Theta, etc) + * [Toastwallet](https://toastwallet.github.io/browser/) Toastwallet Passphrase + * [Free and Open Source](http://en.wikipedia.org/wiki/Free_and_open-source_software) - anyone can download, inspect, use, and redistribute this software + * Supported on Windows, Linux, and OS X + * Support for Unicode passwords and seeds + * Multithreaded searches, with user-selectable thread count + * Ability to spread search workload over multiple devices + * [GPU acceleration](GPU_Acceleration.md) for Bitcoin Core Passwords, Blockchain.com (Main and Second Password), Electrum Passwords + BIP39 and Electrum Seeds + * Wildcard expansion for passwords + * Typo simulation for passwords and seeds + * Progress bar and ETA display (at the command line) + * Optional autosave - interrupt and continue password recoveries without losing progress + * Automated seed recovery with a simple graphical user interface + * Ability to search multiple derivation paths simultaneously for a given seed via --pathlist command (example pathlist files in the ) + * “Offline” mode for nearly all supported wallets - use one of the [extract scripts (click for more information)](Extract_Scripts.md) to extract just enough information to attempt password recovery, without giving *btcrecover* or whoever runs it access to *any* of the addresses or private keys in your Bitcoin wallet. + +If you want the tool to support a crypto/wallet that isn't listed above, please test that it works and submit a PR which includes a unit test for that coin and also any required code to accept the address format. + +**_If you are trying to do a recovery for a coin/wallet that isn't listed above, feel free to contact me as it may be possible for you to sponsor the addition of that crypto as part of an assisted recovery fee._** \ No newline at end of file diff --git a/docs/licence.md b/docs/licence.md new file mode 100644 index 000000000..d8cf7d463 --- /dev/null +++ b/docs/licence.md @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/docs/local_mkdocs.md b/docs/local_mkdocs.md new file mode 100644 index 000000000..6656e4c92 --- /dev/null +++ b/docs/local_mkdocs.md @@ -0,0 +1,13 @@ +# Running documentation locally + +You can also view this documentation on your PC, even if it is air-gapped. + +After Python is installed as per the installation guide, run the following commands from the btcrecover folder. + +`pip3 install -r ./docs/requirements.txt` + +Once that has complete run... + +`mkdocs serve` + +You will then be able to access the documentation in your browser at [http://127.0.0.1:8000](http://127.0.0.1:8000) \ No newline at end of file diff --git a/docs/mkdocs_overrides/main.html b/docs/mkdocs_overrides/main.html new file mode 100644 index 000000000..acca1ae86 --- /dev/null +++ b/docs/mkdocs_overrides/main.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block analytics %} + + + +{% endblock %} + +{% block footer %} + +{% include "partials/footer.html" %} +{% endblock %} \ No newline at end of file diff --git a/docs/passwordlist_file.md b/docs/passwordlist_file.md new file mode 100644 index 000000000..3ebb5f8ad --- /dev/null +++ b/docs/passwordlist_file.md @@ -0,0 +1,12 @@ +contributors: gurnec +## The Passwordlist ## + +If you already have a simple list of whole passwords you'd like to test, and you don't need any of the features described above, you can use the `--passwordlist` command-line option (instead of the `--tokenlist` option as described later in the [Running *btcrecover*](TUTORIAL.md#running-btcrecover) section). The passwordlist is just a standard text file, with one password per line. (It can be either raw text or also compressed with gzip)If your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII) (non-English) characters, you should read the section on [Unicode Support](TUTORIAL.md#unicode-support) before continuing. + +If you specify `--passwordlist` without a file, *btcrecover* will prompt you to type in a list of passwords, one per line, in the Command Prompt window. If you already have a text file with the passwords in it, you can use `--passwordlist FILE` instead (replacing `FILE` with the file name). + +Be sure not to add any extra spaces, unless those spaces are actually a part of a password. + +Each line is used verbatim as a single password when using the `--passwordlist` option (and none of the features from above are applied). You can however use any of the Typos features described below to try different variations of the passwords in the passwordlist. + +If you would like to store your command line options alongside the passwordlist file itself, you can add the `--passwordlist-arguments` option when launching *btcrecover*. When this flag is present, *btcrecover* will look at the first line of the passwordlist file; if it begins with `#--`, everything after that sequence is treated as additional command line arguments. The values provided inside the file behave exactly like command line arguments, but the normal command line still takes precedence if you specify the same option in both places. For safety reasons `--passwordlist`, `--tokenlist`, `--performance`, and `--utf8` are not permitted inside the file. diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 000000000..65dcad890 --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,12 @@ +mkdocs==1.6.1 +mkdocs-material +mkdocs-include-markdown-plugin +mkdocs-autolinks-plugin +mkdocs-git-committers-plugin +mkdocs-material-extensions +pymdown-extensions +mkdocs-get-deps +ghp-import +pyyaml +pyyaml-env-tag +PyGithub diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..084301784 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,184 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes docs/requirements.in +# +babel==2.17.0 \ + --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 + # via mkdocs-material +backrefs==5.9 \ + --hash=sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf \ + --hash=sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa \ + --hash=sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59 \ + --hash=sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b \ + --hash=sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f \ + --hash=sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9 \ + --hash=sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60 + # via mkdocs-material +bracex==2.6 \ + --hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \ + --hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7 + # via wcmatch +certifi==2025.10.5 \ + --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de + # via requests +cffi==2.0.0 \ + --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \ + --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \ + --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 + # via + # cryptography + # pynacl +charset-normalizer==3.4.4 \ + --hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \ + --hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \ + --hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 + # via requests +click==8.3.0 \ + --hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc + # via mkdocs +colorama==0.4.6 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via mkdocs-material +cryptography==46.0.3 \ + --hash=sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d \ + --hash=sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a + # via pyjwt +ghp-import==2.1.0 \ + --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 + # via + # -r docs/requirements.in + # mkdocs +idna==3.11 \ + --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea + # via requests +jinja2==3.1.6 \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via + # mkdocs + # mkdocs-material +markdown==3.9 \ + --hash=sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280 + # via + # mkdocs + # mkdocs-material + # pymdown-extensions +markupsafe==3.0.3 \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 + # via + # jinja2 + # mkdocs +mergedeep==1.3.4 \ + --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ + --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + # via + # mkdocs + # mkdocs-get-deps +mkdocs==1.6.1 \ + --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + # via + # -r docs/requirements.in + # mkdocs-autolinks-plugin + # mkdocs-git-committers-plugin + # mkdocs-include-markdown-plugin + # mkdocs-material +mkdocs-autolinks-plugin==0.7.1 \ + --hash=sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151 + # via -r docs/requirements.in +mkdocs-get-deps==0.2.0 \ + --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 + # via + # -r docs/requirements.in + # mkdocs +mkdocs-git-committers-plugin==0.2.3 \ + --hash=sha256:77188d8aacc11d5233d6949435670e3d6545ffb7a0e274d56f32ed3984353c61 + # via -r docs/requirements.in +mkdocs-include-markdown-plugin==7.2.0 \ + --hash=sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b + # via -r docs/requirements.in +mkdocs-material==9.6.22 \ + --hash=sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84 + # via -r docs/requirements.in +mkdocs-material-extensions==1.3.1 \ + --hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + # via + # -r docs/requirements.in + # mkdocs-material +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 + # via mkdocs +paginate==0.5.7 \ + --hash=sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 + # via mkdocs-material +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 + # via mkdocs +platformdirs==4.5.0 \ + --hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 + # via mkdocs-get-deps +pycparser==2.23 \ + --hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 + # via cffi +pygithub==2.8.1 \ + --hash=sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0 + # via + # -r docs/requirements.in + # mkdocs-git-committers-plugin +pygments==2.19.2 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via mkdocs-material +pyjwt[crypto]==2.10.1 \ + --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb + # via pygithub +pymdown-extensions==10.16.1 \ + --hash=sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d + # via + # -r docs/requirements.in + # mkdocs-material +pynacl==1.6.0 \ + --hash=sha256:16c60daceee88d04f8d41d0a4004a7ed8d9a5126b997efd2933e08e93a3bd850 \ + --hash=sha256:04f20784083014e265ad58c1b2dd562c3e35864b5394a14ab54f5d150ee9e53e + # via pygithub +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 + # via ghp-import +pyyaml==6.0.3 \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 + # via + # -r docs/requirements.in + # mkdocs + # mkdocs-get-deps + # pymdown-extensions + # pyyaml-env-tag +pyyaml-env-tag==1.1 \ + --hash=sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + # via + # -r docs/requirements.in + # mkdocs +requests==2.32.5 \ + --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + # via + # mkdocs-material + # pygithub +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + # via python-dateutil +typing-extensions==4.15.0 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + # via pygithub +urllib3==2.5.0 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # pygithub + # requests +watchdog==6.0.0 \ + --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 + # via mkdocs +wcmatch==10.1 \ + --hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \ + --hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af + # via mkdocs-include-markdown-plugin diff --git a/docs/tokenlist_file.md b/docs/tokenlist_file.md new file mode 100644 index 000000000..9aca4b7b2 --- /dev/null +++ b/docs/tokenlist_file.md @@ -0,0 +1,307 @@ +contributors: gurnec +# The Token File # + +*btcrecover* can accept as input a text file which has a list of what are called password “tokens”. A token is simply a portion of a password which you do remember, even if you don't remember where that portion appears in the actual password. It will combine these tokens in different ways to create different whole password guesses to try. + +This file, typically named `tokens.txt`, can be created in any basic text editor, such as Notepad on Windows or TextEdit on OS X, and should probably be saved into the same folder as the `btcrecover.py` script (just to keep things simple). Note that if your password contains any non-[ASCII](https://en.wikipedia.org/wiki/ASCII) (non-English) characters, you should read the section on [Unicode Support](TUTORIAL.md#unicode-support) before continuing. + +## Basics ## + +Let’s say that you remember your password contains 3 parts, you just can’t remember in what order you used them. Here are the contents of a simple `tokens.txt` file: +``` linenums="1" +Cairo +Beetlejuice +Hotel_california +``` +When used with these contents, *btcrecover* will try all possible combinations using one or more of these three tokens, e.g. `Hotel_california` (just one token), `BettlejuiceCairo` (two tokens pasted together), etc. + +Note that lines which start with a `#` are ignored as comments, but only if the `#` is at the *very beginning* of the line: +``` linenums="1" +# This line is a comment, it's ignored. +# The line at the bottom is not a comment because the +# first character on the line is a space, and not a # + #a_single_token_starting_with_the_#_symbol +``` +## Mutual Exclusion ## + +Maybe you’re not sure about how you spelled or capitalized one of those words. Take this token file: +``` linenums="1" +Cairo +Beetlejuice beetlejuice Betelgeuse betelgeuse +Hotel_california +``` +Tokens listed on the same line, separated by spaces, are mutually exclusive and will never be tried together in a password guess. *btcrecover* will try `Cairo` and `bettlejuiceCairoHotel_california`, but it will skip over `Betelgeusebetelgeuse`. Had all four Beetlejuice versions been listed out on separate lines, this would have resulted in trying thousands of additional passwords which we know to be incorrect. As is, this token file only needs to try 48 passwords to account for all possible combinations. Had they all been on separate lines, it would have had to try 1,956 different combinations. + +In short, when you’re sure that certain tokens or variations of a token have no chance of appearing together in a password, placing them all on the same line can save a lot of time. + +## Required Tokens ## + +What if you’re certain that `Cairo` appears in the password, but you’re not so sure about the other tokens? +``` linenums="1" ++ Cairo +Beetlejuice beetlejuice Betelgeuse betelgeuse +Hotel_california +``` +Placing a `+` (and some space after it) at the beginning of a line tells *btcrecover* to only try passwords that include `Cairo` in them. You can also combine these two last features. Here’s a longer example: +``` linenums="1" +Cairo cairo Katmai katmai ++ Beetlejuice beetlejuice Betelgeuse betelgeuse +Hotel_california hotel_california +``` +In this example above, passwords will be constructed by taking at most one token from the first line, exactly one token from the second line (it’s required), and at most one token from the third line. So `Hotel_californiaBetelgeuse` would be tried, but `cairoKatmaiBetelgeuse` would be skipped (`cairo` and `Katmai` are on the same line, so they’re never tried together) and `katmaiHotel_california` is also skipped (because one token from the second line is required in every try). + +This file will create a total of just 244 different combinations. Had all ten of those tokens been listed on separate lines, it would have produced 9,864,100 guesses, which could take days longer to test! + +## Anchors ## + +### Beginning and Ending Anchors ### + +Another way to save time is to use “anchors”. You can tell *btcrecover* that certain tokens, if they are present at all, are definitely at the beginning or end of the password: +``` linenums="1" +^Cairo +Beetlejuice beetlejuice Betelgeuse betelgeuse +Hotel_california$ +``` +In this example above, the `^` symbol is considered special if it appears at the beginning of any token (it’s not actually a part of the password), and the `$` symbol is special if it appears at the end of any token. `Cairo`, if it is tried, is only tried at the beginning of a password, and `Hotel_california`, if it is tried, is only tried at the end. Note that neither is required to be tried in password guesses with the example above. As before, all of these options can be combined: +``` linenums="1" +Cairo +Beetlejuice beetlejuice Betelgeuse betelgeuse ++ ^Hotel_california ^hotel_california +``` +In this example above, either `Hotel_california` or `hotel_california` is *required* at the beginning of every password that is tried (and the other tokens are tried normally after that). + +### Positional Anchors ### + +Tokens with positional anchors may only appear at one specific position in the password -- there are always a specific number of other tokens which precede the anchored one. In the example below you'll notice a number in between the two `^` symbols added to the very beginning to create positionally anchored tokens (with no spaces): +``` linenums="1" +^2^Second_or_bust +^3^Third_or_bust +Cairo +Beetlejuice +Hotel_california +``` +As you can guess, `Second_or_bust`, if it is tried, is only tried as the second token in a password, and `Third_or_bust`, if it is tried, is only tried as the third. (Neither token is required because there is no `+` at the beginning these of these lines.) + +### Middle Anchors ### + +Middle anchors are a bit like positional anchors, only more flexible: the anchored tokens may appear once throughout a specific *range* of positions in the password. + +**Note** that placing a middle anchor on a token introduces a special restriction: it *forces* the token into the *middle* of a password. A token with a middle anchor (unlike any of the other anchors described above) will *never* be tried as the first or last token of a password. + +You specify a middle anchor by adding a comma and two numbers (between the `^` symbols) at the very beginning of a token (all with no spaces): +``` linenums="1" +^2,3^Second_or_third_(but_never_last) +^2,4^Second_to_fourth_(but_never_last) +Cairo +Beetlejuice +Hotel_california +``` + As mentioned above, neither of those middle-anchored tokens will ever be tried as the last token in a password, so something (one or more of the non-anchored tokens) will appear after the middle-anchored ones in every guess in which they appear. Since tokens with middle anchors never appear at the beginning either, the smallest value you can use for that first number is 2. Finally, when you specify the range, you can leave out one (or even both) of the numbers, like this: +``` linenums="1" +^3,^Third_or_after_(but_never_last) +^,3^Third_or_earlier(but_never_first_or_last) +^,^Anywhere_in_the_middle +Cairo +Beetlejuice +Hotel_california +``` +You can't leave out the comma (that's what makes it a middle anchor instead of a positional anchor). Leaving out a number doesn't change the “never at the beginning or the end” rule which always applies to middle anchors. If you do need a token with a middle anchor to also possibly appear at the beginning or end of a password, you can add second copy to the same line with a beginning or end anchor (because at most one token on a line can appear in any guess): +``` linenums="1" +^,^Anywhere_in_the_middle_or_end Anywhere_in_the_middle_or_end$ +^,^Anywhere_in_the_middle_or_beginning ^Anywhere_in_the_middle_or_beginning +``` + +### Relative Anchors ### + +Relative anchors restrict the position of tokens relative to one another. They are only affected by other tokens which also have relative anchors. They look like positional anchors, except they have a single `r` preceding the relative number value: +``` linenums="1" +^r1^Earlier +^r2^Middlish_A +^r2^Middlish_B +^r3^Later +Anywhere +``` +In this example above, if two or more relative-anchored tokens appear together in a single password guess, they appear in their specified order. `Earlier Anywhere Later` and `Anywhere Middlish_A Later` would be tried, however `Later Earlier` would not. Note that `Middlish_A` and `Middlish_B` can appear in the same guess, and they can appear with either being first since they have a matching relative value, e.g. `Middlish_B Middlish_A Later` would be tried. + +You cannot specify a single token with both a positional and relative anchor at the same time. + +## Token Counts ## + +There are a number of command-line options that affect the combinations tried. The `--max-tokens` option limits the number of tokens that are added together and tried. With `--max-tokens` set to 2, `Hotel_californiaCairo`, made from two tokens, would be tried from the earlier example, but `Hotel_californiaCairoBeetlejuice` would be skipped because it’s made from three tokens. You can still use *btcrecover* even if you have a large number of tokens, as long as `--max-tokens` is set to something reasonable. If you’d like to re-run *btcrecover* with a larger number of `--max-tokens` if at first it didn’t succeed, you can also specify `--min-tokens` to avoid trying combinations you’ve already tried. + +## Expanding Wildcards ## + +What if you think one of the tokens has a number in it, but you’re not sure what that number is? For example, if you think that Cairo is definitely followed by a single digit, you could do this: +``` linenums="1" +Cairo0 Cairo1 Cairo2 Cairo3 Cairo4 Cairo5 Cairo6 Cairo7 Cairo8 Cairo9 +Beetlejuice +Hotel_california +``` +While this definitely works, it’s not very convenient. This next token file has the same effect, but it’s easier to write: +``` linenums="1" +Cairo%d +Beetlejuice +Hotel_california +``` +The `%d` is a wildcard which is replaced by all combinations of a single digit. Here are some examples of the different types of wildcards you can use: + + * `%d` - a single digit + * `%2d` - exactly 2 digits + * `%1,3d` - between 1 and 3 digits (all possible permutations thereof) + * `%0,2d` - between 0 and 2 digits (in other words, the case where there are no digits is also tried) + * `%a` - a single ASCII lowercase letter + * `%1,3a` - between 1 and 3 lowercase letters + * `%A` - a single ASCII uppercase letter + * `%n` - a single digit or lowercase letter + * `%N` - a single digit or uppercase letter + * `%ia` - a “case-insensitive” version of %a: a single lower or uppercase letter + * `%in` - a single digit, lower or uppercase letter + * `%1,2in`- between 1 and 2 characters long of digits, lower or uppercase letters + * `%[chars]` - exactly 1 of the characters between `[` and `]` (e.g. either a `c`, `h`, `a`, `r`, or `s`) _**Note**: All characters in this wildcard are used as-is, even if that character would normally have its own wildcard if used as a token, like space, $, % or ^_ + * `%1,3[chars]` - between 1 and 3 of the characters between `[` and `]` + * `%[0-9a-f]` - exactly 1 of these characters: `0123456789abcdef` + * `%2i[0-9a-f]` - exactly 2 of these characters: `0123456789abcdefABCDEF` + * `%s` - a single space + * `%l` - a single line feed character + * `%r` - a single carriage return character + * `%R` - a single line feed or carriage return character + * `%t` - a single tab character + * `%T` - a single space or tab character + * `%w` - a single space, line feed, or carriage return character + * `%W` - a single space, line feed, carriage return, or tab character + * `%y` - any single ASCII symbol + * `%Y` - any single ASCII digit or symbol + * `%p` - any single ASCII letter, digit, or symbol + * `%P` - any single character from either `%p` or `%W` (pretty much everything) + * `%q` - any single ASCII letter, digit, symbol or space. (The characters typically used for BIP39 passphrase for most vendors) + * `%c` - a single character from a custom set specified at the command line with `--custom-wild characters` + * `%C` - an uppercased version of `%c` (the same as `%c` if `%c` has no lowercase letters) + * `%ic` - a case-insensitive version of `%c` + * `%%` - a single `%` (so that `%`’s in your password aren’t confused as wildcards) + * `%^` - a single `^` (so it’s not confused with an anchor if it’s at the beginning of a token) + * `%S` - a single `$` (yes, that’s `%` and a capital `S` that gets replaced by a dollar sign, sorry if that’s confusing) + * `%H` - a single hexidcimal character (0-9, A-F) + * `%B` - a single Base58 character (Bitcoin Base58 Character Set) + * `%U` - a single Unicode character (All Unicode characters up to 65,535) + * `%e` - Custom String Repeating wildcard, [Read about behaviour and usage](custom_wildcards.md) + * `%f` - Custom String Repeating wildcard, [Read about behaviour and usage](custom_wildcards.md) + * `%j` - Custom String Standard wildcard, [Read about behaviour and usage](custom_wildcards.md) + * `%k` - Custom String Standard wildcard, [Read about behaviour and usage](custom_wildcards.md) + +Up until now, most of the features help by reducing the number of passwords that need to be tried by exploiting your knowledge of what’s probably in the password. Wildcards significantly expand the number of passwords that need to be tried, so they’re best used in moderation. + +## Backreference Wildcards ## + +Backreference wildcards copy one or more characters which appear somewhere earlier in the password. In the simplest case, they're not very useful. For example, in the token `Z%b`, the `%b` simply copies the character which immediately precedes it, resulting in `ZZ`. + +Consider the case where the password contains patterns such as `AA`, `BB`, up through `ZZ`, but would never contain `AZ`. You could use `%2A` to generate these patterns, but then you'd end up with `AZ` being tried. `%2A` generates 676 different combinations, but in this example we only want to try 26. Instead you can use two wildcards together: `%A%b`. The `%A` will expand into a single letter (from `A` to `Z`), and *after* this expansion happens, the `%b` will copy that letter, resulting in only the 26 patterns we want. + +As with normal wildcards, backreference wildcards may contain a copy length, for example: + + * `Test%d%b` - `Test00` through `Test99`, but never `Test12` + * `Test%d%2b` - `Test000` through `Test999`, but never `Test123` + * `Test%d%0,3b` - `Test0` to `Test9` (the backreference length is 0), `Test00` to `Test99`, etc., `Test0000` to `Test9999` + +In the examples so far, the copying starts with the character immediately to the left of the `%b`, but this can be changed by adding a `;#` just before the `b`, for example: + + * `Test%b` - `Testt` + * `Test%;1b` - starts 1 back, same as above, `Testt` + * `Test%;2b` - starts 2 back, `Tests` + * `Test%;4b` - starts 4 back, `TestT` + * `Test%2;4b` - starts 4 back, with a copy length of 2: `TestTe` + * `Test%8;4b` - starts 4 back, with a copy length of 8: `TestTestTest` + * `Test%0,2;4b` - starts 4 back, with a copy length from 0 to 2: `Test`, `TestT`, and `TestTe` + * `%2Atest%2;6b` - patterns such as `ABtestAB` and `XKtestXK` where the two capital letters before and after `test` match each other, but never `ABtestXK` where they don't match + +To summarize, wildcards to the left of a `%b` are expanded first, and then the `%b` is replaced by copying one or more characters from the left, and then wildcards towards the right (if any) are examined. + +## Contracting Wildcards ## + +Instead of adding new characters to a password guess, contracting wildcards remove one or more characters. Here's an example: + + Start%0,2-End + +The `%0,2-` contracting wildcard will remove between 0 and 2 adjacent characters from either side, so that each of `StartEnd` (removes 0), `StarEnd` (removes 1 from left), `StaEnd` (removes 2 from left), `Starnd` (removes 1 from left and 1 from right), `Startnd` (removes 1 from right), and `Startd` (removes 2 from right) will be tried. This can be useful when considering copy-paste errors, for example: + + %0,20-A/Long/Password/with/symbols/that/maybe/was/partially/copy/pasted%0,20- + +Different versions of this password will be tried removing up to 20 characters from either end. + +Here are the three types of contracting wildcards: + + * `%0,5-` - removes between 0 and 5 adjacent characters (total) taken from either side of the wildcard + * `%0,5<` - removes between 0 and 5 adjacent characters only from the wildcard's left + * `%0,5>` - removes between 0 and 5 adjacent characters only from the wildcard's right + +You may want to note that a contracting wildcard in one token can potentially remove characters from other tokens, but it will never remove or cross over another wildcard. Here's an example to fully illustrate this (feel free to skip to the next section if you're not interested in these specific details): +``` linenums="1" +AAAA%0,10>BBBB +xxxx%dyyyy +``` +These two tokens each have eight normal letters. The first token has a contracting wildcard which removes up to 10 characters from its right, and the second token has an expanding wildcard which expands to a single digit. + +One of the passwords generated from these tokens is `AAAABBxxxx5yyyy`, which comes from selecting the first token followed by the second token, and then applying the wildcards with the contracting wildcard removing two characters. Another is `AAAAxx5yyyy` which comes from the same tokens, but the contracting wildcard now is removing six characters, two of which are from the second token. + +The digit and the `yyyy` will never be removed by the contracting wildcard because other wildcards are never removed or crossed over. Even though the contracting wildcard is set to remove up to 10 characters, `AAAAyyy` will never be produced because the `%d` blocks it. + +## Keyboard Walking — Backreference Wildcards, revisited ## + +This feature combines traits of both backreference wildcards and typos maps into a single function. If you haven't read about typos maps below (or about backreference wildcards above), you should probably skip this section for now and come back later. + +Consider a complex password pattern such as this: `00test11`, `11test22`, etc. up through `88test99`. In other words, the pattern is generated by combining these 5 strings: `#` `#` `test` `#+1` `#+1`. Using simple backreference wildcards, we can almost produce such a pattern with this token: `%d%btest%d%b`. This produces everything from our list, but it also produced a lot more that we don't want, for example `33test55` is produced even though it doesn't match the pattern because 3+1 is not 5. + +Instead a way is needed for a backreference wildcard to do more than simply copy a previous character, it must be able to create a *modified copy* of a previous character. It can do this the same way that a typos map replaces characters by using a separate map file to determine the replacement. So to continue this example, a new map file is needed, `nextdigit.txt`: +``` linenums="1" +0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 +8 9 +``` +Finally, here's a token that makes use of this map file to generate the pattern we're looking for: `%d%btest%2;nextdigit.txt;6b`. That's pretty complicated, so let's break it down: + + * `%d` - expands to `0` through `9` + * `%b` - copies the previous character, so no we have `00` through `99` + * `test` - now we have `00test` through `99test` + * `%2;nextdigit.txt;6b` - a single backreference wildcard which is made up of: + * `2` - the copy length (the length of the result after expansion) + * `nextdigit.txt` - the map file used determine how to modify characters + * `6` - how far to the left of the wildcard to start copying; 6 characters counting leftwards from the end of `00test` is the first `0` + + The result of expanding this wildcard when the token starts off with `00test` is `00test11`. It expands into *two* `1`'s because the copy length is 2, and it expands into modified `1`'s instead of just copying the `0`'s because the file maps a `0` (in its first column) to a `1` (in the second column). Likewise, a `77test` is expanded into `77test88`. `99test` is expanded into `99test99` because the the lookup character, a `9`, isn't present in (the first column of) the map file, and so it's copied unmodified. + +Note that when you use a map file inside a backreference wildcard, the file name always has a semicolon (`;`) on either side. These are all valid backreference wildcards (but they're all different because the have different copy lengths and starting positions): `%;file.txt;b`, `%2;file.txt;b`, `%;file.txt;6b`, `%2;file.txt;6b`. + +The final example involves something called keyboard walking. Consider a password pattern where a typist starts with any letter, and then chooses the next character by moving their finger using a particular pattern, for example by always going either diagonal up and right, or diagonal down and right, and then repeating until the result is a certain length. A single backreference wildcard that uses a map file can create this pattern. + +Here's what the beginning of a map file for this pattern, `pattern.txt`, would look like: +``` linenums="1" +q 2a +a wz +z s +2 w +w 3s +... +``` +So if the last letter is a `q`, the next letter in the pattern is either a `2` or an `a` (for going upper-right or lower-right). If the last letter is a `z`, there's only one direction available for the next letter, upper-right to `s`. With this map file, and the following token, all combinations which follow this pattern between 4 and 6 characters long would be tried: `%a%3,5;pattern.txt;b` + +## Delimiters, Spaces, and Special Symbols in Passwords ## + +By default, *btcrecover* uses one or more whitespaces to separate tokens in the tokenlist file, and to separated to-be-replaced characters from their replacements in the typos-map file. It also ignores any extra whitespace in these files. This makes it difficult to test passwords which include spaces and certain other symbols. + +One way around this that only works for the tokenlist file is to use the `%s` wildcard which will be replaced by a single space. Another option that works both for the tokenlist file and a typos-map file is using the `--delimiter` option which allows you to change this behavior. If used, whitespace is no longer ignored, nor is extra whitespace stripped. Instead, the new `--delimiter` string must be used *exactly as specified* to separate tokens or typos-map columns. Any whitespace becomes a part of a token, so you must take care not to add any inadvertent whitespace to these files. + +Additionally, *btcrecover* considers the following symbols special under certain specific circumstances in the tokenlist file (and for the `#` symbol, also in the typos-map file). A special symbol is part of the syntax, and not part of a password. + + * `%` - always considered special (except when *inside* a `%[...]`-style wildcard, see the [Wildcards](#expanding-wildcards) section); `%%` in a token will be replaced by `%` during searches + * `^` - only special if it's the first character of a token; `%^` will be replaced by `^` during searches + * `$` - only special if it's the last character of a token; `%S` (note the capital `S`) will be replaced by `$` during searches + * `#` - only special if it's the *very first* character on a line, see the [note about comments here](#basics) + * `+` - only special if it's the first (not including any spaces) character on a line, immediately followed by a space (or delimiter) and then some tokens (see the [Mutual Exclusion](#mutual-exclusion) section); if you need a single `+` character as a token, make sure it's not the first token on the line, or it's on a line all by itself + * `]` - only special when it follows `%[` in a token to mark the end of a `%[...]`-style wildcard. If it appears *immediately after* the `%[`, it is part of the replacement set and the *next* `]` actually ends the wildcard, e.g. the wildcard `%[]x]` contains two replacement characters, `]` and `x`. + +None of this applies to passwordlist files, which always treat spaces and symbols (except for carriage-returns and line-feeds) verbatim, treating them as parts of a password. diff --git a/btcrecover/wordlists/README.md b/docs/wordlist_notes.md similarity index 67% rename from btcrecover/wordlists/README.md rename to docs/wordlist_notes.md index d4f4b954e..a661f92dd 100644 --- a/btcrecover/wordlists/README.md +++ b/docs/wordlist_notes.md @@ -1,9 +1,10 @@ +contributors: gurnec ## *seedrecover* wordlists ## All wordlists used by *seedrecover.py* are sourced from third parties. In particular: * BIP-39 wordlists can be found here: - * Electrum 2 wordlists can be found here: + * Electrum 2 wordlists can be found here: The wordlist files themselves were copied verbatim from the sources above, including any copyright notices. Only the filenames have been modified. @@ -12,7 +13,7 @@ The wordlist files themselves were copied verbatim from the sources above, inclu *seedrecover.py* attempts to guess the correct language of the mnemonic it is trying to recover, however it may not always guess correctly (in particular when it comes to Chinese). You can instruct *seedrecover.py* to use a specific language via the `--language LANG-CODE` option. -The available `LANG-CODE`s (based on ISO 639-1) are taken from the filenames in the same directory as this file; they follow the first `-` in their filenames. Specifically, in alphabetical order they are: +The available `LANG-CODE`s (based on ISO 639-1) are taken from the filenames [in the folder here](https://github.com/3rdIteration/btcrecover/tree/master/btcrecover/wordlists); they follow the first `-` in their filenames. Specifically, in alphabetical order they are: * Chinese, simplified - `zh-hans` * Chinese, traditional (BIP-39 only) - `zh-hant` @@ -21,7 +22,8 @@ The available `LANG-CODE`s (based on ISO 639-1) are taken from the filenames in * Italian (BIP-39 only) - `it` * Japanese - `ja` * Korean (BIP-39 only) - `ko` - * Portuguese (Electrum 2.x only) - `pt` + * Portuguese - `pt` * Spanish - `es` + * Czech - `cs` -There are also four "firstfour" language codes based on the ones above: `en-firstfour`, `es-firstfour`, `fr-firstfour`, and `it-firstfour`. Wallet software that uses mnemonics which include just the first four letters of each word can use one of these language codes. \ No newline at end of file +There are also four "firstfour" language codes based on the ones above: `en-firstfour`, `es-firstfour`, `fr-firstfour`, `it-firstfour`, `pt-firstfour` and `cs-firstfour`. Wallet software that uses mnemonics which include just the first four letters of each word can use one of these language codes. diff --git a/extract-scripts/download-blockchain-wallet.py b/extract-scripts/download-blockchain-wallet.py deleted file mode 100755 index fd9cec7ab..000000000 --- a/extract-scripts/download-blockchain-wallet.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python - -# download-blockchain-wallet.py -- Blockchain.info wallet file downloader -# Copyright (C) 2016, 2017 Christopher Gurnee -# -# This file is part of btcrecover. -# -# btcrecover is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version -# 2 of the License, or (at your option) any later version. -# -# btcrecover is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ - -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! - -from __future__ import print_function -import sys, os.path, atexit, uuid, urllib2, json, time - -# The base URL -BASE_URL = "https://blockchain.info/" -# The api_code (as of Feb 2 2017) -API_CODE = "1770d5d9-bcea-4d28-ad21-6cbd5be018a8" - -prog = os.path.basename(sys.argv[0]) - -if len(sys.argv) < 2: - atexit.register(lambda: raw_input("\nPress Enter to exit ...")) - filename = "wallet.aes.json" -elif len(sys.argv) == 2 and not sys.argv[1].startswith("-"): - filename = sys.argv[1] -else: - print("usage:", prog, "[NEW_BLOCKCHAIN_WALLET_FILE]", file=sys.stderr) - sys.exit(2) - -# Refuse to overwrite an existing file -assert not os.path.exists(filename), filename + " already exists, won't overwrite" - -print("Please enter your wallet's ID (e.g. 9bb4c672-563e-4806-9012-a3e8f86a0eca)") -wallet_id = str(uuid.UUID(raw_input("> ").strip())) - - -# Performs a web request, adding the api_code and (if available) auth_token -auth_token = None -def do_request(query, body = None): - if body is None: - assert "?" in query - query += "&api_code=" + API_CODE - req = urllib2.Request(BASE_URL + query) - if body is not None: - req.add_data((body+"&" if body else "") + "api_code=" + API_CODE) - if auth_token: - req.add_header("authorization", "Bearer " + auth_token) - try: - return urllib2.urlopen(req, cadefault=True) # calls ssl.create_default_context() (despite what the docs say) - except TypeError: - return urllib2.urlopen(req) # Python < 2.7.9 doesn't support the cadefault argument -# -# Performs a do_request(), decoding the result as json -def do_request_json(query, body = None): - return json.load(do_request(query, body)) - - -# Get an auth_token -auth_token = do_request_json("sessions", "")["token"] # a POST request - -# Try to download the wallet -try: - wallet_data = do_request_json( - "wallet/{}?format=json".format(wallet_id) - ).get("payload") - -# If IP address / email verification is required -except urllib2.HTTPError as e: - error_msg = e.read() - try: - error_msg = json.loads(error_msg)["initial_error"] - except: pass - print(error_msg) - if error_msg.lower().startswith("unknown wallet identifier"): - sys.exit(1) - - # Wait for the user to complete the requested authorization - time.sleep(5) - print("Waiting for authorization (press Ctrl-C to give up)...") - while True: - poll_data = do_request_json("wallet/poll-for-session-guid?format=json") - if "guid" in poll_data: - break - time.sleep(5) - print() - - # Try again to download the wallet (this shouldn't fail) - wallet_data = do_request_json( - "wallet/{}?format=json".format(wallet_id) - ).get("payload") - -# If there was no payload data, then 2FA is enabled -while not wallet_data: - - print("This wallet has two-factor authentication enabled, please enter your 2FA code") - two_factor = raw_input("> ").strip() - - try: - # Send the 2FA to the server and download the wallet - wallet_data = do_request("wallet", - "method=get-wallet&guid={}&payload={}&length={}" - .format(wallet_id, two_factor, len(two_factor)) - ).read() - - except urllib2.HTTPError as e: - print(e.read() + "\n", file=sys.stderr) - -# Save the wallet -with open(filename, "wb") as wallet_file: - wallet_file.write(wallet_data) - -print("Wallet file saved as " + filename) diff --git a/extract-scripts/extract-armory-privkey.py b/extract-scripts/extract-armory-privkey.py deleted file mode 100755 index 7664469e1..000000000 --- a/extract-scripts/extract-armory-privkey.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python - -# extract-armory-privkey.py -- Armory private key extractor -# Copyright (C) 2014, 2015 Christopher Gurnee -# -# This file is part of btcrecover. -# -# btcrecover is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version -# 2 of the License, or (at your option) any later version. -# -# btcrecover is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ - -# If you find this program helpful, please consider a small -# donation to the developer at the following Bitcoin address: -# -# 3Au8ZodNHPei7MQiSVAWb7NB2yqsb48GW4 -# -# Thank You! - -from __future__ import print_function -import sys, os.path, struct, zlib, base64, time - -prog = os.path.basename(sys.argv[0]) - -def print_usage(): - print("usage:", prog, "ARMORY_WALLET_FILE