[
  {
    "path": ".gitignore",
    "content": "####-*.patch\n*.pyc\n*.swp\nbuild/\ndist/\n*.egg/\n/electrum.py\n.DS_Store\ncontrib/pyinstaller/\nElectrum_BTCP.egg-info/\ngui/qt/icons_rc.py\nlocale/\n.devlocaltmp/\n*_trial_temp\npackages\nenv/\n.tox/\n.buildozer/\nbin/\n\n# tox files\n.cache/\n.coverage\n\n# User-specific stuff:\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/dictionaries\n"
  },
  {
    "path": ".idea/electrum.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"PYTHON_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\">\n      <sourceFolder url=\"file://$MODULE_DIR$\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/pkg\" isTestSource=\"false\" />\n    </content>\n    <orderEntry type=\"jdk\" jdkName=\"Python 3.6 (electrum)\" jdkType=\"Python SDK\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n  <component name=\"TestRunnerService\">\n    <option name=\"PROJECT_TEST_RUNNER\" value=\"Unittests\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectRootManager\" version=\"2\" project-jdk-name=\"Python 3.6 (electrum)\" project-jdk-type=\"Python SDK\" />\n</project>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/electrum.iml\" filepath=\"$PROJECT_DIR$/.idea/electrum.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: python\npython:\n    - 3.5\n    - 3.6\ninstall:\n  - pip install -r requirements_travis.txt\ncache:\n  - pip\nscript:\n    - tox\nafter_success:\n    - if [ \"$TRAVIS_BRANCH\" = \"master\" ]; then pip install pycurl requests && contrib/make_locale; fi\n    - coveralls\n"
  },
  {
    "path": "AUTHORS",
    "content": "ThomasV - Creator and maintainer.\nAnimazing / Tachikoma - Styled the new GUI. Mac version.\nAzelphur - GUI stuff.\nCoblee - Alternate coin support and py2app support.\nDeafboy - Ubuntu packages.\nEagleTM - Bugfixes.\nErebusBat - Mac distribution.\nGenjix - Porting pro-mode functionality to lite-gui and worked on server\nSlush - Work on the server. Designed the original Stratum spec.\nJulian Toash (Tuxavant) - Various fixes to the client.\nrdymac - Website and translations.\nkyuupichan - Miscellaneous."
  },
  {
    "path": "Dockerfile",
    "content": "FROM ubuntu:18.04\n\nENV VERSION 1.0.0\n\nRUN set -x \\\n    && apt-get update \\\n    && apt-get install -y curl \\\n    && curl -sL https://github.com/BTCPrivate/electrum-btcp/archive/P!${VERSION}.tar.gz |tar xvz \\\n    && mv electrum-btcp-P-${VERSION} electrum-btcp \\\n    && cd electrum-btcp \\\n    && apt-get install -y $(grep -vE \"^\\s*#\" packages.txt  | tr \"\\n\" \" \") \\\n    && pip3 install -r requirements.txt \\\n    && pip3 install pyblake2 \\\n    && protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto \\\n    && pyrcc5 icons.qrc -o gui/qt/icons_rc.py \\\n    && ./contrib/make_locale\n\nWORKDIR /electrum-btcp\n\nENV DISPLAY :0\n\nCMD ./electrum\n\n\n"
  },
  {
    "path": "Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleURLTypes</key>\n  <array>\n    <dict>\n      <key>CFBundleURLName</key>\n      <string>bitcoinprivate</string>\n      <key>CFBundleURLSchemes</key>\n      <array>\n        <string>bitcoinprivate</string>\n      </array>\n    </dict>\n  </array>\n  <key>LSArchitecturePriority</key>\n  <array>\n    <string>x86_64</string>\n    <string>i386</string>\n  </array>\n</dict>\n</plist>\n"
  },
  {
    "path": "LICENCE",
    "content": "The MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENCE RELEASE-NOTES AUTHORS\ninclude README.rst\ninclude electrum.conf.sample\ninclude electrum.desktop\ninclude *.py\ninclude electrum\nrecursive-include lib *.py\nrecursive-include gui *.py\nrecursive-include plugins *.py\nrecursive-include packages *.py\nrecursive-include packages cacert.pem\ninclude app.fil\ninclude icons.qrc\nrecursive-include icons *\nrecursive-include scripts *\n\n"
  },
  {
    "path": "README.rst",
    "content": "BTCP Electrum - Lightweight Bitcoin Private Wallet\n==========================================\n.. image:: https://opencollective.com/electrum-btcp/backers/badge.svg\n\n    :alt: Backers on Open Collective\n\n    :target: #backers\n\n.. image:: https://opencollective.com/electrum-btcp/sponsors/badge.svg\n\n    :alt: Sponsors on Open Collective\n\n    :target: #sponsors\n\n\n\nDownload the current Release: https://github.com/BTCPrivate/electrum-btcp/releases/\n\n\nViewing & Sending from Z addresses is not yet supported on this wallet.\n\n\nKnow about your data directory:\n\n    Linux & Mac: ~/.electrum-btcp/\n    Windows: C:\\Users\\YourUserName\\AppData\\Roaming\\Electrum-btcp\\\n\n    ~/.electrum-btcp/wallets/ has your wallet files - BACK UP THIS FOLDER\n\nYou can also use the 'Export Private Keys' and 'Show Seed' functions from inside the application to write down and safely store your the keys to your funds.\n\nPlease use the issue tracker for bug reports, feature requests, and other mission-critical information. It is actively monitored by the Zclassic development team. For general support, please visit our Discord: https://discord.gg/2PRZ5q\n\nDevelopment Version\n===================\n\nFirst, clone from Github::\n\n    git clone git://github.com/BTCPrivate/electrum-btcp.git\n    cd electrum-btcp\n\nFor Mac:\n--------\n\nUsing Homebrew::\n\n    # Setup Homebrew\n    ./setup-mac\n\n    # Install Homebrew dependencies\n    brew bundle\n\n    # Install Python dependencies\n    pip3 install -r requirements.txt\n\n    # Build icons\n    pyrcc5 icons.qrc -o gui/qt/icons_rc.py\n\n    # Compile the protobuf description file\n    protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto\n\n    # Build .app, .dmg\n    ./create-dmg\n\n    # Run\n    ./electrum-btcp\n\n\nFor Linux:\n----------\n\nInstall Dependencies::\n\n  sudo apt-get install $(grep -vE \"^\\s*#\" packages.txt  | tr \"\\n\" \" \")\n\n  pip install -r requirements.txt\n\n  // ^ pip3 for newer version\n\n  (Ubuntu with ledger wallet)\n  ln -s /lib/x86_64-linux-gnu/libudev.so.1 /lib/x86_64-linux-gnu/libudev.so\n\n  # For yum installations (no apt-get), or for a clean python env, use Anaconda with Python 3:\n\n  #https://poweruphosting.com/blog/install-anaconda-python-ubuntu-16-04/\n\n\nCompile the icons file for Qt::\n\n    pyrcc5 icons.qrc -o gui/qt/icons_rc.py\n\nFor the Linux app launcher (start menu) icon::\n\n    sudo desktop-file-install electrum.desktop\n\nCompile the protobuf description file::\n\n    protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto\n\nCreate translations (optional)::\n\n    ./contrib/make_locale\n\nRun::\n\n    ./electrum-btcp\n\nFor Ubuntu 18.04 including Docker install:\n----------------------\n\nUpdate apt package index and upgrade packages as needed\n\n    sudo apt-get update && apt-get upgrade\n\nInstall Docker package from Ubuntu repository\n\n    sudo apt install docker.io\n\nBuild the docker image::\n\n    sudo ./build-docker.sh\n\nRun the docker image::\n\n    ./run-docker.sh\n\n\nFor Linux with docker:\n----------------------\n\nBuild the docker image::\n\n    ./build-docker.sh\n\nRun the docker image::\n\n    ./run-docker.sh\n\n\nBuilding Releases\n=================\n\n\nMacOS\n------\n\nSimply - ::\n\n    ./setup-mac.sh\n\n    sudo ./create-dmg.sh\n\nWindows\n-------\n\nSee `contrib/build-wine/README` file.\n\n\nAndroid\n-------\n\nSee `gui/kivy/Readme.txt` file.\nUPSTREAM PATCH: https://github.com/spesmilo/electrum/blob/master/gui/kivy/Readme.md\n\n---\n\nTo just create binaries, create the 'packages/' directory::\n\n    ./contrib/make_packages\n\n(This directory contains the Python dependencies used by Electrum.)\n\n\nBTCP Hints and Debug\n===================\n\nThere are several useful scripts in:\n\n    scripts\n\nThis is a good initial check to determine whether things are working:\n\n    cd scripts\n    python3 block_headers\n\n--\n\nThe Zclassic Wiki is located at: https://github.com/z-classic/zclassic/wiki. Please use this as a reference and feel free to contribute.\n\n    ~/.electrum-btcp/\n\n    ~/.electrum-btcp/wallets/ has your wallet files - ** back up this folder **\n\n    ~/.electrum-btcp/config has your Electrum connection object.\n\nCredits\n\n+++++++\n\nContributors\n\n------------\n\nThis project exists thanks to all the people who contribute! \n\n.. image:: https://opencollective.com/electrum-btcp/contributors.svg?width=890&button=false\n\nBackers\n\n-------\n\nThank you to all our backers! `Become a backer`__.\n\n.. image:: https://opencollective.com/electrum-btcp/backers.svg?width=890\n\n    :target: https://opencollective.com/electrum-btcp#backers\n\n__ Backer_\n\n.. _Backer: https://opencollective.com/electrum-btcp#backer\n\nSponsors\n\n--------\n\nSupport us by becoming a sponsor. Your logo will show up here with a link to your website. `Become a sponsor`__.\n\n.. image:: https://opencollective.com/electrum-btcp/sponsor/0/avatar.svg\n\n    :target: https://opencollective.com/electrum-btcp/sponsor/0/website\n\n__ Sponsor_\n\n.. _Sponsor: https://opencollective.com/electrum-btcp#sponsor\n\n\n\nOriginal Project Info\n---------------------\n::\n\n  Forked from **spesmilo/electrum**: https://github.com/spesmilo/electrum\n\n  Licence: MIT Licence\n  Author: Thomas Voegtlin\n  Language: Python (GUI: Qt, Kivy)\n  Platforms: Windows, Mac, Linux, Android\n  Homepage: https://electrum.org/\n\n\n.. image:: https://travis-ci.org/spesmilo/electrum.svg?branch=master\n    :target: https://travis-ci.org/spesmilo/electrum\n    :alt: Build Status\n.. image:: https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master\n    :target: https://coveralls.io/github/spesmilo/electrum?branch=master\n    :alt: Test coverage statistics\n\n\n---\n\nThe Bitcoin Private Team\n"
  },
  {
    "path": "RELEASE-NOTES",
    "content": "# Release 3.0.5 : (Security update)\n\nThis is a follow-up to the 3.0.4 release, which did not completely fix\nissue #3374. Users should upgrade to 3.0.5.\n\n * The JSONRPC interface is password protected\n * JSONRPC commands are disabled if the GUI is running, except 'ping',\n   which is used to determine if a GUI is already running\n\n\n# Release 3.0.4 : (Security update)\n\n * Fix a vulnerability caused by Cross-Origin Resource Sharing (CORS)\n   in the JSONRPC interface. Previous versions of Electrum are\n   vulnerable to port scanning and deanonimization attacks from\n   malicious websites. Wallets that are not password-protected are\n   vulnerable to theft.\n * Bundle QR scanner with Android app\n * Minor bug fixes\n\n# Release 3.0.3\n  * Qt GUI: sweeping now uses the Send tab, allowing fees to be set\n  * Windows: if using the installer binary, there is now a separate shortcut\n    for \"Electrum Testnet\"\n  * Digital Bitbox: added suport for p2sh-segwit\n  * OS notifications for incoming transactions\n  * better transaction size estimation:\n    - fees for segwit txns were somewhat underestimated (#3347)\n    - some multisig txns were underestimated\n    - handle uncompressed pubkeys\n  * fix #3321: testnet for Windows binaries\n  * fix #3264: Ledger/dbb signing on some platforms\n  * fix #3407: KeepKey sending to p2sh output\n  * other minor fixes and usability improvements\n\n# Release 3.0.2\n  * Android: replace requests tab with address tab, with access to\n    private keys\n  * sweeping minikeys: search for both compressed and uncompressed\n    pubkeys\n  * fix wizard crash when attempting to reset Google Authenticator\n  * fix #3248: fix Ledger+segwit signing\n  * fix #3262: fix SSL payment request signing\n  * other minor fixes.\n\n# Release 3.0.1\n  * minor bug and usability fixes\n\n# Release 3.0 - Uncanny Valley (November 1st, 2017)\n\n  * The project was migrated to Python3 and Qt5. Python2 is no longer\n    supported. If you cloned the source repository, you will need to\n    run \"python3 setup.py install\" in order to install the new\n    dependencies.\n\n  * Segwit support: \n\n    - Native segwit scripts are supported using a new type of\n      seed. The version number for segwit seeds is 0x100. The install\n      wizard will not create segwit seeds by default; users must\n      opt-in with the segwit option.\n\n    - Native segwit scripts are represented using bech32 addresses,\n      following BIP173. Please note that BIP173 is still in draft\n      status, and that other wallets/websites may not support\n      it. Thus, you should keep a non-segwit wallet in order to be\n      able to receive bitcoins during the transition period. If BIP173\n      ends up being rejected or substantially modified, your wallet\n      may have to be restored from seed. This will not affect funds\n      sent to bech32 addresses, and it will not affect the capacity of\n      Electrum to spend these funds.\n\n    - Segwit scripts embedded in p2sh are supported with hardware\n      wallets or bip39 seeds. To create a segwit-in-p2sh wallet,\n      trezor/ledger users will need to enter a BIP49 derivation path.\n\n    - The BIP32 master keys of segwit wallets are serialized using new\n      version numbers. The new version numbers encode the script type,\n      and they result in the following prefixes:\n\n         * xpub/xprv : p2pkh or p2sh\n         * ypub/yprv : p2wpkh-in-p2sh\n         * Ypub/Yprv : p2wsh-in-p2sh\n         * zpub/zprv : p2wpkh\n         * Zpub/Zprv : p2wsh\n\n      These values are identical for mainnet and testnet; tpub/tprv\n      prefixes are no longer used in testnet wallets.\n\n    - The Wallet Import Format (WIF) is similarly extended for segwit\n      scripts. After a base58-encoded key is decoded to binary, its\n      first byte encodes the script type:\n\n         * 128 + 0: p2pkh\n         * 128 + 1: p2wpkh\n         * 128 + 2: p2wpkh-in-p2sh\n         * 128 + 5: p2sh\n         * 128 + 6: p2wsh\n         * 128 + 7: p2wsh-in-p2sh\n\n      The distinction between p2sh and p2pkh in private key means that\n      it is not possible to import a p2sh private key and associate it\n      to a p2pkh address.\n\n  * A new version of the Electrum protocol is required by the client\n    (version 1.1). Servers using older versions of the protocol will\n    not be displayed in the GUI.\n\n  * By default, transactions are time-locked to the height of the\n    current block. Other values of locktime may be passed using the\n    command line.\n\n\n# Release 2.9.3\n  * fix configuration file issue #2719\n  * fix ledger signing of non-RBF transactions\n  * disable 'spend confirmed only' option by default\n\n# Release 2.9.2\n  * force headers download if headers file is corrupted\n  * add websocket to windows builds\n\n# Release 2.9.1\n  * fix initial headers download\n  * validate contacts on import\n  * command-line option for locktime\n\n# Release 2.9 - Independence (July 27th, 2017)\n  * Multiple Chain Validation: Electrum will download and validate\n    block headers sent by servers that may follow different branches\n    of a fork in the Bitcoin blockchain. Instead of a linear sequence,\n    block headers are organized in a tree structure. Branching points\n    are located efficiently using binary search. The purpose of MCV is\n    to detect and handle blockchain forks that are invisible to the\n    classical SPV model.\n  * The desired branch of a blockchain fork can be selected using the\n    network dialog. Branches are identified by the hash and height of\n    the diverging block. Coin splitting is possible using RBF\n    transaction (a tutorial will be added).\n  * Multibit support: If the user enters a BIP39 seed (or uses a\n    hardware wallet), the full derivation path is configurable in the\n    install wizard.\n  * Option to send only confirmed coins\n  * Qt GUI:\n    - Network dialog uses tabs and gets updated by network events.\n    - The gui tabs use icons\n  * Kivy GUI:\n    - separation between network dialog and wallet settings dialog.\n    - option for manual server entry\n    - proxy configuration\n  * Daemon: The wallet password can be passed as parameter to the\n    JSONRPC API.\n  * Various other bugfixes and improvements.\n\n\n# Release 2.8.3\n  * Fix crash on reading older wallet formats.\n  * TrustedCoin: remove pay-per-tx option\n\n# Release 2.8.2\n  * show paid invoices in history tab\n  * improve CPFP dialog\n  * fixes for trezor, keepkey\n  * other minor bugfixes\n\n# Release 2.8.1\n  * fix Digital Bitbox plugin\n  * fix daemon jsonrpc\n  * fix trustedcoin wallet creation\n  * other minor bugfixes\n\n# Release 2.8.0 (March 9, 2017)\n  * Wallet file encryption using ECIES: A keypair is derived from the\n    wallet password. Once the wallet is decrypted, only the public key\n    is retained in memory, in order to save the encrypted file.\n  * The daemon requires wallets to be explicitly loaded before\n    commands can use them. Wallets can be loaded using: 'electrum\n    daemon load_wallet [-w path]'. This command will require a\n    password if the wallet is encrypted.\n  * Invoices and contacts are stored in the wallet file and are no\n    longer shared between wallets. Previously created invoices and\n    contacts files may be imported from the menu.\n  * Fees improvements:\n    - Dynamic fees are enabled by default.\n    - Child Pays For Parent (CPFP) dialog in the GUI.\n    - RBF is automatically proposed for low fee transactions.\n  * Support for Segregated Witness (testnet only).\n  * Support for Digital Bitbox hardware wallet.\n  * The GUI shows a blue icon when connected using a proxy.\n\n# Release 2.7.18\n  * enforce https on exchange rate APIs\n  * use hardcoded list of exchanges\n  * move 'Freeze' menu to Coins (utxo) tab\n  * various bugfixes\n\n# Release 2.7.17\n  * fix a few minor regressions in the Qt GUI\n\n# Release 2.7.16\n  * add Testnet support (fix #541)\n  * allow daemon to be launched in the foreground (fix #1873)\n  * Qt: use separate tabs for addresses and UTXOs\n  * Qt: update fee slider with a network callback\n  * Ledger: new ui and mobile 2fa validation (neocogent)\n\n# Release 2.7.15\n  * Use fee slider for both static and dynamic fees.\n  * Add fee slider to RBF dialog (fix #2083).\n  * Simplify fee preferences.\n  * Critical: Fix password update issue (#2097). This bug prevents\n    password updates in multisig and 2FA wallets. It may also cause\n    wallet corruption if the wallet contains several master private\n    keys (such as 2FA wallets that have been restored from\n    seed). Affected wallets will need to be restored again.\n\n# Release 2.7.14\n  * Merge exchange_rate plugin with main code\n  * Faster synchronization and transaction creation\n  * Fix bugs #2096, #2016\n\n# Release 2.7.13\n  * fix message signing with imported keys\n  * add size to transaction details window\n  * move plot plugin to main code\n  * minor bugfixes\n\n# Release 2.7.12\n  various bugfixes\n\n# Release 2.7.11\n  * fix offline signing (issue #195)\n  * fix android crashes caused by threads\n\n# Release 2.7.10\n  * various fixes for hardware wallets\n  * improve fee bumping\n  * separate sign and broadcast buttons in Qt tx dialog\n  * allow spaces in private keys\n\n# Release 2.7.9\n  * Fix a bug with the ordering of pubkeys in recent multisig wallets.\n    Affected wallets will regenerate their public keys when opened for\n    the first time. This bug does not affect address generation.\n  * Fix hardware wallet issues #1975, #1976\n\n# Release 2.7.8\n  * Fix a bug with fee bumping\n  * Fix crash when parsing request (issue #1969)\n\n# Release 2.7.7\n  * Fix utf8 encoding bug with old wallet seeds (issue #1967)\n  * Fix delete request from menu (isue #1968)\n\n# Release 2.7.6\n * Fixes a critical bug with imported private keys (issue #1966). Keys\n   imported in Electrum 2.7.x were not encrypted, even if the wallet\n   had a password. If you imported private keys using Electrum 2.7.x,\n   you will need to import those keys again. If you imported keys in\n   2.6 and converted with 2.7.x, you don't need to do anything, but\n   you still need to upgrade in order to be able to spend.\n * Wizard: Hide seed options in a popup dialog.\n\n# Release 2.7.5\n * Add number of confirmations to request status. (issue #1757)\n * In the GUI, refer to passphrase as 'seed extension'.\n * Fix bug with utf8 encoded passphrases.\n * Kivy wizard: add a dialog for seed options.\n * Kivy wizard: add current word to suggestions, because some users\n   don't see the space key.\n\n# Release 2.7.4\n * Fix private key import in wizard\n * Fix Ledger display (issue #1961)\n * Fix old watching-only wallets (issue #1959)\n * Fix Android compatibility (issue #1947)\n\n# Release 2.7.3\n * fix Trezor and Keepkey support in Windows builds\n * fix sweep private key dialog\n * minor fixes: #1958, #1959\n\n# Release 2.7.2\n * fix bug in password update (issue #1954)\n * fix fee slider (issue #1953)\n\n# Release 2.7.1\n * fix wizard crash with old seeds\n * fix issue #1948: fee slider\n\n# Release 2.7.0 (Oct 2 2016)\n\n * The wallet file format has been upgraded. This upgrade is not\n   backward compatible, which means that a wallet upgraded to the 2.7\n   format will not be readable by earlier versions of\n   Electrum. Multiple accounts inside the same wallet are not\n   supported in the new format; the Qt GUI will propose to split any\n   wallet that has several accounts. Make sure that you have saved\n   your seed phrase before you upgrade Electrum.\n * This version introduces a separation between wallets types and\n   keystores types. 'Wallet type' defines the type of Bitcoin contract\n   used in the wallet, while 'keystore type' refers to the method used\n   to store private keys. Therefore, so-called 'hardware wallets' will\n   be referred to as 'hardware keystores'.\n * Hardware keystores:\n   - The Ledger Nano S is supported.\n   - Hardware keystores can be used as cosigners in multi-signature\n     wallets.\n   - Multiple hardware cosigners can be used in the same multisig\n     wallet. One icon per keystore is displayed in the satus bar. Each\n     connected device will co-sign the transaction.\n * Replace-By-Fee: RBF transactions are supported in both Qt and\n   Android. A warning is displayed in the history for transactions\n   that are replaceable, have unconfirmed parents, or that have very\n   low fees.\n * Dynamic fees: Dynamic fees are enabled by default. A slider allows\n   the user to select the expected confirmation time of their\n   transaction. The expected confirmation times of incoming\n   transactions is also displayed in the history.\n * The install wizards of Qt and Kivy have been unified.\n * Qt GUI (Desktop):\n   - A fee slider is visible in the in send tab\n   - The Address tab is hidden by default, can be shown with Ctrl-A\n   - UTXOs are displayed in the Address tab\n * Kivy GUI (Android):\n   - The GUI displays the complete transaction history.\n   - Multisig wallets are supported.\n   - Wallets can be created and deleted in the GUI.\n * Seed phrases can be extended with a user-chosen passphrase. The\n   length of seed phrases is standardized to 12 words, using 132 bits\n   of entropy (including 2FA seeds). In the wizard, the type of the\n   seed is displayed in the seed input dialog.\n * TrustedCoin users can request a reset of their Google Authenticator\n   account, if they still have their seed.\n\n\n# Release 2.6.4 (bugfixes)\n * fix coinchooser bug (#1703)\n * fix daemon JSONRPC (#1731)\n * fix command-line broadcast (#1728)\n * QT: add colors to labels\n\n# Release 2.6.3 (bugfixes)\n * fix command line parsing of transactions\n * fix signtransaction --privkey (#1715)\n\n# Release 2.6.2 (bugfixes)\n * fix Trustedcoin restore from seed (bug #1704)\n * small improvements to kivy GUI\n\n# Release 2.6.1 (bugfixes)\n * fix broadcast command (bug #1688)\n * fix tx dialog (bug #1690)\n * kivy: support old-type seed phrases in wizard\n\n# Release 2.6\n * The source code is relicensed under the MIT Licence\n * First official release of the Kivy GUI, with android APK\n * The old 'android' and 'gtk' GUIs are deprecated\n * Separation between plugins and GUIs\n * The command line uses jsonrpc to communicate with the daemon\n * New command: 'notify <address> <url>'\n * Alternative coin selection policy, designed to help preserve user\n   privacy. Enable it by setting the Coin Selection preference to\n   Privacy.\n * The install wizard has been rewritten and improved\n * Support minikeys as used in Casascius coins for private key import\n   and sweeping\n * Much improved support for TREZOR and KeepKey devices:\n   - full device information display\n   - initialize a new or wiped device in 4 ways:\n     1) device generates a new wallet\n     2) you enter a seed\n     3) you enter a BIP39 mnemonic to generate the seed\n     4) you enter a master private key\n   - KeepKey secure seed recovery (KeepKey only)\n   - change / set / disable PIN\n   - set homescreen (TREZOR only)\n   - set a session timeout.  Once a session has timed out, further use\n     of the device requires your PIN and passhphrase to be re-entered\n   - enable / disable passphrases\n   - device wipe\n   - multiple device support\n\n# Release 2.5.4\n * increase MIN_RELAY_TX_FEE to avoid dust transactions\n\n# Release 2.5.3 (bugfixes)\n * installwizard: do not allow direct copy-paste of the seed\n * installwizard: fix bug #1531 (starting offline)\n\n# Release 2.5.2 (bugfixes)\n * fix bug #1513 (client tries to broadcast transaction while not connected)\n * fix synchronization bug (#1520)\n * fix command line bug (#1494)\n * fixes for exchange rate plugin\n\n# Release 2.5.1 (bugfixes)\n * signatures in transactions were still using the old class\n * make sure that setup.py uses python2\n * fix wizard crash with trustedcoin plugin\n * fix socket infinite loop\n * fix history bug #1479\n\n# Release 2.5\n * Low-S values are used in signatures (BIP 62).\n * The Kivy GUI has been merged into master.\n * The Qt GUI supports multiple windows in the same process. When a\n   new Electrum instance is started, it checks for an already running\n   Electrum process, and connects to it.\n * The network layer uses select(), so all server communication is\n   handled by a single thread. Moreover, the synchronizer, verifier,\n   and exchange rate plugin now run as separate jobs within the\n   networking thread instead of as their own threads.\n * Plugins are revamped, particularly the exchange rate plugin.\n\n# Release 2.4.4\n * Fix bug with TrustedCoin plugin\n\n# Release 2.4.3\n * Support for KeepKey hardware wallet\n * Simplified Chinese wordlist\n * Minor bugfixes and GUI tweaks\n\n# Release 2.4.2\n * Command line can read arguments from stdin (pipe)\n * Speedup fee computation for large transactions\n * Various bugfixes\n\n# Release 2.4.1\n * Use ssl.PROTOCOL_TLSv1\n * Fix DNSSEC issues with ECDSA signatures\n * Replace TLSLite dependency with minimal RSA implementation\n * Dynamic Fees: using estimatefee value returned by server\n * Various GUI improvements\n\n# Release 2.4\n * Payment to DNS names storing a Bitcoin addresses (OpenAlias) is\n   supported directly, without activating a plugin. The verification\n   uses DNSSEC.\n * The DNSSEC verification code was rewritten. The previous code,\n   which was part of the OpenAlias plugin, is vulnerable and should\n   not be trusted (Electrum 2.0 to 2.3).\n * Payment requests can be signed using Bitcoin addresses stored\n   in DNS (OpenAlias). The identity of the requestor is verified using\n   DNSSEC.\n * Payment requests signed with OpenAlias keys can be shared as\n   bitcoin: URIs, if they are simple (a single address-type\n   output). The BIP21 URI scheme is extended with 'name', 'sig',\n   'time', 'exp'.\n * Arbitrary m-of-n multisig wallets are supported (n<=15).\n * Multisig transactions can be signed with TREZOR. When you create\n   the multisig wallet, just enter the xpub of your existing TREZOR\n   wallet.\n * Transaction fees set manually in the GUI are retained, including\n   when the user uses the '!' shortcut.\n * New 'email' plugin, that enables sending and receiving payment\n   requests by email.\n * The daemon supports Websocket notifications of payments.\n\n# Release 2.3.3\n * fix proxy settings (issue #1309)\n * improvements to the transaction dialog:\n    - request password after showing transaction\n    - show change addresses in yellow color\n\n# Release 2.3.2\n * minor bugfixes\n * updated ledger plugin\n * sort inputs/outputs lexicographically (BIP-LI01)\n\n# Release 2.3.1\n * patch a bug with payment requests\n\n# Release 2.3\n * Improved logic for the network layer.\n * More efficient coin selection. Spend oldest coins first, and\n   minimize the number of transaction inputs.\n * Plugins are loaded independently of the GUI. As a result, Openalias,\n   TrustedCoin and TREZOR wallets can be used with the command\n   line. Example: 'electrum payto <openalias> <amount>'\n * The command line has been refactored:\n  - Arguments are parsed with argparse.\n  - The inline help includes a description of options.\n  - Some commands have been renamed. Notably, 'mktx' and 'payto' have\n    been merged into a single command, with a --broadcast option.\n   Type 'electrum --help' for a complete overview.\n * The command line accepts the '!' syntax to send the maximum\n   amount available. It can be combined with the '--from' option.\n   Example: 'payto <destination> ! --from <from_address>'\n * The command line also accepts a '?' shortcut for private keys\n   arguments, that triggers a prompt.\n * Payment requests can be managed with the command line, using the\n   following commands: 'addrequest', 'rmrequest', 'listrequests'.\n   Payment requests can be signed with a SSL certificate, and published\n   as bip70 files in a public web directory. To see the relevant\n   configuration variables, type 'electrum addrequest --help'\n * Commands can be called with jsonrpc, using the 'jsonrpc' gui. The\n   jsonrpc interface may be called by php.\n\n# Release 2.2\n * Show amounts (thousands separators and decimal point)\n   according to locale in GUI\n * Show unmatured coins in balance\n * Fix exchange rates plugin\n * Network layer: refactoring and fixes\n\n# Release 2.1.1\n * patch a bug that prevents new wallet creation.\n * fix connection issue on osx binaries\n\n# Release 2.1\n * Faster startup, thanks to the following optimizations:\n   1. Transaction input/outputs are cached in the wallet file\n   2. Fast X509 certificate parser, not using pyasn1 anymore.\n   3. The Label Sync plugin only requests modified labels.\n * The 'Invoices' and 'Send' tabs have been merged.\n * Contacts are stored in a separate file, shared between wallets.\n * A Search Box is available in the GUI (Ctrl-S)\n * Payment requests have an expiration date and can be exported to\n   BIP70 files.\n * file: scheme support in BIP72 URIs: \"bitcoin:?r=file:///...\"\n * Own addresses are shown in green in the Transaction dialog.\n * Address History dialog.\n * The OpenAlias plugin was improved.\n * Various bug fixes and GUI improvements.\n * A new LabelSync backend is being used an import of the old\n   database was made but since the release came later it's\n   recommended that you do a full push when you upgrade.\n\n# Release 2.0.4 - Minor GUI improvements\n * The password dialog will ask for password again if the user enters\n   a wrong password\n * The Master Public Key dialog displays which keys belong to the\n   wallet, and which are cosigners\n * The transaction dialog will ask to save unsaved transaction\n   received from cosigner pool, when user clicks on 'Close'\n * The multisig restore dialog accepts xprv keys.\n * The network daemon must be started explicitly before using commands\n   that require a connection\n   Example:\n     electrum daemon start\n     electrum getaddressunspent <addr>\n     electrum daemon status\n     electrum daemon stop\n   If a daemon is running, the GUI will use it.\n\n# Release 2.0.3 - bugfixes and minor GUI improvements\n * Do not use daemon threads (fix #960)\n * Add a zoom button to receive tab\n * Add exchange rate conversion to receive tab\n * Use Tor's default port number in default proxy config\n\n# Release 2.0.2 - bugfixes\n * Fix transaction sweep (#1066)\n * Fix thread timing bug (#1054)\n\n# Release 2.0.1 - bugfixes\n * Fix critical bug in TREZOR address derivation: passphrases were not\n   NFKD normalized. TREZOR users who created a wallet protected by a\n   passphrase containing utf-8 characters with diacritics are\n   affected. These users will have to open their wallet with version\n   2.0 and to move their funds to a new wallet.\n * Use a file socket for the daemon (fixes network dialog issues)\n * Fix crash caused by QR scanner icon when zbar not installed.\n * Fix CosignerPool plugin\n * Label Sync plugin: Fix label sharing between multisig wallets\n\n\n# Release 2.0\n\n * Before you upgrade, make sure you have saved your wallet seed on\n   paper.\n\n * Documentation is now hosted on a wiki: http://electrum.orain.org\n\n * New seed derivation method (not compatible with BIP39). The seed\n   phrase includes a version number, that refers to the wallet\n   structure. The version number also serves as a checksum, and it\n   will prevent the import of seeds from incompatible wallets. Old\n   Electrum seeds are still supported.\n\n * New address derivation (BIP32). Standard wallets are single account\n   and use a gap limit of 20.\n\n * Support for Multisig wallets using parallel BIP32 derivations and\n   P2SH addresses (\"2 of 2\", \"2 of 3\").\n\n * Compact serialization format for unsigned or partially signed\n   transactions, that includes the BIP32 master public key and\n   derivation needed to sign inputs. Serialized transactions can be\n   sent to cosigners or to cold storage using QR codes (using Andreas\n   Schildbach's base 43 idea).\n\n * Support for BIP70 payment requests:\n   - Verification of the chain of signatures uses tlslite.\n   - In the GUI, payment requests are shown in the 'Invoices' tab.\n\n * Support for hardware wallets: TREZOR (SatoshiLabs) and Btchip (Ledger).\n\n * Two-factor authentication service by TrustedCoin. This service uses\n   \"2 of 3\" multisig wallets and Google Authenticator. Note that\n   wallets protected by this service can be deterministically restored\n   from seed, without Trustedcoin's server.\n\n * Cosigner Pool plugin: encrypted communication channel for multisig\n   wallets, to send and receive partially signed transactions.\n\n * Audio Modem plugin: send and receive transactions by sound.\n\n * OpenAlias plugin: send bitcoins to aliases verified using DNSSEC.\n\n * New 'Receive' tab in the GUI:\n   - create and manage payment requests, with QR Codes\n   - the former 'Receive' tab was renamed to 'Addresses'\n   - the former Point of Sale plugin is replaced by a resizeable\n     window that pops up if you click on the QR code\n\n * The 'Send' tab in the Qt GUI supports transactions with multiple\n   outputs, and raw hexadecimal scripts.\n\n * The GUI can connect to the Electrum daemon: \"electrum -d\" will\n   start the daemon if it is not already running, and the GUI will\n   connect to it. The daemon can serve several clients. It times out\n   if no client uses if for more than 5 minutes.\n\n * The install wizard can be used to import addresses or private\n   keys. A watching-only wallet is created by entering a list of\n   addresses in the wizard dialog.\n\n * New file format: Wallets files are saved as JSON. Note that new\n   wallet files cannot be read by older versions of Electrum. Old\n   wallet files will be converted to the new format; this operation\n   may take some time, because public keys will be derived for each\n   address of your wallet.\n\n * The client accepts servers with a CA-signed SSL certificate.\n\n * ECIES encrypt/decrypt methods, availabe in the GUI and using\n   the command line:\n      encrypt <pubkey> <message>\n      decrypt <pubkey> <message>\n\n * The Android GUI has received various updates and it is much more\n   stable. Another script was added to Android, called Authenticator,\n   that works completely offline: it reads an unsigned transaction\n   shown as QR code, signs it and shows the result as a QR code.\n\n\n# Release 1.9.8\n\n* Electrum servers were upgraded to version 0.9. The new server stores\n  a Patrica tree of all UTXOs, an idea proposed by Alan Reiner in the\n  bitcointalk forum. This property allows the client to directly\n  request the balance of any address. The new commands are:\n     1. getaddressbalance <address>\n     2. getaddressunspent <address>\n     3. getutxoaddress <txid> <pos>\n\n* Command-line commands that require a connection to the network spawn\n  a daemon, that remains connected and handles subsequent\n  commands. The daemon terminates itself if it remains unused for more\n  than one minute. The purpose of this is to make scripting more\n  efficient. For example, a bash script using many electrum commands\n  will open only one connection.\n\n# Release 1.9.7\n* Fix for offline signing\n* Various bugfixes\n* GUI usability improvements\n* Coinbase Buyback plugin\n\n# Release 1.9.6\n* During wallet creation, do not write seed to disk until it is encrypted.\n* Confirmation dialog if the transaction fee is higher than 1mBTC.\n* bugfixes\n\n# Release 1.9.5\n\n* Coin control: select addresses to send from\n* Put addresses that have been used in a minimized section (Qt GUI)\n* Allow non ascii chars in passwords\n\n\n# Release 1.9.4\nbugfixes: offline transactions\n\n# Release 1.9.3\nbugfixes: connection problems, transactions staying unverified\n\n# Release 1.9.2\n* fix a syntax error\n\n# Release 1.9.1\n* fix regression with --offline mode\n* fix regression with --portable mode: use a dedicated directory\n\n# Release 1.9\n\n* The client connects to multiple servers in order to retrieve block headers and find the longest chain\n* SSL certificate validation (to prevent MITM)\n* Deterministic signatures (RFC 6979)\n* Menu to create/restore/open wallets\n* Create transactions with multiple outputs from CSV (comma separated values)\n* New text gui: stdio\n* Plugins are no longer tied to the qt GUI, they can reach all GUIs\n* Proxy bugs have been fixed\n\n\n# Release 1.8.1\n\n* Notification option when receiving new tranactions\n* Confirm dialogue before sending large amounts\n* Alternative datafile location for non-windows systems\n* Fix offline wallet creation\n* Remove enforced tx fee\n* Tray icon improvements\n* Various bugfixes\n\n\n# Release 1.8\n\n* Menubar in classic gui\n* Updated the QR Code plugin to enable offline/online wallets to transmit unsigned/signed transactions via QR code.\n* Fixed bug where never-confirmed transactions prevented further spending\n\n\n# Release 1.7.4\n\n* Increase default fee\n* fix create and restore in command line\n* fix verify message in the gui\n\n\n# Release 1.7.3:\n\n* Classic GUI can display amounts in mBTC\n* Account selector in the classic GUI\n* Changed the way the portable flag uses without supplying a -w argument\n* Classic GUI asks users to enter their seed on wallet creation\n\n\n# Release 1.7.2:\n\n* Transactions that are in the same block are displayed in chronological order in the history.\n* The client computes transaction priority and rejects zero-fee transactions that need a fee.\n* The default fee was lowered to 200 uBTC per kb.\n* Due to an internal format change, your history may be pruned when\n  you open your wallet for the first time after upgrading to 1.7.2. If\n  this is the case, please visit a full server to restore your full\n  history. You will only need to do that once.\n\n\n# Release 1.7.1:  bugfixes.\n\n\n# Release 1.7\n\n* The Classic GUI can be extended with plugins. Developers who want to\nadd new features or third-party services to Electrum are invited to\nwrite plugins. Some previously existing and non-essential features of\nElectrum (point-of-sale mode, qrcode scanner) were removed from the\ncore and are now available as plugins.\n\n* The wallet waits for 2 confirmations before creating new\naddresses. This makes recovery from seed more robust. Note that it\nmight create unwanted gaps if you use Electrum 1.7 together with older\nversions of Electrum.\n\n* An interactive Python console replaces the 'Wall' tab. The provided\npython environment gives users access to the wallet and gui. Most\nelectrum commands are available as python function in the\nconsole. Custom scripts an be loaded with a \"run(filename)\"\ncommand. Tab-completions are available.\n\n* The location of the Electrum folder in Windows changed from\nLOCALAPPDATA to APPDATA. Discussion on this topic can be found here:\nhttps://bitcointalk.org/index.php?topic=144575.0\n\n* Private keys can be exported from within the classic GUI:\n  For a single address, use the address menu (right-click).\n  To export the keys of your entire wallet, use the settings dialog (import/export tab).\n\n* It is possible to create, sign and redeem multisig transaction using the\ncommand line interface.  This is made possible by the following new commands:\n    dumpprivkey, listunspent, createmultisig, createrawtransaction, decoderawtransaction, signrawtransaction\nThe syntax of these commands is similar to their bitcoind counterpart.\nFor an example, see Gavin's tutorial: https://gist.github.com/gavinandresen/3966071\n\n* Offline wallets now work in a way similar to Armory:\n  1. user creates an unsigned transaction using the online (watching-only) wallet.\n  2. unsigned transaction is copied to the offline computer, and signed by the offline wallet.\n  3. signed transaction is copied to the online computer, broadcasted by the online client.\n  4. All these steps can be done via the command line interface or the classic GUI.\n\n* Many command line commands have been renamed in order to make the syntax consistent with bitcoind.\n\n# Release 1.6.2\n\n== Classic GUI\n* Added new version notification\n\n# Release 1.6.1 (11-01-2013)\n\n== Core\n* It is now possible to restore a wallet from MPK (this will create a watching-only wallet)\n* A switch button allows to easily switch between Lite and Classic GUI.\n\n== Classic GUI\n* Seed and MPK help dialogs were rewritten\n* Point of Sale: requested amounts can be expressed in other currencies and are converted to bitcoin.\n\n== Lite GUI\n* The receiving button was removed in favor of a menu item to keep it consistent with the history toggle.\n\n# Release 1.6.0 (07-01-2013)\n\n== Core\n* (Feature) Add support for importing, signing and verifiying compressed keys\n* (Feature) Auto reconnect to random server on disconnect\n* (Feature) Ultimate fallback to HTTP port 80 if TCP doesn't work on any server\n* (Bug) Under rare circumstances changing password with incorrect password could damage wallet\n\n== Lite GUI\n* (Chore) Use blockchain.info for exchange rate data\n* (Feature) added currency conversion for BRL, CNY, RUB\n* (Feature) Saraha theme\n* (Feature) csv import/export for transactions including labels\n\n== Classic GUI\n* (Chore) pruning servers now called \"p\", full servers \"f\" to avoid confusion with terms\n* (Feature) Debits in history shown in red\n* (Feature) csv import/export for transactions including labels\n\n# Release 1.5.8 (02-01-2013)\n\n== Core\n* (Bug) Fix pending address balance on received coins for pruning servers\n* (Bug) Fix history command line option to show output again (regression by SPV)\n* (Chore) Add timeout to blockchain headers file download by HTTP\n* (Feature) new option: -L, --language: default language used in GUI.\n\n== Lite GUI\n* (Bug) Sending to auto-completed contacts works again\n* (Chore) Added version number to title bar\n\n== Classic GUI\n* (Feature) Language selector in options.\n\n# Release 1.5.7 (18-12-2012)\n\n== Core\n* The blockchain headers file is no longer included in the packages, it is downloaded on startup.\n* New command line option: -P or --portable, for portable wallets. With this flag, all preferences are saved to the wallet file, and the blockchain headers file is in the same directory as the wallet\n\n== Lite GUI\n* (Feature) Added the ability to export your transactions to a CSV file.\n* (Feature) Added a label dialog after sending a transaction.\n* (Feature) Reworked receiving addresses; instead of a random selection from one of your receiving addresses a new widget will show listing unused addresses.\n* (Chore)   Removed server selection. With all the new server options a simple menu item does not suffice anymore.\n"
  },
  {
    "path": "app.fil",
    "content": "gui/qt/__init__.py\ngui/qt/main_window.py\ngui/qt/history_list.py\ngui/qt/contact_list.py\ngui/qt/invoice_list.py\ngui/qt/request_list.py\ngui/qt/installwizard.py\ngui/qt/network_dialog.py\ngui/qt/password_dialog.py\ngui/qt/util.py\ngui/qt/seed_dialog.py\ngui/qt/transaction_dialog.py\ngui/qt/address_dialog.py\ngui/qt/qrcodewidget.py\ngui/qt/qrtextedit.py\ngui/qt/qrwindow.py\ngui/kivy/main.kv\ngui/kivy/main_window.py\ngui/kivy/uix/dialogs/__init__.py\ngui/kivy/uix/dialogs/fee_dialog.py\ngui/kivy/uix/dialogs/installwizard.py\ngui/kivy/uix/dialogs/settings.py\ngui/kivy/uix/dialogs/wallets.py\ngui/kivy/uix/ui_screens/history.kv\ngui/kivy/uix/ui_screens/receive.kv\ngui/kivy/uix/ui_screens/send.kv\nplugins/labels/qt.py\nplugins/trezor/qt.py\nplugins/virtualkeyboard/qt.py\n"
  },
  {
    "path": "brewfile",
    "content": "tap \"caskroom/cask\"\nbrew \"python3\"\nbrew  \"protobuf\"\n# brew \"zbar\" -\nbrew \"gmp\"\n# required by gmpy\nbrew \"gettext\"\ntap \"brewsci/science\"\nbrew \"matplotlib\"\n# required for matplotlib used in Plot History\nbrew \"libusb-compat\"\n# ledger wallet\n"
  },
  {
    "path": "build-docker.sh",
    "content": "#!/bin/bash\n\ndocker build -t electrum-btcp:latest .\n"
  },
  {
    "path": "clean.sh",
    "content": "#!/bin/bash\n\nsudo rm -rf build/\nsudo rm -rf dist/\nsudo rm -rf __pycache__\nsudo rm -rf Electrum_BTCP.egg-info\nsudo rm -rf /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/Electrum_BTCP-*\n\n"
  },
  {
    "path": "config",
    "content": "{\n    \"server\": \"35.224.186.7:50001:t\" \n }\n"
  },
  {
    "path": "contrib/build-wine/README.md",
    "content": "Windows Binary Builds\n=====================\n\nThese scripts can be used for cross-compilation of Windows Electrum executables from Linux/Wine.\nProduced binaries are deterministic so you should be able to generate binaries that match the official releases.\n\n\nUsage:\n\n\n1. Install the following dependencies:\n\n - dirmngr\n - gpg\n - Wine (>= v2)\n\n\nFor example:\n\n\n```\n$ sudo apt-get install wine-development dirmngr gnupg2\n$ sudo ln -sf /usr/bin/wine-development /usr/local/bin/wine\n$ wine --version\n wine-2.0 (Debian 2.0-3+b2)\n```\n\nor\n\n```\n$ pacman -S wine gnupg\n$ wine --version\n wine-2.21\n```\n\n2. Make sure `/opt` is writable by the current user.\n3. Run `build.sh`.\n4. The generated binaries are in `./dist`.\n"
  },
  {
    "path": "contrib/build-wine/build-electrum-git.sh",
    "content": "#!/bin/bash\n\nNAME_ROOT=electrum\nPYTHON_VERSION=3.5.4\n\n# These settings probably don't need any change\nexport WINEPREFIX=/opt/wine64\nexport PYTHONDONTWRITEBYTECODE=1\nexport PYTHONHASHSEED=22\n\nPYHOME=c:/python$PYTHON_VERSION\nPYTHON=\"wine $PYHOME/python.exe -OO -B\"\n\n\n# Let's begin!\ncd `dirname $0`\nset -e\n\ncd tmp\n\nfor repo in electrum electrum-locale electrum-icons; do\n    if [ -d $repo ]; then\n\tcd $repo\n\tgit pull\n\tgit checkout master\n\tcd ..\n    else\n\tURL=https://github.com/spesmilo/$repo.git\n\tgit clone -b master $URL $repo\n    fi\ndone\n\npushd electrum-locale\nfor i in ./locale/*; do\n    dir=$i/LC_MESSAGES\n    mkdir -p $dir\n    msgfmt --output-file=$dir/electrum.mo $i/electrum.po || true\ndone\npopd\n\npushd electrum\nif [ ! -z \"$1\" ]; then\n    git checkout $1\nfi\n\nVERSION=`git describe --tags`\necho \"Last commit: $VERSION\"\nfind -exec touch -d '2000-11-11T11:11:11+00:00' {} +\npopd\n\nrm -rf $WINEPREFIX/drive_c/electrum\ncp -r electrum $WINEPREFIX/drive_c/electrum\ncp electrum/LICENCE .\ncp -r electrum-locale/locale $WINEPREFIX/drive_c/electrum/lib/\ncp electrum-icons/icons_rc.py $WINEPREFIX/drive_c/electrum/gui/qt/\n\n# Install frozen dependencies\n$PYTHON -m pip install -r ../../requirements.txt\n\npushd $WINEPREFIX/drive_c/electrum\n$PYTHON setup.py install\npopd\n\ncd ..\n\nrm -rf dist/\n\n# build standalone and portable versions\nwine \"C:/python$PYTHON_VERSION/scripts/pyinstaller.exe\" --noconfirm --ascii --name $NAME_ROOT-$VERSION -w deterministic.spec\n\n# set timestamps in dist, in order to make the installer reproducible\npushd dist\nfind -exec touch -d '2000-11-11T11:11:11+00:00' {} +\npopd\n\n# build NSIS installer\n# $VERSION could be passed to the electrum.nsi script, but this would require some rewriting in the script iself.\nwine \"$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe\" /DPRODUCT_VERSION=$VERSION electrum.nsi\n\ncd dist\nmv electrum-setup.exe $NAME_ROOT-$VERSION-setup.exe\ncd ..\n\necho \"Done.\"\nmd5sum dist/electrum*exe\n"
  },
  {
    "path": "contrib/build-wine/build.sh",
    "content": "#!/bin/bash\n# Lucky number\nexport PYTHONHASHSEED=22\n\nif [ ! -z \"$1\" ]; then\n    to_build=\"$1\"\nfi\n\nhere=$(dirname \"$0\")\n\necho \"Clearing $here/build and $here/dist...\"\nrm $here/build/* -rf\nrm $here/dist/* -rf\n\n$here/prepare-wine.sh && \\\n$here/prepare-pyinstaller.sh && \\\n$here/prepare-hw.sh || exit 1\n\necho \"Resetting modification time in C:\\Python...\"\n# (Because of some bugs in pyinstaller)\npushd /opt/wine64/drive_c/python*\nfind -exec touch -d '2000-11-11T11:11:11+00:00' {} +\npopd\nls -l /opt/wine64/drive_c/python*\n\n$here/build-electrum-git.sh $to_build && \\\necho \"Done.\"\n"
  },
  {
    "path": "contrib/build-wine/deterministic.spec",
    "content": "# -*- mode: python -*-\r\n\r\nfrom PyInstaller.utils.hooks import collect_data_files, collect_submodules\r\n\r\nimport sys\r\nimport os\r\nfor i, x in enumerate(sys.argv):\r\n    if x == '--name':\r\n        cmdline_name = sys.argv[i+1]\r\n        break\r\nelse:\r\n    raise BaseException('no name')\r\n\r\nhome = os.getcwd()+'\\\\'\r\n\r\n# see https://github.com/pyinstaller/pyinstaller/issues/2005\r\nhiddenimports = []\r\n# hiddenimports += collect_submodules('trezorlib')\r\n# hiddenimports += collect_submodules('btchip')\r\n# hiddenimports += collect_submodules('keepkeylib')\r\n\r\ndatas = [\r\n    (home+'lib/currencies.json', 'electrum'),\r\n    (home+'lib/servers.json', 'electrum'),\r\n    (home+'lib/checkpoints.json', 'electrum'),\r\n    (home+'lib/servers_testnet.json', 'electrum'),\r\n    (home+'lib/checkpoints_testnet.json', 'electrum'),\r\n    (home+'lib/wordlist/english.txt', 'electrum/wordlist'),\r\n#    (home+'lib/locale', 'electrum/locale'),\r\n    (home+'plugins', 'electrum_plugins'),\r\n]\r\n# datas += collect_data_files('trezorlib')\r\n# datas += collect_data_files('btchip')\r\n# datas += collect_data_files('keepkeylib')\r\n\r\n# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports\r\na = Analysis([home+'electrum-btcp',\r\n              home+'gui/qt/main_window.py',\r\n              home+'gui/text.py',\r\n              home+'lib/util.py',\r\n              home+'lib/wallet.py',\r\n              home+'lib/simple_config.py',\r\n              home+'lib/bitcoin.py',\r\n              home+'lib/dnssec.py',\r\n              home+'lib/commands.py',\r\n              home+'plugins/cosigner_pool/qt.py',\r\n              home+'plugins/email_requests/qt.py',\r\n              #home+'plugins/trezor/client.py',\r\n              #home+'plugins/trezor/qt.py',\r\n              #home+'plugins/keepkey/qt.py',\r\n              #home+'plugins/ledger/qt.py',\r\n              #home+'packages/requests/utils.py'\r\n              ],\r\n             datas=datas,\r\n             #pathex=[home+'lib', home+'gui', home+'plugins'],\r\n             hiddenimports=hiddenimports,\r\n             hookspath=[])\r\n\r\n\r\n# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal\r\nfor d in a.datas:\r\n    if 'pyconfig' in d[0]: \r\n        a.datas.remove(d)\r\n        break\r\n\r\n# hotfix for #3171 (pre-Win10 binaries)\r\na.binaries = [x for x in a.binaries if not x[1].lower().startswith(r'c:\\windows')]\r\n\r\npyz = PYZ(a.pure)\r\n\r\n\r\n#####\r\n# \"standalone\" exe with all dependencies packed into it\r\n\r\nexe_standalone = EXE(\r\n    pyz,\r\n    a.scripts,\r\n    a.binaries,\r\n    a.datas, \r\n    name=os.path.join('build\\\\pyi.win32\\\\electrum-btcp', cmdline_name + \".exe\"),\r\n    debug=False,\r\n    strip=None,\r\n    upx=False,\r\n    icon=home+'icons/electrum.ico',\r\n    console=False)\r\n    # console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used\r\n\r\n# exe_portable = EXE(\r\n    # pyz,\r\n    # a.scripts,\r\n    # a.binaries,\r\n    # a.datas,\r\n    # name=os.path.join('build\\\\pyi.win32\\\\electrum', cmdline_name + \"-portable.exe\"),\r\n    # debug=False,\r\n    # strip=None,\r\n    # upx=False,\r\n    # icon=home+'icons/electrum.ico',\r\n    # console=False)\r\n\r\n# #####\r\n# # exe and separate files that NSIS uses to build installer \"setup\" exe\r\n\r\n# exe_dependent = EXE(\r\n    # pyz,\r\n    # a.scripts,\r\n    # exclude_binaries=True,\r\n    # name=os.path.join('build\\\\pyi.win32\\\\electrum', cmdline_name),\r\n    # debug=False,\r\n    # strip=None,\r\n    # upx=False,\r\n    # icon=home+'icons/electrum.ico',\r\n    # console=False)\r\n\r\n# coll = COLLECT(\r\n    # exe_dependent,\r\n    # a.binaries,\r\n    # a.zipfiles,\r\n    # a.datas,\r\n    # strip=None,\r\n    # upx=True,\r\n    # debug=False,\r\n    # icon=home+'icons/electrum.ico',\r\n    # console=False,\r\n    # name=os.path.join('dist', 'electrum'))\r\n"
  },
  {
    "path": "contrib/build-wine/electrum.nsi",
    "content": ";--------------------------------\n;Include Modern UI\n  !include \"TextFunc.nsh\" ;Needed for the $GetSize fuction. I know, doesn't sound logical, it isn't.\n  !include \"MUI2.nsh\"\n  \n;--------------------------------\n;Variables\n\n  !define PRODUCT_NAME \"Electrum\"\n  !define PRODUCT_WEB_SITE \"https://github.com/spesmilo/electrum\"\n  !define PRODUCT_PUBLISHER \"Electrum Technologies GmbH\"\n  !define PRODUCT_UNINST_KEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCT_NAME}\"\n\n;--------------------------------\n;General\n\n  ;Name and file\n  Name \"${PRODUCT_NAME}\"\n  OutFile \"dist/electrum-setup.exe\"\n\n  ;Default installation folder\n  InstallDir \"$PROGRAMFILES\\${PRODUCT_NAME}\"\n\n  ;Get installation folder from registry if available\n  InstallDirRegKey HKCU \"Software\\${PRODUCT_NAME}\" \"\"\n\n  ;Request application privileges for Windows Vista\n  RequestExecutionLevel admin\n\n  ;Specifies whether or not the installer will perform a CRC on itself before allowing an install\n  CRCCheck on\n  \n  ;Sets whether or not the details of the install are shown. Can be 'hide' (the default) to hide the details by default, allowing the user to view them, or 'show' to show them by default, or 'nevershow', to prevent the user from ever seeing them.\n  ShowInstDetails show\n  \n  ;Sets whether or not the details of the uninstall  are shown. Can be 'hide' (the default) to hide the details by default, allowing the user to view them, or 'show' to show them by default, or 'nevershow', to prevent the user from ever seeing them.\n  ShowUninstDetails show\n  \n  ;Sets the colors to use for the install info screen (the default is 00FF00 000000. Use the form RRGGBB (in hexadecimal, as in HTML, only minus the leading '#', since # can be used for comments). Note that if \"/windows\" is specified as the only parameter, the default windows colors will be used.\n  InstallColors /windows\n  \n  ;This command sets the compression algorithm used to compress files/data in the installer. (http://nsis.sourceforge.net/Reference/SetCompressor)\n  SetCompressor /SOLID lzma\n  \n  ;Sets the dictionary size in megabytes (MB) used by the LZMA compressor (default is 8 MB).\n  SetCompressorDictSize 64\n  \n  ;Sets the text that is shown (by default it is 'Nullsoft Install System vX.XX') in the bottom of the install window. Setting this to an empty string (\"\") uses the default; to set the string to blank, use \" \" (a space).\n  BrandingText \"${PRODUCT_NAME} Installer v${PRODUCT_VERSION}\" \n  \n  ;Sets what the titlebars of the installer will display. By default, it is 'Name Setup', where Name is specified with the Name command. You can, however, override it with 'MyApp Installer' or whatever. If you specify an empty string (\"\"), the default will be used (you can however specify \" \" to achieve a blank string)\n  Caption \"${PRODUCT_NAME}\"\n\n  ;Adds the Product Version on top of the Version Tab in the Properties of the file.\n  VIProductVersion 1.0.0.0\n  \n  ;VIAddVersionKey - Adds a field in the Version Tab of the File Properties. This can either be a field provided by the system or a user defined field.\n  VIAddVersionKey ProductName \"${PRODUCT_NAME} Installer\"\n  VIAddVersionKey Comments \"The installer for ${PRODUCT_NAME}\"\n  VIAddVersionKey CompanyName \"${PRODUCT_NAME}\"\n  VIAddVersionKey LegalCopyright \"2013-2016 ${PRODUCT_PUBLISHER}\"\n  VIAddVersionKey FileDescription \"${PRODUCT_NAME} Installer\"\n  VIAddVersionKey FileVersion ${PRODUCT_VERSION}\n  VIAddVersionKey ProductVersion ${PRODUCT_VERSION}\n  VIAddVersionKey InternalName \"${PRODUCT_NAME} Installer\"\n  VIAddVersionKey LegalTrademarks \"${PRODUCT_NAME} is a trademark of ${PRODUCT_PUBLISHER}\" \n  VIAddVersionKey OriginalFilename \"${PRODUCT_NAME}.exe\"\n\n;--------------------------------\n;Interface Settings\n\n  !define MUI_ABORTWARNING\n  !define MUI_ABORTWARNING_TEXT \"Are you sure you wish to abort the installation of ${PRODUCT_NAME}?\"\n  \n  !define MUI_ICON \"tmp\\electrum\\icons\\electrum.ico\"\n  \n;--------------------------------\n;Pages\n\n  !insertmacro MUI_PAGE_DIRECTORY\n  !insertmacro MUI_PAGE_INSTFILES\n  !insertmacro MUI_UNPAGE_CONFIRM\n  !insertmacro MUI_UNPAGE_INSTFILES\n\n;--------------------------------\n;Languages\n\n  !insertmacro MUI_LANGUAGE \"English\"\n\n;--------------------------------\n;Installer Sections\n\n;Check if we have Administrator rights\nFunction .onInit\n\tUserInfo::GetAccountType\n\tpop $0\n\t${If} $0 != \"admin\" ;Require admin rights on NT4+\n\t\tMessageBox mb_iconstop \"Administrator rights required!\"\n\t\tSetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED\n\t\tQuit\n\t${EndIf}\nFunctionEnd\n\nSection\n  SetOutPath $INSTDIR\n\n  ;Uninstall previous version files\n  RMDir /r \"$INSTDIR\\*.*\"\n  Delete \"$DESKTOP\\${PRODUCT_NAME}.lnk\"\n  Delete \"$SMPROGRAMS\\${PRODUCT_NAME}\\*.*\"\n  \n  ;Files to pack into the installer\n  File /r \"dist\\electrum\\*.*\"\n  File \"..\\..\\icons\\electrum.ico\"\n\n  ;Store installation folder\n  WriteRegStr HKCU \"Software\\${PRODUCT_NAME}\" \"\" $INSTDIR\n\n  ;Create uninstaller\n  DetailPrint \"Creating uninstaller...\"\n  WriteUninstaller \"$INSTDIR\\Uninstall.exe\"\n\n  ;Create desktop shortcut\n  DetailPrint \"Creating desktop shortcut...\"\n  CreateShortCut \"$DESKTOP\\${PRODUCT_NAME}.lnk\" \"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe\" \"\"\n\n  ;Create start-menu items\n  DetailPrint \"Creating start-menu items...\"\n  CreateDirectory \"$SMPROGRAMS\\${PRODUCT_NAME}\"\n  CreateShortCut \"$SMPROGRAMS\\${PRODUCT_NAME}\\Uninstall.lnk\" \"$INSTDIR\\Uninstall.exe\" \"\" \"$INSTDIR\\Uninstall.exe\" 0\n  CreateShortCut \"$SMPROGRAMS\\${PRODUCT_NAME}\\${PRODUCT_NAME}.lnk\" \"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe\" \"\" \"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe\" 0\n  CreateShortCut \"$SMPROGRAMS\\${PRODUCT_NAME}\\${PRODUCT_NAME} Testnet.lnk\" \"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe\" \"--testnet\" \"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe\" 0\n\n\n  ;Links bitcoinprivate: URI's to Electrum\n  WriteRegStr HKCU \"Software\\Classes\\bitcoin\" \"\" \"URL:bitcoinprivate Protocol\"\n  WriteRegStr HKCU \"Software\\Classes\\bitcoin\" \"URL Protocol\" \"\"\n  WriteRegStr HKCU \"Software\\Classes\\bitcoin\" \"DefaultIcon\" \"$\\\"$INSTDIR\\electrum.ico, 0$\\\"\"\n  WriteRegStr HKCU \"Software\\Classes\\bitcoin\\shell\\open\\command\" \"\" \"$\\\"$INSTDIR\\electrum-${PRODUCT_VERSION}.exe$\\\" $\\\"%1$\\\"\"\n\n  ;Adds an uninstaller possibilty to Windows Uninstall or change a program section\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"DisplayName\" \"$(^Name)\"\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"UninstallString\" \"$INSTDIR\\Uninstall.exe\"\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"DisplayVersion\" \"${PRODUCT_VERSION}\"\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"URLInfoAbout\" \"${PRODUCT_WEB_SITE}\"\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"Publisher\" \"${PRODUCT_PUBLISHER}\"\n  WriteRegStr HKCU \"${PRODUCT_UNINST_KEY}\" \"DisplayIcon\" \"$INSTDIR\\electrum.ico\"\n\n  ;Fixes Windows broken size estimates\n  ${GetSize} \"$INSTDIR\" \"/S=0K\" $0 $1 $2\n  IntFmt $0 \"0x%08X\" $0\n  WriteRegDWORD HKCU \"${PRODUCT_UNINST_KEY}\" \"EstimatedSize\" \"$0\"\nSectionEnd\n\n;--------------------------------\n;Descriptions\n\n;--------------------------------\n;Uninstaller Section\n\nSection \"Uninstall\"\n  RMDir /r \"$INSTDIR\\*.*\"\n\n  RMDir \"$INSTDIR\"\n\n  Delete \"$DESKTOP\\${PRODUCT_NAME}.lnk\"\n  Delete \"$SMPROGRAMS\\${PRODUCT_NAME}\\*.*\"\n  RMDir  \"$SMPROGRAMS\\${PRODUCT_NAME}\"\n  \n  DeleteRegKey HKCU \"Software\\Classes\\bitcoin\"\n  DeleteRegKey HKCU \"Software\\${PRODUCT_NAME}\"\n  DeleteRegKey HKCU \"${PRODUCT_UNINST_KEY}\"\nSectionEnd\n"
  },
  {
    "path": "contrib/build-wine/prepare-hw.sh",
    "content": "#!/bin/bash\n\nTREZOR_GIT_URL=https://github.com/trezor/python-trezor.git\nKEEPKEY_GIT_URL=https://github.com/keepkey/python-keepkey.git\nBTCHIP_GIT_URL=https://github.com/LedgerHQ/btchip-python.git\n\nBRANCH=master\n\nPYTHON_VERSION=3.5.4\n\n# These settings probably don't need any change\nexport WINEPREFIX=/opt/wine64\n\nPYHOME=c:/python$PYTHON_VERSION\nPYTHON=\"wine $PYHOME/python.exe -OO -B\"\n\n# Let's begin!\ncd `dirname $0`\nset -e\n\ncd tmp\n\n$PYTHON -m pip install setuptools --upgrade\n$PYTHON -m pip install cython --upgrade\n$PYTHON -m pip install trezor==0.7.16 --upgrade\n$PYTHON -m pip install keepkey==4.0.0 --upgrade\n$PYTHON -m pip install btchip-python==0.1.23 --upgrade\n\n"
  },
  {
    "path": "contrib/build-wine/prepare-pyinstaller.sh",
    "content": "#!/bin/bash\nPYTHON_VERSION=3.5.4\n\nPYINSTALLER_GIT_URL=https://github.com/ecdsa/pyinstaller.git\nBRANCH=fix_2952\n\nexport WINEPREFIX=/opt/wine64\nPYHOME=c:/python$PYTHON_VERSION\nPYTHON=\"wine $PYHOME/python.exe -OO -B\"\n\ncd `dirname $0`\nset -e\ncd tmp\nif [ ! -d \"pyinstaller\" ]; then\n    git clone -b $BRANCH $PYINSTALLER_GIT_URL pyinstaller\nfi\n\ncd pyinstaller\ngit pull\ngit checkout $BRANCH\n$PYTHON setup.py install\ncd ..\n\nwine \"C:/python$PYTHON_VERSION/scripts/pyinstaller.exe\" -v\n"
  },
  {
    "path": "contrib/build-wine/prepare-wine.sh",
    "content": "#!/bin/bash\n\n# Please update these carefully, some versions won't work under Wine\nNSIS_URL=https://prdownloads.sourceforge.net/nsis/nsis-3.02.1-setup.exe?download\nNSIS_SHA256=736c9062a02e297e335f82252e648a883171c98e0d5120439f538c81d429552e\nPYTHON_VERSION=3.5.4\n\n## These settings probably don't need change\nexport WINEPREFIX=/opt/wine64\n#export WINEARCH='win32'\n\nPYHOME=c:/python$PYTHON_VERSION\nPYTHON=\"wine $PYHOME/python.exe -OO -B\"\n\n\n# based on https://superuser.com/questions/497940/script-to-verify-a-signature-with-gpg\nverify_signature() {\n    local file=$1 keyring=$2 out=\n    if out=$(gpg --no-default-keyring --keyring \"$keyring\" --status-fd 1 --verify \"$file\" 2>/dev/null) &&\n       echo \"$out\" | grep -qs \"^\\[GNUPG:\\] VALIDSIG \"; then\n        return 0\n    else\n        echo \"$out\" >&2\n        exit 0\n    fi\n}\n\nverify_hash() {\n    local file=$1 expected_hash=$2 out=\n    actual_hash=$(sha256sum $file | awk '{print $1}')\n    if [ \"$actual_hash\" == \"$expected_hash\" ]; then\n        return 0\n    else\n        echo \"$file $actual_hash (unexpected hash)\" >&2\n        exit 0\n    fi\n}\n\n# Let's begin!\ncd `dirname $0`\nset -e\n\n# Clean up Wine environment\necho \"Cleaning $WINEPREFIX\"\nrm -rf $WINEPREFIX\necho \"done\"\n\nwine 'wineboot'\n\necho \"Cleaning tmp\"\nrm -rf tmp\nmkdir -p tmp\necho \"done\"\n\ncd tmp\n\n# Install Python\n# note: you might need \"sudo apt-get install dirmngr\" for the following\n# keys from https://www.python.org/downloads/#pubkeys\nKEYRING_PYTHON_DEV=keyring-electrum-build-python-dev.gpg\ngpg --no-default-keyring --keyring $KEYRING_PYTHON_DEV --recv-keys 531F072D39700991925FED0C0EDDC5F26A45C816 26DEA9D4613391EF3E25C9FF0A5B101836580288 CBC547978A3964D14B9AB36A6AF053F07D9DC8D2 C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF 12EF3DC38047DA382D18A5B999CDEA9DA4135B38 8417157EDBE73D9EAC1E539B126EB563A74B06BF DBBF2EEBF925FAADCF1F3FFFD9866941EA5BBD71 2BA0DB82515BBB9EFFAC71C5C9BE28DEE6DF025C 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D C9B104B3DD3AA72D7CCB1066FB9921286F5E1540 97FC712E4C024BBEA48A61ED3A5CA953F73C700D 7ED10B6531D7C8E1BC296021FC624643487034E5\nfor msifile in core dev exe lib pip tools; do\n    echo \"Installing $msifile...\"\n    wget \"https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi\"\n    wget \"https://www.python.org/ftp/python/$PYTHON_VERSION/win32/${msifile}.msi.asc\"\n    verify_signature \"${msifile}.msi.asc\" $KEYRING_PYTHON_DEV\n    wine msiexec /i \"${msifile}.msi\" /qb TARGETDIR=C:/python$PYTHON_VERSION\ndone\n\n# upgrade pip\n$PYTHON -m pip install pip --upgrade\n\n# Install PyWin32\n$PYTHON -m pip install pypiwin32\n\n# Install PyQt\n$PYTHON -m pip install PyQt5\n\n## Install pyinstaller\n#$PYTHON -m pip install pyinstaller==3.3\n\n\n# Install ZBar\n#wget -q -O zbar.exe \"https://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download\"\n#wine zbar.exe\n\n# install Cryptodome\n$PYTHON -m pip install pycryptodomex\n\n# install PySocks\n$PYTHON -m pip install win_inet_pton\n\n# install websocket (python2)\n$PYTHON -m pip install websocket-client\n\n# Upgrade setuptools (so Electrum can be installed later)\n$PYTHON -m pip install setuptools --upgrade\n\n# Install NSIS installer\nwget -q -O nsis.exe \"$NSIS_URL\"\nverify_hash nsis.exe $NSIS_SHA256\nwine nsis.exe /S\n\n# Install UPX\n#wget -O upx.zip \"https://downloads.sourceforge.net/project/upx/upx/3.08/upx308w.zip\"\n#unzip -o upx.zip\n#cp upx*/upx.exe .\n\n# add dlls needed for pyinstaller:\ncp $WINEPREFIX/drive_c/python$PYTHON_VERSION/Lib/site-packages/PyQt5/Qt/bin/* $WINEPREFIX/drive_c/python$PYTHON_VERSION/\n\n\necho \"Wine is configured. Please run prepare-pyinstaller.sh\"\n"
  },
  {
    "path": "contrib/freeze_packages.sh",
    "content": "#!/bin/bash\n# Run this after a new release to update dependencies\n\nvenv_dir=~/.electrum-venv\ncontrib=$(dirname \"$0\")\n\nwhich virtualenv > /dev/null 2>&1 || { echo \"Please install virtualenv\" && exit 1; }\n\nrm $venv_dir -rf\nvirtualenv $venv_dir\n\nsource $venv_dir/bin/activate\n\necho \"Installing dependencies\"\n\npushd $contrib/..\npython setup.py install\npopd\n\npip freeze | sed '/^Electrum/ d' > $contrib/requirements.txt\n\necho \"Updated requirements\"\n"
  },
  {
    "path": "contrib/make_apk",
    "content": "#!/bin/bash\npushd lib\nVERSION=$(python -c \"import version; print version.ELECTRUM_VERSION\")\".0\"\npopd\necho $VERSION\necho $VERSION > contrib/apk_version\npushd ./gui/kivy/; make apk; popd\n"
  },
  {
    "path": "contrib/make_download",
    "content": "#!/usr/bin/python2\nimport re\nimport os\n\nfrom versions import version, version_win, version_mac, version_android, version_apk\nfrom versions import download_template, download_page\n\nwith open(download_template) as f:\n    string = f.read()\n\nstring = string.replace(\"##VERSION##\", version)\nstring = string.replace(\"##VERSION_WIN##\", version_win)\nstring = string.replace(\"##VERSION_MAC##\", version_mac)\nstring = string.replace(\"##VERSION_ANDROID##\", version_android)\nstring = string.replace(\"##VERSION_APK##\", version_apk)\n\nfiles = {\n    'tgz': \"Electrum-%s.tar.gz\" % version,\n    'zip': \"Electrum-%s.zip\" % version,\n    'mac': \"electrum-%s.dmg\" % version_mac,\n    'win': \"electrum-%s.exe\" % version_win,\n    'win_setup': \"electrum-%s-setup.exe\" % version_win,\n    'win_portable': \"electrum-%s-portable.exe\" % version_win,\n}\n\nfor k, n in files.items():\n    path = \"dist/%s\"%n\n    link = \"https://download.electrum.org/%s/%s\"%(version,n)\n    if not os.path.exists(path):\n        os.system(\"wget -q %s -O %s\" % (link, path))\n    if not os.path.getsize(path):\n        os.unlink(path)\n        string = re.sub(\"<div id=\\\"%s\\\">(.*?)</div>\"%k, '', string, flags=re.DOTALL + re.MULTILINE)\n        continue\n    sigpath = path + '.asc'\n    siglink = link + '.asc'\n    if not os.path.exists(sigpath):\n        os.system(\"wget -q %s -O %s\" % (siglink, sigpath))\n    if not os.path.getsize(sigpath):\n        os.unlink(sigpath)\n        string = re.sub(\"<div id=\\\"%s\\\">(.*?)</div>\"%k, '', string, flags=re.DOTALL + re.MULTILINE)\n        continue\n    if os.system(\"gpg --verify %s\"%sigpath) != 0:\n        raise BaseException(sigpath)\n    string = string.replace(\"##link_%s##\"%k, link)\n\n\nwith open(download_page,'w') as f:\n    f.write(string)\n\n\n"
  },
  {
    "path": "contrib/make_locale",
    "content": "#!/usr/bin/env python3\nimport os\nimport io\nimport zipfile\nimport requests\n\nos.chdir(os.path.dirname(os.path.realpath(__file__)))\nos.chdir('..')\n\n# Generate fresh translation template\nif not os.path.exists('lib/locale'):\n    os.mkdir('lib/locale')\ncmd = 'xgettext -s --no-wrap -f app.fil --output=lib/locale/messages.pot'\nprint('Generate template')\nos.system(cmd)\n\nos.chdir('lib')\n\ncrowdin_identifier = 'electrum'\ncrowdin_file_name = 'electrum-client/messages.pot'\nlocale_file_name = 'locale/messages.pot'\ncrowdin_api_key = None\n\nfilename = '~/.crowdin_api_key'\nif os.path.exists(filename):\n    with open(filename) as f:\n        crowdin_api_key = f.read().strip()\n\nif \"crowdin_api_key\" in os.environ:\n    crowdin_api_key = os.environ[\"crowdin_api_key\"]\n\nif crowdin_api_key:\n    # Push to Crowdin\n    print('Push to Crowdin')\n    url = ('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key)\n    with open(locale_file_name,'rb') as f:\n        files = {crowdin_file_name: f}\n        requests.request('POST', url, files=files)\n    # Build translations\n    print('Build translations')\n    response = requests.request('GET', 'https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key).content\n    print(response)\n\n# Download & unzip\nprint('Download translations')\ns = requests.request('GET', 'https://crowdin.com/download/project/' + crowdin_identifier + '.zip').content\nzfobj = zipfile.ZipFile(io.BytesIO(s))\n\nprint('Unzip translations')\nfor name in zfobj.namelist():\n    if not name.startswith('electrum-client/locale'):\n        continue\n    if name.endswith('/'):\n        if not os.path.exists(name[16:]):\n            os.mkdir(name[16:])\n    else:\n        with open(name[16:], 'wb') as output:\n            output.write(zfobj.read(name))\n\n# Convert .po to .mo\nprint('Installing')\nfor lang in os.listdir('locale'):\n    if lang.startswith('messages'):\n        continue\n    # Check LC_MESSAGES folder\n    mo_dir = 'locale/%s/LC_MESSAGES' % lang\n    if not os.path.exists(mo_dir):\n        os.mkdir(mo_dir)\n    cmd = 'msgfmt --output-file=\"%s/electrum.mo\" \"locale/%s/electrum.po\"' % (mo_dir,lang)\n    print('Installing', lang)\n    os.system(cmd)\n"
  },
  {
    "path": "contrib/make_packages",
    "content": "#!/bin/bash\n\ncontrib=$(dirname \"$0\")\n\nwhereis pip3\nif [ $? -ne 0 ] ; then echo \"Install pip3\" ; exit ; fi\n\nrm $contrib/../packages/ -r\n\n#Install pure python modules in electrum directory\npip3 install -r $contrib/requirements.txt -t $contrib/../packages\n\n"
  },
  {
    "path": "contrib/requirements.txt",
    "content": "certifi==2017.11.5\nchardet==3.0.4\ndnspython==1.15.0\necdsa==0.13.3\nidna==2.6\njsonrpclib-pelix==0.3.1\npbkdf2==1.3\nprotobuf==3.5.0.post1\npyaes==1.6.1\nPySocks==1.6.7\nqrcode==5.3\nrequests==2.18.4\nsix==1.11.0\nurllib3==1.22\n"
  },
  {
    "path": "contrib/sign_packages",
    "content": "#!/usr/bin/python2\n\nimport os\nimport getpass\n\nif __name__ == '__main__':\n\n    os.chdir(\"dist\")\n    password = getpass.getpass(\"Password:\")\n    for f in os.listdir('.'):\n        if f.endswith('asc'):\n            continue\n        os.system( \"gpg --sign --armor --detach --passphrase \\\"%s\\\" %s\"%(password, f) )\n\n    os.chdir(\"..\")\n\n\n\n"
  },
  {
    "path": "create-dmg.sh",
    "content": "#!/bin/sh\n\necho \"Cleaning...\"\nsudo sh ./clean.sh\nVERSION=$(python3 -c \"from lib import version; print(version.ELECTRUM_VERSION)\")\nVERSION=${VERSION//ELECTRUM_VERSION=/}\n\necho \"Creating package $VERSION\"\n\necho \"Running brew install\"\nbrew bundle\n\necho \"Running pip3 install\"\npip3 install -r requirements.txt\n\necho \"Building icons\"\npyrcc5 icons.qrc -o gui/qt/icons_rc.py\n\necho \"Compiling the protobuf description file\"\nprotoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto\n\necho \"Compiling translations\"\n./contrib/make_locale\n\necho \"Creating package $VERSION\"\nsudo python3 setup.py sdist\n\necho \"Creating .app from python using py2app\"\nsudo ARCHFLAGS=\"-arch i386 -arch x86_64\" sudo python3 setup-release.py py2app --includes sip\n\nsudo mkdir dist/installer-mac/\nsudo mv \"dist/Electrum BTCP.app\" \"dist/installer-mac/\"\nsudo touch \"dist/installer-mac/To install, copy it into Applications\"\n\necho \"Creating .dmg\"\nsudo hdiutil create -fs HFS+ -volname \"Electrum BTCP - Installer\" -srcfolder \"dist/installer-mac\" dist/electrum-btcp-$VERSION-macosx.dmg\n\necho \"Done! .dmg and .app are in dist/\"\n\n"
  },
  {
    "path": "docs/release-tests.md",
    "content": "## BTCP Electrum Wallet\n\n**Tests to perform before each release**\n\n### 1. Connection\n\t1. User can connect for the first time (Green Dot).\n\t\t1. User can connect without having a BTCPrivate directory\n\t\t2. User can connect having an existing, matching, BTCPrivate directory\n\t2. User can connect consecutively. This includes being able to resume from previous blockchain headers sync\n\n### 2. User can import b addr private key\n\t1. User can import b addr private key (from ZCL, BTC, and BTCP)\n\t2. User can export b addr private key (from ZCL, BTC, and BTCP)\n\t3. User can Sweep from a b addr private key (from ZCL, BTC, and BTCP)\n\n### 3. Transactions\n\t1. User can send from b->b\n\t2. User can receive from z->b\n\t3. User can receive from b->b\n  4. User can send from bx (multisig)\n  5. User can send to bx (multisig)\n  6. All transactions become Verified\n\t7. Coinbase transaction error handling is correctly done ('e.g. needs to send full amount to Z').\n\n"
  },
  {
    "path": "electrum-btcp",
    "content": "#!/usr/bin/env python3\n# -*- mode: python -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport os\nimport sys\n\n# from https://gist.github.com/tito/09c42fb4767721dc323d\nimport threading\ntry:\n    import jnius\nexcept:\n    jnius = None\nif jnius:\n    orig_thread_run = threading.Thread.run\n    def thread_check_run(*args, **kwargs):\n        try:\n            return orig_thread_run(*args, **kwargs)\n        finally:\n            jnius.detach()\n    threading.Thread.run = thread_check_run\n\nscript_dir = os.path.dirname(os.path.realpath(__file__))\nis_bundle = getattr(sys, 'frozen', False)\nis_local = not is_bundle and os.path.exists(os.path.join(script_dir, \"electrum.desktop\"))\nis_android = 'ANDROID_DATA' in os.environ\nis_macOS = sys.platform == 'darwin'\n\n# move this back to gui/kivy/__init.py once plugins are moved\nos.environ['KIVY_DATA_DIR'] = os.path.abspath(os.path.dirname(__file__)) + '/gui/kivy/data/'\n\nif is_local or is_android:\n    sys.path.insert(0, os.path.join(script_dir, 'packages'))\n\n\ndef check_imports():\n    # pure-python dependencies need to be imported here for pyinstaller\n    try:\n        import dns\n        import pyaes\n        import ecdsa\n        import requests\n        import qrcode\n        import pbkdf2\n        import google.protobuf\n        import jsonrpclib\n    except ImportError as e:\n        sys.exit(\"Error: %s. Try 'sudo pip install <module-name>'\"%str(e))\n    # the following imports are for pyinstaller\n    from google.protobuf import descriptor\n    from google.protobuf import message\n    from google.protobuf import reflection\n    from google.protobuf import descriptor_pb2\n    from jsonrpclib import SimpleJSONRPCServer\n    # make sure that certificates are here\n    if is_bundle and is_macOS:\n        requests.utils.DEFAULT_CA_BUNDLE_PATH = os.path.join(os.path.dirname(__file__), 'cacert.pem')\n    assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)\n\n\nif not is_android:\n    check_imports()\n\n# load local module as electrum\nif is_local or is_android or is_macOS:\n    import imp\n    imp.load_module('electrum', *imp.find_module('lib'))\n    imp.load_module('electrum_gui', *imp.find_module('gui'))\n    imp.load_module('electrum_plugins', *imp.find_module('plugins'))\n\n\nfrom electrum import bitcoin\nfrom electrum import SimpleConfig, Network\nfrom electrum.wallet import Wallet, Imported_Wallet\nfrom electrum.storage import WalletStorage\nfrom electrum.util import print_msg, print_stderr, json_encode, json_decode\nfrom electrum.util import set_verbosity, InvalidPassword\nfrom electrum.commands import get_parser, known_commands, Commands, config_variables\nfrom electrum import daemon\nfrom electrum import keystore\nfrom electrum.mnemonic import Mnemonic\nimport electrum_plugins\n\n# get password routine\ndef prompt_password(prompt, confirm=True):\n    import getpass\n    password = getpass.getpass(prompt, stream=None)\n    if password and confirm:\n        password2 = getpass.getpass(\"Confirm: \")\n        if password != password2:\n            sys.exit(\"Error: Passwords do not match.\")\n    if not password:\n        password = None\n    return password\n\n\n\ndef run_non_RPC(config):\n    cmdname = config.get('cmd')\n\n    storage = WalletStorage(config.get_wallet_path())\n    if storage.file_exists():\n        sys.exit(\"Error: Remove the existing wallet first!\")\n\n    def password_dialog():\n        return prompt_password(\"Password (hit return if you do not wish to encrypt your wallet):\")\n\n    if cmdname == 'restore':\n        text = config.get('text').strip()\n        passphrase = config.get('passphrase', '')\n        password = password_dialog() if keystore.is_private(text) else None\n        if keystore.is_address_list(text):\n            wallet = Imported_Wallet(storage)\n            for x in text.split():\n                wallet.import_address(x)\n        elif keystore.is_private_key_list(text):\n            k = keystore.Imported_KeyStore({})\n            storage.put('keystore', k.dump())\n            storage.put('use_encryption', bool(password))\n            wallet = Imported_Wallet(storage)\n            for x in text.split():\n                wallet.import_private_key(x, password)\n            storage.write()\n        else:\n            if keystore.is_seed(text):\n                k = keystore.from_seed(text, passphrase, False)\n            elif keystore.is_master_key(text):\n                k = keystore.from_master_key(text)\n            else:\n                sys.exit(\"Error: Seed or key not recognized\")\n            if password:\n                k.update_password(None, password)\n            storage.put('keystore', k.dump())\n            storage.put('wallet_type', 'standard')\n            storage.put('use_encryption', bool(password))\n            storage.write()\n            wallet = Wallet(storage)\n        if not config.get('offline'):\n            network = Network(config)\n            network.start()\n            wallet.start_threads(network)\n            print_msg(\"Recovering wallet...\")\n            wallet.synchronize()\n            wallet.wait_until_synchronized()\n            msg = \"Recovery successful\" if wallet.is_found() else \"Found no history for this wallet\"\n        else:\n            msg = \"This wallet was restored offline. It may contain more addresses than displayed.\"\n        print_msg(msg)\n\n    elif cmdname == 'create':\n        password = password_dialog()\n        passphrase = config.get('passphrase', '')\n        seed_type = 'segwit' if config.get('segwit') else 'standard'\n        seed = Mnemonic('en').make_seed(seed_type)\n        k = keystore.from_seed(seed, passphrase, False)\n        storage.put('keystore', k.dump())\n        storage.put('wallet_type', 'standard')\n        wallet = Wallet(storage)\n        wallet.update_password(None, password, True)\n        wallet.synchronize()\n        print_msg(\"Your wallet generation seed is:\\n\\\"%s\\\"\" % seed)\n        print_msg(\"Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.\")\n\n    wallet.storage.write()\n    print_msg(\"Wallet saved in '%s'\" % wallet.storage.path)\n    sys.exit(0)\n\n\ndef init_daemon(config_options):\n    config = SimpleConfig(config_options)\n    storage = WalletStorage(config.get_wallet_path())\n    if not storage.file_exists():\n        print_msg(\"Error: Wallet file not found.\")\n        print_msg(\"Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option\")\n        sys.exit(0)\n    if storage.is_encrypted():\n        if config.get('password'):\n            password = config.get('password')\n        else:\n            password = prompt_password('Password:', False)\n            if not password:\n                print_msg(\"Error: Password required\")\n                sys.exit(1)\n    else:\n        password = None\n    config_options['password'] = password\n\n\ndef init_cmdline(config_options, server):\n    config = SimpleConfig(config_options)\n    cmdname = config.get('cmd')\n    cmd = known_commands[cmdname]\n\n    if cmdname == 'signtransaction' and config.get('privkey'):\n        cmd.requires_wallet = False\n        cmd.requires_password = False\n\n    if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):\n        cmd.requires_password = False\n\n    if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):\n        cmd.requires_network = True\n\n    # instanciate wallet for command-line\n    storage = WalletStorage(config.get_wallet_path())\n\n    if cmd.requires_wallet and not storage.file_exists():\n        print_msg(\"Error: Wallet file not found.\")\n        print_msg(\"Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option\")\n        sys.exit(0)\n\n    # important warning\n    if cmd.name in ['getprivatekeys']:\n        print_stderr(\"WARNING: ALL your private keys are secret.\")\n        print_stderr(\"Exposing a single private key can compromise your entire wallet!\")\n        print_stderr(\"In particular, DO NOT use 'redeem private key' services proposed by third parties.\")\n\n    # commands needing password\n    if (cmd.requires_wallet and storage.is_encrypted() and server is None)\\\n       or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):\n        if config.get('password'):\n            password = config.get('password')\n        else:\n            password = prompt_password('Password:', False)\n            if not password:\n                print_msg(\"Error: Password required\")\n                sys.exit(1)\n    else:\n        password = None\n\n    config_options['password'] = password\n\n    if cmd.name == 'password':\n        new_password = prompt_password('New password:')\n        config_options['new_password'] = new_password\n\n    return cmd, password\n\n\ndef run_offline_command(config, config_options):\n    cmdname = config.get('cmd')\n    cmd = known_commands[cmdname]\n    password = config_options.get('password')\n    if cmd.requires_wallet:\n        storage = WalletStorage(config.get_wallet_path())\n        if storage.is_encrypted():\n            storage.decrypt(password)\n        wallet = Wallet(storage)\n    else:\n        wallet = None\n    # check password\n    if cmd.requires_password and storage.get('use_encryption'):\n        try:\n            seed = wallet.check_password(password)\n        except InvalidPassword:\n            print_msg(\"Error: This password does not decode this wallet.\")\n            sys.exit(1)\n    if cmd.requires_network:\n        print_msg(\"Warning: running command offline\")\n    # arguments passed to function\n    args = [config.get(x) for x in cmd.params]\n    # decode json arguments\n    args = list(map(json_decode, args))\n    # options\n    kwargs = {}\n    for x in cmd.options:\n        kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))\n    cmd_runner = Commands(config, wallet, None)\n    func = getattr(cmd_runner, cmd.name)\n    result = func(*args, **kwargs)\n    # save wallet\n    if wallet:\n        wallet.storage.write()\n    return result\n\ndef init_plugins(config, gui_name):\n    from electrum.plugins import Plugins\n    return Plugins(config, is_local or is_android, gui_name)\n\nif __name__ == '__main__':\n\n    # on osx, delete Process Serial Number arg generated for apps launched in Finder\n    sys.argv = list(filter(lambda x: not x.startswith('-psn'), sys.argv))\n\n    # old 'help' syntax\n    if len(sys.argv) > 1 and sys.argv[1] == 'help':\n        sys.argv.remove('help')\n        sys.argv.append('-h')\n\n    # read arguments from stdin pipe and prompt\n    for i, arg in enumerate(sys.argv):\n        if arg == '-':\n            if not sys.stdin.isatty():\n                sys.argv[i] = sys.stdin.read()\n                break\n            else:\n                raise BaseException('Cannot get argument from stdin')\n        elif arg == '?':\n            sys.argv[i] = input(\"Enter argument:\")\n        elif arg == ':':\n            sys.argv[i] = prompt_password('Enter argument (will not echo):', False)\n\n    # parse command line\n    parser = get_parser()\n    args = parser.parse_args()\n\n    # config is an object passed to the various constructors (wallet, interface, gui)\n    if is_android:\n        config_options = {\n            'verbose': True,\n            'cmd': 'gui',\n            'gui': 'kivy',\n        }\n    else:\n        config_options = args.__dict__\n        f = lambda key: config_options[key] is not None and key not in config_variables.get(args.cmd, {}).keys()\n        config_options = {key: config_options[key] for key in filter(f, config_options.keys())}\n        if config_options.get('server'):\n            config_options['auto_connect'] = False\n\n    config_options['cwd'] = os.getcwd()\n\n    # fixme: this can probably be achieved with a runtime hook (pyinstaller)\n    if is_bundle and hasattr(sys, '_MEIPASS') and os.path.exists(os.path.join(sys._MEIPASS, 'is_portable')):\n        config_options['portable'] = True\n\n    if config_options.get('portable'):\n        config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')\n\n    # kivy sometimes freezes when we write to sys.stderr\n    set_verbosity(config_options.get('verbose') and config_options.get('gui')!='kivy')\n\n    # check uri\n    uri = config_options.get('url')\n    if uri:\n        if not uri.startswith('bitcoin:'):\n            print_stderr('unknown command:', uri)\n            sys.exit(1)\n        config_options['url'] = uri\n\n    # todo: defer this to gui\n    config = SimpleConfig(config_options)\n    cmdname = config.get('cmd')\n\n    if config.get('testnet'):\n        bitcoin.NetworkConstants.set_testnet()\n\n    # run non-RPC commands separately\n    if cmdname in ['create', 'restore']:\n        run_non_RPC(config)\n        sys.exit(0)\n\n    if cmdname == 'gui':\n        fd, server = daemon.get_fd_or_server(config)\n        if fd is not None:\n            plugins = init_plugins(config, config.get('gui', 'qt'))\n            d = daemon.Daemon(config, fd, True)\n            d.start()\n            d.init_gui(config, plugins)\n            sys.exit(0)\n        else:\n            result = server.gui(config_options)\n\n    elif cmdname == 'daemon':\n        subcommand = config.get('subcommand')\n        if subcommand in ['load_wallet']:\n            init_daemon(config_options)\n\n        if subcommand in [None, 'start']:\n            fd, server = daemon.get_fd_or_server(config)\n            if fd is not None:\n                if subcommand == 'start':\n                    pid = os.fork()\n                    if pid:\n                        print_stderr(\"starting daemon (PID %d)\" % pid)\n                        sys.exit(0)\n                init_plugins(config, 'cmdline')\n                d = daemon.Daemon(config, fd, False)\n                d.start()\n                if config.get('websocket_server'):\n                    from electrum import websockets\n                    websockets.WebSocketServer(config, d.network).start()\n                if config.get('requests_dir'):\n                    path = os.path.join(config.get('requests_dir'), 'index.html')\n                    if not os.path.exists(path):\n                        print(\"Requests directory not configured.\")\n                        print(\"You can configure it using https://github.com/spesmilo/electrum-merchant\")\n                        sys.exit(1)\n                d.join()\n                sys.exit(0)\n            else:\n                result = server.daemon(config_options)\n        else:\n            server = daemon.get_server(config)\n            if server is not None:\n                result = server.daemon(config_options)\n            else:\n                print_msg(\"Daemon not running\")\n                sys.exit(1)\n    else:\n        # command line\n        server = daemon.get_server(config)\n        init_cmdline(config_options, server)\n        if server is not None:\n            result = server.run_cmdline(config_options)\n        else:\n            cmd = known_commands[cmdname]\n            if cmd.requires_network:\n                print_msg(\"Daemon not running; try 'electrum daemon start'\")\n                sys.exit(1)\n            else:\n                init_plugins(config, 'cmdline')\n                result = run_offline_command(config, config_options)\n                # print result\n    if isinstance(result, str):\n        print_msg(result)\n    elif type(result) is dict and result.get('error'):\n        print_stderr(result.get('error'))\n    elif result is not None:\n        print_msg(json_encode(result))\n    sys.exit(0)\n"
  },
  {
    "path": "electrum-env",
    "content": "#!/bin/bash\n#\n# This script creates a virtualenv named 'env' and installs all\n# python dependencies before activating the env and running Electrum.\n# If 'env' already exists, it is activated and Electrum is started\n# without any installations. Additionally, the PYTHONPATH environment\n# variable is set properly before running Electrum.\n#\n# python-qt and its dependencies will still need to be installed with\n# your package manager.\n\nif [ -e ./env/bin/activate ]; then\n    source ./env/bin/activate\nelse\n    virtualenv env -p `which python3`\n    source ./env/bin/activate\n    python3 setup.py install\nfi\n\nexport PYTHONPATH=\"/usr/local/lib/python3.5/site-packages:$PYTHONPATH\"\n\n./electrum-btcp \"$@\"\n\ndeactivate\n"
  },
  {
    "path": "electrum.conf.sample",
    "content": "# Configuration file for the electrum client\n# Settings defined here are shared across wallets\n#\n# copy this file to /etc/electrum.conf if you want read-only settings\n\n[client]\nserver = electrum.novit.ro:50001:t\nproxy = None\ngap_limit = 5\n# booleans use python syntax\nuse_change = True\ngui = qt\nnum_zeros = 2\n# default transaction fee is in Satoshis\nfee = 10000\nwinpos-qt = [799, 226, 877, 435]\n"
  },
  {
    "path": "electrum.desktop",
    "content": "# If you want electrum to appear in a linux app launcher (\"start menu\"), install this by doing:\n# sudo desktop-file-install electrum.desktop\n\n[Desktop Entry]\nComment=Lightweight Bitcoin Private Client\nExec=electrum-btcp %u\nGenericName[en_US]=Bitcoin Private Wallet\nGenericName=Bitcoin Private Wallet\nIcon=electrum\nName[en_US]=Bitcoin Private Electrum Wallet\nName=Bitcoin Private Electrum Wallet\nCategories=Finance;Network;\nStartupNotify=false\nTerminal=false\nType=Application\nMimeType=x-scheme-handler/bitcoin;\n\n"
  },
  {
    "path": "gui/__init__.py",
    "content": "# To create a new GUI, please add its code to this directory.\n# Three objects are passed to the ElectrumGui: config, daemon and plugins\n# The Wallet object is instanciated by the GUI\n\n# Notifications about network events are sent to the GUI by using network.register_callback()\n"
  },
  {
    "path": "gui/kivy/Makefile",
    "content": "PYTHON = python3\n\n# needs kivy installed or in PYTHONPATH\n\n.PHONY: theming apk clean\n\ntheming:\n\t$(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png\nprepare:\n\t# running pre build setup\n\t@cp tools/buildozer.spec ../../buildozer.spec\n\t# copy electrum to main.py\n\t@cp ../../electrum ../../main.py\n\t@-if [ ! -d \"../../.buildozer\" ];then \\\n\t\tcd ../..; buildozer android debug;\\\n\t\tcp -f gui/kivy/tools/blacklist.txt .buildozer/android/platform/python-for-android/src/blacklist.txt;\\\n\t\trm -rf ./.buildozer/android/platform/python-for-android/dist;\\\n\tfi\napk:\n\t@make prepare\n\t@-cd ../..; buildozer android debug deploy run\n\t@make clean\nrelease:\n\t@make prepare\n\t@-cd ../..; buildozer android release\n\t@make clean\nclean:\n\t# Cleaning up\n\t# rename main.py to electrum\n\t@-rm ../../main.py\n\t# remove buildozer.spec\n\t@-rm ../../buildozer.spec\n"
  },
  {
    "path": "gui/kivy/Readme.txt",
    "content": "Before compiling, create packages: `contrib/make_packages`\n\nCommands::\n\n    `make theming` to make a atlas out of a list of pngs\n\n    `make apk` to make a apk\n\n\nIf something in included modules like kivy or any other module changes\nthen you need to rebuild the distribution. To do so:\n\n  rm -rf .buildozer/android/platform/python-for-android/dist\n\n\nhow to build with ssl:\n\n  rm -rf .buildozer/android/platform/build/\n  ./contrib/make_apk\n  pushd /opt/electrum/.buildozer/android/platform/build/build/libs_collections/Electrum/armeabi-v7a\n  cp libssl1.0.2g.so /opt/crystax-ndk-10.3.2/sources/openssl/1.0.2g/libs/armeabi-v7a/libssl.so\n  cp libcrypto1.0.2g.so /opt/crystax-ndk-10.3.2/sources/openssl/1.0.2g/libs/armeabi-v7a/libcrypto.so\n  popd\n  ./contrib/make_apk\n"
  },
  {
    "path": "gui/kivy/__init__.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n#\n# Kivy GUI\n\nimport sys\nimport os\n\ntry:\n    sys.argv = ['']\n    import kivy\nexcept ImportError:\n    # This error ideally shouldn't raised with pre-built packages\n    sys.exit(\"Error: Could not import kivy. Please install it using the\" + \\\n             \"instructions mentioned here `http://kivy.org/#download` .\")\n\n# minimum required version for kivy\nkivy.require('1.8.0')\nfrom kivy.logger import Logger\n\n\n\n\nclass ElectrumGui:\n\n    def __init__(self, config, daemon, plugins):\n        Logger.debug('ElectrumGUI: initialising')\n        self.daemon = daemon\n        self.network = daemon.network\n        self.config = config\n        self.plugins = plugins\n\n    def main(self):\n        from .main_window import ElectrumWindow\n        self.config.open_last_wallet()\n        w = ElectrumWindow(config=self.config,\n                           network=self.network,\n                           plugins = self.plugins,\n                           gui_object=self)\n        w.run()\n        if w.wallet:\n            self.config.save_last_wallet(w.wallet)\n"
  },
  {
    "path": "gui/kivy/data/fonts/tron/License.txt",
    "content": "Copyright (c) 2010-2011, Jeff Bell [www.randombell.com] | [jeffbell@randombell.com].\nThis font may be distributed freely however must retain this document as well as the Readme.txt file.\r\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\r\nThis license is available with a FAQ at: http://scripts.sil.org/OFL"
  },
  {
    "path": "gui/kivy/data/fonts/tron/Readme.txt",
    "content": "TR2N v1.3\n\nABOUT THE FONT:\nA font based upon the poster text for TRON LEGACY.\n\nThe font is different from the pre-existing TRON font currently on the web.  Similar in minor aspects but different in most.  Style based upon text from different region posters.\n\nUPDATE HISTORY:\n3/7/11 - Adjusted the letter B (both lowercase and uppercase), capped off the ends of T, P and R, added a few more punctuation marks, as well as added the TR and TP ligature to allow for the solid bar connect as in the poster art.\n\n1/22/11 - Made minor corrections to all previous letters and punctuation.  Corrected issue with number 8's top filling in.\n\nABOUT THE AUTHOR:\r\nJeff Bell has produced fonts before, but this is the first one in over 10 years.  His original 3 fonts were under the name DJ-JOHNNYRKA and include \"CASPER\", \"BEVERLY HILLS COP\", \"THE GODFATHER\" and \"FIDDUMS FAMILY\".\n\nFor more information on Jeff Bell and his work can be found online:\n\nwww.randombell.com\nwww.damovieman.deviantart.com\nhttp://www.imdb.com/name/nm3983081/\nhttp://www.vimeo.com/user4004969/videos"
  },
  {
    "path": "gui/kivy/data/glsl/default.fs",
    "content": "$HEADER$\nvoid main (void){\n    gl_FragColor = frag_color * texture2D(texture0, tex_coord0);\n}\n"
  },
  {
    "path": "gui/kivy/data/glsl/default.vs",
    "content": "$HEADER$\nvoid main (void) {\n  frag_color = color * vec4(1.0, 1.0, 1.0, opacity);\n  tex_coord0 = vTexCoords0;\n  gl_Position = projection_mat * modelview_mat * vec4(vPosition.xy, 0.0, 1.0);\n}\n"
  },
  {
    "path": "gui/kivy/data/glsl/header.fs",
    "content": "#ifdef GL_ES\n    precision highp float;\n#endif\n\n/* Outputs from the vertex shader */\nvarying vec4 frag_color;\nvarying vec2 tex_coord0;\n\n/* uniform texture samplers */\nuniform sampler2D texture0;\n"
  },
  {
    "path": "gui/kivy/data/glsl/header.vs",
    "content": "#ifdef GL_ES\n    precision highp float;\n#endif\n\n/* Outputs to the fragment shader */\nvarying vec4 frag_color;\nvarying vec2 tex_coord0;\n\n/* vertex attributes */\nattribute vec2     vPosition;\nattribute vec2     vTexCoords0;\n\n/* uniform variables */\nuniform mat4       modelview_mat;\nuniform mat4       projection_mat;\nuniform vec4       color;\nuniform float      opacity;\n"
  },
  {
    "path": "gui/kivy/data/images/defaulttheme.atlas",
    "content": "{\"defaulttheme-0.png\": {\"progressbar_background\": [391, 227, 24, 24], \"tab_btn_disabled\": [264, 137, 32, 32], \"tab_btn_pressed\": [366, 137, 32, 32], \"image-missing\": [152, 171, 48, 48], \"splitter_h\": [174, 123, 32, 7], \"splitter_down\": [501, 253, 7, 32], \"splitter_disabled_down\": [503, 291, 7, 32], \"vkeyboard_key_down\": [468, 137, 32, 32], \"vkeyboard_disabled_key_down\": [400, 137, 32, 32], \"selector_right\": [248, 223, 55, 62], \"player-background\": [2, 287, 103, 103], \"selector_middle\": [191, 223, 55, 62], \"spinner\": [235, 82, 29, 37], \"tab_btn_disabled_pressed\": [298, 137, 32, 32], \"switch-button_disabled\": [277, 291, 43, 32], \"textinput_disabled_active\": [372, 326, 64, 64], \"splitter_grip\": [36, 50, 12, 26], \"vkeyboard_key_normal\": [2, 44, 32, 32], \"button_disabled\": [80, 82, 29, 37], \"media-playback-stop\": [302, 171, 48, 48], \"splitter\": [501, 87, 7, 32], \"splitter_down_h\": [140, 123, 32, 7], \"sliderh_background_disabled\": [72, 132, 41, 37], \"modalview-background\": [464, 456, 45, 54], \"button\": [142, 82, 29, 37], \"splitter_disabled\": [502, 137, 7, 32], \"checkbox_radio_disabled_on\": [433, 87, 32, 32], \"slider_cursor\": [402, 171, 48, 48], \"vkeyboard_disabled_background\": [68, 221, 64, 64], \"checkbox_disabled_on\": [297, 87, 32, 32], \"sliderv_background_disabled\": [2, 78, 37, 41], \"button_disabled_pressed\": [111, 82, 29, 37], \"audio-volume-muted\": [102, 171, 48, 48], \"close\": [417, 231, 20, 20], \"action_group_disabled\": [452, 171, 33, 48], \"vkeyboard_background\": [2, 221, 64, 64], \"checkbox_off\": [331, 87, 32, 32], \"tab_disabled\": [305, 253, 96, 32], \"sliderh_background\": [115, 132, 41, 37], \"switch-button\": [322, 291, 43, 32], \"tree_closed\": [439, 231, 20, 20], \"bubble_btn_pressed\": [435, 291, 32, 32], \"selector_left\": [134, 223, 55, 62], \"filechooser_file\": [174, 326, 64, 64], \"checkbox_radio_disabled_off\": [399, 87, 32, 32], \"checkbox_radio_on\": [196, 137, 32, 32], \"checkbox_on\": [365, 87, 32, 32], \"button_pressed\": [173, 82, 29, 37], \"audio-volume-high\": [464, 406, 48, 48], \"audio-volume-low\": [2, 171, 48, 48], \"progressbar\": [305, 227, 32, 24], \"previous_normal\": [487, 187, 19, 32], \"separator\": [504, 342, 5, 48], \"filechooser_folder\": [240, 326, 64, 64], \"checkbox_radio_off\": [467, 87, 32, 32], \"textinput_active\": [306, 326, 64, 64], \"textinput\": [438, 326, 64, 64], \"player-play-overlay\": [122, 395, 117, 115], \"media-playback-pause\": [202, 171, 48, 48], \"sliderv_background\": [41, 78, 37, 41], \"ring\": [354, 402, 108, 108], \"bubble_arrow\": [487, 175, 16, 10], \"slider_cursor_disabled\": [352, 171, 48, 48], \"checkbox_disabled_off\": [469, 291, 32, 32], \"action_group_down\": [2, 121, 33, 48], \"spinner_disabled\": [204, 82, 29, 37], \"splitter_disabled_h\": [106, 123, 32, 7], \"bubble\": [107, 325, 65, 65], \"media-playback-start\": [252, 171, 48, 48], \"vkeyboard_disabled_key_normal\": [434, 137, 32, 32], \"overflow\": [230, 137, 32, 32], \"tree_opened\": [461, 231, 20, 20], \"action_item\": [339, 227, 24, 24], \"bubble_btn\": [401, 291, 32, 32], \"audio-volume-medium\": [52, 171, 48, 48], \"action_group\": [37, 121, 33, 48], \"spinner_pressed\": [266, 82, 29, 37], \"filechooser_selected\": [2, 392, 118, 118], \"tab\": [403, 253, 96, 32], \"action_bar\": [158, 133, 36, 36], \"action_view\": [365, 227, 24, 24], \"tab_btn\": [332, 137, 32, 32], \"switch-background\": [192, 291, 83, 32], \"splitter_disabled_down_h\": [72, 123, 32, 7], \"action_item_down\": [367, 291, 32, 32], \"switch-background_disabled\": [107, 291, 83, 32], \"textinput_disabled\": [241, 399, 111, 111], \"splitter_grip_h\": [483, 239, 26, 12]}}"
  },
  {
    "path": "gui/kivy/data/java-classes/org/electrum/qr/SimpleScannerActivity.java",
    "content": "package org.electrum.qr;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.content.Intent;\n\nimport java.util.Arrays;\n\nimport me.dm7.barcodescanner.zxing.ZXingScannerView;\n\nimport com.google.zxing.Result;\nimport com.google.zxing.BarcodeFormat;\n\npublic class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {\n    private ZXingScannerView mScannerView;\n    final String TAG = \"org.electrum.SimpleScannerActivity\";\n\n    @Override\n    public void onCreate(Bundle state) {\n        super.onCreate(state);\n        mScannerView = new ZXingScannerView(this);   // Programmatically initialize the scanner view\n        mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));\n        setContentView(mScannerView);                // Set the scanner view as the content view\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n        mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.\n        mScannerView.startCamera();          // Start camera on resume\n    }\n\n    @Override\n    public void onPause() {\n        super.onPause();\n        mScannerView.stopCamera();           // Stop camera on pause\n    }\n\n    @Override\n    public void handleResult(Result rawResult) {\n        Intent resultIntent = new Intent();\n        resultIntent.putExtra(\"text\", rawResult.getText());\n        resultIntent.putExtra(\"format\", rawResult.getBarcodeFormat().toString());\n        setResult(Activity.RESULT_OK, resultIntent);\n        this.finish();\n    }\n}\n"
  },
  {
    "path": "gui/kivy/data/style.kv",
    "content": "#:kivy 1.0\n\n<Label>:\n    canvas:\n        Color:\n            rgba: self.disabled_color if self.disabled else (self.color if not self.markup else (1, 1, 1, 1))\n        Rectangle:\n            texture: self.texture\n            size: self.texture_size\n            pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)\n\n<-Button,-ToggleButton>:\n    state_image: self.background_normal if self.state == 'normal' else self.background_down\n    disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down\n    canvas:\n        Color:\n            rgba: self.background_color\n        BorderImage:\n            border: self.border\n            pos: self.pos\n            size: self.size\n            source: self.disabled_image if self.disabled else self.state_image\n        Color:\n            rgba: self.disabled_color if self.disabled else self.color\n        Rectangle:\n            texture: self.texture\n            size: self.texture_size\n            pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)\n\n<BubbleContent>\n    opacity: .7 if self.disabled else 1\n    rows: 1\n    canvas:\n        Color:\n            rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)\n        BorderImage:\n            border: self.parent.border if self.parent else (16, 16, 16, 16)\n            texture: root.parent._bk_img.texture if root.parent else None\n            size: self.size\n            pos: self.pos\n\n<BubbleButton>:\n    background_normal: 'atlas://data/images/defaulttheme/bubble_btn'\n    background_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'\n    background_disabled_normal: 'atlas://data/images/defaulttheme/bubble_btn'\n    background_disabled_down: 'atlas://data/images/defaulttheme/bubble_btn_pressed'\n    border: (0, 0, 0, 0)\n\n<Slider>:\n    canvas:\n        Color:\n            rgb: 1, 1, 1\n        BorderImage:\n            border: (0, 18, 0, 18) if self.orientation == 'horizontal' else (18, 0, 18, 0)\n            pos: (self.x + self.padding, self.center_y - sp(18)) if self.orientation == 'horizontal' else (self.center_x - 18, self.y + self.padding)\n            size: (self.width - self.padding * 2, sp(36)) if self.orientation == 'horizontal' else (sp(36), self.height - self.padding * 2)\n            source: 'atlas://data/images/defaulttheme/slider{}_background{}'.format(self.orientation[0], '_disabled' if self.disabled else '')\n        Rectangle:\n            pos: (self.value_pos[0] - sp(16), self.center_y - sp(17)) if self.orientation == 'horizontal' else (self.center_x - (16), self.value_pos[1] - sp(16))\n            size: (sp(32), sp(32))\n            source: 'atlas://data/images/defaulttheme/slider_cursor{}'.format('_disabled' if self.disabled else '')\n\n<RelativeLayout>:\n    canvas.before:\n        PushMatrix\n        Translate:\n            xy: self.pos\n    canvas.after:\n        PopMatrix\n\n<Image,AsyncImage>:\n    canvas:\n        Color:\n            rgba: self.color\n        Rectangle:\n            texture: self.texture\n            size: self.norm_image_size\n            pos: self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.\n\n<TabbedPanelContent>\n    rows: 1\n    padding: 3\n    canvas:\n        Color:\n            rgba: self.parent.background_color if self.parent else (1, 1, 1, 1)\n        BorderImage:\n            border: self.parent.border if self.parent else (16, 16, 16, 16)\n            source: (root.parent.background_disabled_image if self.disabled else root.parent.background_image) if root.parent else None\n            size: self.size\n            pos: self.pos\n\n<TabbedPanelStrip>\n    rows: 1\n\n<StripLayout>\n    padding: '2dp', '2dp', '2dp', '2dp'\n    canvas.before:\n        BorderImage:\n            pos: self.pos\n            size: self.size\n            border: root.border\n            source: root.background_image\n\n<TabbedPanelHeader>:\n    halign: 'center'\n    valign: 'middle'\n    background_normal: 'atlas://data/images/defaulttheme/tab_btn'\n    background_disabled_normal: 'atlas://data/images/defaulttheme/tab_btn_disabled'\n    background_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'\n    background_disabled_down: 'atlas://data/images/defaulttheme/tab_btn_pressed'\n    border: (8, 8, 8, 8)\n    font_size: '15sp'\n\n<Selector>\n    allow_stretch: True\n\n<TextInput>:\n    canvas.before:\n        Color:\n            rgba: self.background_color\n        BorderImage:\n            border: self.border\n            pos: self.pos\n            size: self.size\n            source: (self.background_disabled_active if self.disabled else self.background_active) if self.focus else (self.background_disabled_normal if self.disabled else self.background_normal)\n        Color:\n            rgba: (self.cursor_color if self.focus and not self.cursor_blink else (0, 0, 0, 0))\n        Rectangle:\n            pos: [int(x) for x in self.cursor_pos]\n            size: 1, -self.line_height\n        Color:\n            rgba: self.disabled_foreground_color if self.disabled else (self.hint_text_color if not self.text and not self.focus else self.foreground_color)\n\n<TextInputCutCopyPaste>:\n    but_cut: cut.__self__\n    but_copy: copy.__self__\n    but_paste: paste.__self__\n    but_selectall: selectall.__self__\n\n    size_hint: None, None\n    size: '150sp', '50sp'\n    BubbleButton:\n        id: cut\n        text: 'Cut'\n        on_release: root.do('cut')\n    BubbleButton:\n        id: copy\n        text: 'Copy'\n        on_release: root.do('copy')\n    BubbleButton:\n        id: paste\n        text: 'Paste'\n        on_release: root.do('paste')\n    BubbleButton:\n        id: selectall\n        text: 'Select All'\n        on_release: root.do('selectall')\n\n<CodeInput>:\n    font_name: 'data/fonts/RobotoMono-Regular.ttf'\n\n\n<TreeViewNode>:\n    canvas.before:\n        Color:\n            rgba: self.color_selected if self.is_selected else self.odd_color if self.odd else self.even_color\n        Rectangle:\n            pos: [self.parent.x, self.y] if self.parent else [0, 0]\n            size: [self.parent.width, self.height] if self.parent else [1, 1]\n        Color:\n            rgba: 1, 1, 1, int(not self.is_leaf)\n        Rectangle:\n            source: 'atlas://data/images/defaulttheme/tree_%s' % ('opened' if self.is_open else 'closed')\n            size: 16, 16\n            pos: self.x - 20, self.center_y - 8\n    canvas.after:\n        Color:\n            rgba: .5, .5, .5, .2\n        Line:\n            points: [self.parent.x, self.y, self.parent.right, self.y] if self.parent else []\n\n\n<TreeViewLabel>:\n    width: self.texture_size[0]\n    height: max(self.texture_size[1] + dp(10), dp(24))\n    text_size: self.width, None\n\n\n<StencilView>:\n    canvas.before:\n        StencilPush\n        Rectangle:\n            pos: self.pos\n            size: self.size\n        StencilUse\n\n    canvas.after:\n        StencilUnUse\n        Rectangle:\n            pos: self.pos\n            size: self.size\n        StencilPop\n\n\n<FileChooserListLayout>:\n    on_entry_added: treeview.add_node(args[1])\n    on_entries_cleared: treeview.root.nodes = []\n    on_subentry_to_entry: not args[2].locked and treeview.add_node(args[1], args[2])\n    on_remove_subentry: args[2].nodes = []\n    BoxLayout:\n        pos: root.pos\n        size: root.size\n        size_hint: None, None\n        orientation: 'vertical'\n        BoxLayout:\n            size_hint_y: None\n            height: 30\n            orientation: 'horizontal'\n            Widget:\n                # Just for spacing\n                width: 10\n                size_hint_x: None\n            Label:\n                text: 'Name'\n                text_size: self.size\n                halign: 'left'\n                bold: True\n            Label:\n                text: 'Size'\n                text_size: self.size\n                size_hint_x: None\n                halign: 'right'\n                bold: True\n            Widget:\n                # Just for spacing\n                width: 10\n                size_hint_x: None\n        ScrollView:\n            id: scrollview\n            do_scroll_x: False\n            Scatter:\n                do_rotation: False\n                do_scale: False\n                do_translation: False\n                size: treeview.size\n                size_hint_y: None\n                TreeView:\n                    id: treeview\n                    hide_root: True\n                    size_hint_y: None\n                    width: scrollview.width\n                    height: self.minimum_height\n                    on_node_expand: root.controller.entry_subselect(args[1])\n                    on_node_collapse: root.controller.close_subselection(args[1])\n\n<FileChooserListView>:\n    layout: layout\n    FileChooserListLayout:\n        id: layout\n        controller: root\n\n[FileListEntry@FloatLayout+TreeViewNode]:\n    locked: False\n    entries: []\n    path: ctx.path\n    # FIXME: is_selected is actually a read_only treeview property. In this\n    # case, however, we're doing this because treeview only has single-selection\n    # hardcoded in it. The fix to this would be to update treeview to allow\n    # multiple selection.\n    is_selected: self.path in ctx.controller().selection\n\n    orientation: 'horizontal'\n    size_hint_y: None\n    height: '48dp' if dp(1) > 1 else '24dp'\n    # Don't allow expansion of the ../ node\n    is_leaf: not ctx.isdir or ctx.name.endswith('..' + ctx.sep) or self.locked\n    on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])\n    on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])\n    BoxLayout:\n        pos: root.pos\n        Label:\n            id: filename\n            text_size: self.width, None\n            halign: 'left'\n            shorten: True\n            text: ctx.name\n        Label:\n            text_size: self.width, None\n            size_hint_x: None\n            halign: 'right'\n            text: '{}'.format(ctx.get_nice_size())\n\n\n<FileChooserIconLayout>:\n    on_entry_added: stacklayout.add_widget(args[1])\n    on_entries_cleared: stacklayout.clear_widgets()\n    ScrollView:\n        id: scrollview\n        pos: root.pos\n        size: root.size\n        size_hint: None, None\n        do_scroll_x: False\n        Scatter:\n            do_rotation: False\n            do_scale: False\n            do_translation: False\n            size_hint_y: None\n            height: stacklayout.height\n            StackLayout:\n                id: stacklayout\n                width: scrollview.width\n                size_hint_y: None\n                height: self.minimum_height\n                spacing: '10dp'\n                padding: '10dp'\n\n<FileChooserIconView>:\n    layout: layout\n    FileChooserIconLayout:\n        id: layout\n        controller: root\n\n[FileIconEntry@Widget]:\n    locked: False\n    path: ctx.path\n    selected: self.path in ctx.controller().selection\n    size_hint: None, None\n\n    on_touch_down: self.collide_point(*args[1].pos) and ctx.controller().entry_touched(self, args[1])\n    on_touch_up: self.collide_point(*args[1].pos) and ctx.controller().entry_released(self, args[1])\n    size: '100dp', '100dp'\n\n    canvas:\n        Color:\n            rgba: 1, 1, 1, 1 if self.selected else 0\n        BorderImage:\n            border: 8, 8, 8, 8\n            pos: root.pos\n            size: root.size\n            source: 'atlas://data/images/defaulttheme/filechooser_selected'\n\n    Image:\n        size: '48dp', '48dp'\n        source: 'atlas://data/images/defaulttheme/filechooser_%s' % ('folder' if ctx.isdir else 'file')\n        pos: root.x + dp(24), root.y + dp(40)\n    Label:\n        text: ctx.name\n        text_size: (root.width, self.height)\n        halign: 'center'\n        shorten: True\n        size: '100dp', '16dp'\n        pos: root.x, root.y + dp(16)\n\n    Label:\n        text: '{}'.format(ctx.get_nice_size())\n        font_size: '11sp'\n        color: .8, .8, .8, 1\n        size: '100dp', '16sp'\n        pos: root.pos\n        halign: 'center'\n\n<FileChooserProgress>:\n    pos_hint: {'x': 0, 'y': 0}\n    canvas:\n        Color:\n            rgba: 0, 0, 0, .8\n        Rectangle:\n            pos: self.pos\n            size: self.size\n    Label:\n        pos_hint: {'x': .2, 'y': .6}\n        size_hint: .6, .2\n        text: 'Opening %s' % root.path\n    FloatLayout:\n        pos_hint: {'x': .2, 'y': .4}\n        size_hint: .6, .2\n        ProgressBar:\n            id: pb\n            pos_hint: {'x': 0, 'center_y': .5}\n            max: root.total\n            value: root.index\n        Label:\n            pos_hint: {'x': 0}\n            text: '%d / %d' % (root.index, root.total)\n            size_hint_y: None\n            height: self.texture_size[1]\n            y: pb.center_y - self.height - 8\n            font_size: '13sp'\n            color: (.8, .8, .8, .8)\n\n    AnchorLayout:\n        pos_hint: {'x': .2, 'y': .2}\n        size_hint: .6, .2\n\n        Button:\n            text: 'Cancel'\n            size_hint: None, None\n            size: 150, 44\n            on_release: root.cancel()\n\n\n\n# Switch widget\n<Switch>:\n    active_norm_pos: max(0., min(1., (int(self.active) + self.touch_distance / sp(41))))\n    canvas:\n        Color:\n            rgb: 1, 1, 1\n        Rectangle:\n            source: 'atlas://data/images/defaulttheme/switch-background{}'.format('_disabled' if self.disabled else '')\n            size: sp(83), sp(32)\n            pos: int(self.center_x - sp(41)), int(self.center_y - sp(16))\n        Rectangle:\n            source: 'atlas://data/images/defaulttheme/switch-button{}'.format('_disabled' if self.disabled else '')\n            size: sp(43), sp(32)\n            pos: int(self.center_x - sp(41) + self.active_norm_pos * sp(41)), int(self.center_y - sp(16))\n\n\n# ModalView widget\n<ModalView>:\n    canvas:\n        Color:\n            rgba: root.background_color[:3] + [root.background_color[-1] * self._anim_alpha]\n        Rectangle:\n            size: self._window.size if self._window else (0, 0)\n\n        Color:\n            rgb: 1, 1, 1\n        BorderImage:\n            source: root.background\n            border: root.border\n            pos: self.pos\n            size: self.size\n\n\n# Popup widget\n<Popup>:\n    _container: container\n    GridLayout:\n        padding: '12dp'\n        cols: 1\n        size_hint: None, None\n        pos: root.pos\n        size: root.size\n\n        Label:\n            text: root.title\n            color: root.title_color\n            size_hint_y: None\n            height: self.texture_size[1] + dp(16)\n            text_size: self.width - dp(16), None\n            font_size: root.title_size\n            font_name: root.title_font\n            halign: root.title_align\n\n        Widget:\n            size_hint_y: None\n            height: dp(4)\n            canvas:\n                Color:\n                    rgba: root.separator_color\n                Rectangle:\n                    pos: self.x, self.y + root.separator_height / 2.\n                    size: self.width, root.separator_height\n\n        BoxLayout:\n            id: container\n\n# =============================================================================\n# Spinner widget\n# =============================================================================\n\n<SpinnerOption>:\n    size_hint_y: None\n    height: '48dp'\n\n<Spinner>:\n    background_normal: 'atlas://data/images/defaulttheme/spinner'\n    background_disabled_normal: 'atlas://data/images/defaulttheme/spinner_disabled'\n    background_down: 'atlas://data/images/defaulttheme/spinner_pressed'\n\n# =============================================================================\n# ActionBar widget\n# =============================================================================\n\n<ActionBar>:\n    height: '48dp'\n    size_hint_y: None\n    spacing: '4dp'\n    canvas:\n        Color:\n            rgba: self.background_color\n        BorderImage:\n            border: root.border\n            pos: self.pos\n            size: self.size\n            source: self.background_image\n\n<ActionView>:\n    orientation: 'horizontal'\n    canvas:\n        Color:\n            rgba: self.background_color\n        BorderImage:\n            pos: self.pos\n            size: self.size\n            source: self.background_image\n\n<ActionSeparator>:\n    size_hint_x: None\n    minimum_width: '2sp'\n    width: self.minimum_width\n    canvas:\n        Rectangle:\n            pos: self.x, self.y + sp(4)\n            size: self.width, self.height - sp(8)\n            source: self.background_image\n\n<ActionButton,ActionToggleButton>:\n    background_normal: 'atlas://data/images/defaulttheme/' + ('action_bar' if self.inside_group else 'action_item')\n    background_down: 'atlas://data/images/defaulttheme/action_item_down'\n    size_hint_x: None if not root.inside_group else 1\n    width: [dp(48) if (root.icon and not root.inside_group) else max(dp(48), (self.texture_size[0] + dp(32))), self.size_hint_x][0]\n    color: self.color[:3] + [0 if (root.icon and not root.inside_group) else 1]\n\n    Image:\n        allow_stretch: True\n        opacity: 1 if (root.icon and not root.inside_group) else 0\n        source: root.icon\n        mipmap: root.mipmap\n        pos: root.x + dp(4), root.y + dp(4)\n        size: root.width - dp(8), root.height - sp(8)\n\n<ActionLabel>:\n    size_hint_x: None if not root.inside_group else 1\n    width: self.texture_size[0] + dp(32)\n\n<ActionGroup>:\n    size_hint_x: None\n    width: self.texture_size[0] + dp(32)\n\n<ActionCheck>:\n    background_normal: 'atlas://data/images/defaulttheme/action_bar' if self.inside_group else 'atlas://data/images/defaulttheme/action_item'\n\n<ActionPreviousImage@Image>:\n    temp_width: 0\n    temp_height: 0\n\n<ActionPreviousButton@Button>:\n    background_normal: 'atlas://data/images/defaulttheme/action_item'\n    background_down: 'atlas://data/images/defaulttheme/action_item_down'\n\n<ActionPrevious>:\n    size_hint_x: 1\n    minimum_width: layout.minimum_width + min(sp(100), title.width)\n    important: True\n    GridLayout:\n        id: layout\n        rows: 1\n        pos: root.pos\n        size_hint_x: None\n        width: self.minimum_width\n        ActionPreviousButton:\n            on_press: root.dispatch('on_press')\n            on_release: root.dispatch('on_release')\n            size_hint_x: None\n            width: prevlayout.width\n            GridLayout:\n                id: prevlayout\n                rows: 1\n                width: self.minimum_width\n                height: self.parent.height\n                pos: self.parent.pos\n                ActionPreviousImage:\n                    id: prev_icon_image\n                    source: root.previous_image\n                    opacity: 1 if root.with_previous else 0\n                    allow_stretch: True\n                    size_hint_x: None\n                    temp_width: root.previous_image_width or dp(prev_icon_image.texture_size[0])\n                    temp_height: root.previous_image_height or dp(prev_icon_image.texture_size[1])\n                    width:\n                        (self.temp_width if self.temp_height <= self.height else \\\n                        self.temp_width * (self.height / self.temp_height)) \\\n                        if self.texture else dp(8)\n                    mipmap: root.mipmap\n                ActionPreviousImage:\n                    id: app_icon_image\n                    source: root.app_icon\n                    allow_stretch: True\n                    size_hint_x: None\n                    temp_width: root.app_icon_width or dp(app_icon_image.texture_size[0])\n                    temp_height: root.app_icon_height or dp(app_icon_image.texture_size[1])\n                    width:\n                        (self.temp_width if self.temp_height <= self.height else \\\n                        self.temp_width * (self.height / self.temp_height)) \\\n                        if self.texture else dp(8)\n                    mipmap: root.mipmap\n                Widget:\n                    size_hint_x: None\n                    width: '5sp'\n    Label:\n        id: title\n        text: root.title\n        text_size: self.size\n        color: root.color\n        shorten: True\n        shorten_from: 'right'\n        halign: 'left'\n        valign: 'middle'\n\n<ActionGroup>:\n    background_normal: 'atlas://data/images/defaulttheme/action_group'\n    background_down: 'atlas://data/images/defaulttheme/action_group_down'\n    background_disabled_normal: 'atlas://data/images/defaulttheme/action_group_disabled'\n    border: 30, 30, 3, 3\n    ActionSeparator:\n        pos: root.pos\n        size: root.separator_width, root.height\n        opacity: 1 if root.use_separator else 0\n        background_image: root.separator_image if root.use_separator else 'action_view'\n\n<ActionOverflow>:\n    border: 3, 3, 3, 3\n    background_normal: 'atlas://data/images/defaulttheme/action_item'\n    background_down: 'atlas://data/images/defaulttheme/action_item_down'\n    background_disabled_normal: 'atlas://data/images/defaulttheme/button_disabled'\n    size_hint_x: None\n    minimum_width: '48sp'\n    width: self.texture_size[0] if self.texture else self.minimum_width\n    canvas.after:\n        Color:\n            rgb: 1, 1, 1\n        Rectangle:\n            pos: root.center_x - sp(16), root.center_y - sp(16)\n            size: sp(32), sp(32)\n            source: root.overflow_image\n\n<ActionDropDown>:\n    auto_width: False\n\n\n# =============================================================================\n# Accordion widget\n# =============================================================================\n\n[AccordionItemTitle@Label]:\n    text: ctx.title\n    normal_background: ctx.item.background_normal if ctx.item.collapse else ctx.item.background_selected\n    disabled_background: ctx.item.background_disabled_normal if ctx.item.collapse else ctx.item.background_disabled_selected\n    canvas.before:\n        Color:\n            rgba: self.disabled_color if self.disabled else self.color\n        BorderImage:\n            source: self.disabled_background if self.disabled else self.normal_background\n            pos: self.pos\n            size: self.size\n        PushMatrix\n        Translate:\n            xy: self.center_x, self.center_y\n        Rotate:\n            angle: 90 if ctx.item.orientation == 'horizontal' else 0\n            axis: 0, 0, 1\n        Translate:\n            xy: -self.center_x, -self.center_y\n    canvas.after:\n        PopMatrix\n\n\n<AccordionItem>:\n    container: container\n    container_title: container_title\n\n    BoxLayout:\n        orientation: root.orientation\n        pos: root.pos\n        BoxLayout:\n            size_hint_x: None if root.orientation == 'horizontal' else 1\n            size_hint_y: None if root.orientation == 'vertical' else 1\n            width: root.min_space if root.orientation == 'horizontal' else 100\n            height: root.min_space if root.orientation == 'vertical' else 100\n            id: container_title\n\n        StencilView:\n            id: sv\n\n            BoxLayout:\n                id: container\n                pos: sv.pos\n                size: root.content_size\n\n\n<ScrollView>:\n    canvas.after:\n        Color:\n            rgba: self._bar_color if (self.do_scroll_y and self.viewport_size[1] > self.height) else [0, 0, 0, 0]\n        Rectangle:\n            pos: (self.right - self.bar_width - self.bar_margin) if self.bar_pos_y == 'right' else (self.x + self.bar_margin), self.y + self.height * self.vbar[0]\n            size: min(self.bar_width, self.width), self.height * self.vbar[1]\n        Color:\n            rgba: self._bar_color if (self.do_scroll_x and self.viewport_size[0] > self.width) else [0, 0, 0, 0]\n        Rectangle:\n            pos: self.x + self.width * self.hbar[0], (self.y + self.bar_margin) if self.bar_pos_x == 'bottom' else (self.top - self.bar_margin - self.bar_width)\n            size: self.width * self.hbar[1], min(self.bar_width, self.height)\n\n\n<CheckBox>:\n    _checkbox_state_image:\n        self.background_checkbox_down \\\n        if self.active else self.background_checkbox_normal\n    _checkbox_disabled_image:\n        self.background_checkbox_disabled_down \\\n        if self.active else self.background_checkbox_disabled_normal\n    _radio_state_image:\n        self.background_radio_down \\\n        if self.active else self.background_radio_normal\n    _radio_disabled_image:\n        self.background_radio_disabled_down \\\n        if self.active else self.background_radio_disabled_normal\n    _checkbox_image:\n        self._checkbox_disabled_image \\\n        if self.disabled else self._checkbox_state_image\n    _radio_image:\n        self._radio_disabled_image \\\n        if self.disabled else self._radio_state_image\n    canvas:\n        Color:\n            rgb: 1, 1, 1\n        Rectangle:\n            source: self._radio_image if self.group else self._checkbox_image\n            size: sp(32), sp(32)\n            pos: int(self.center_x - sp(16)), int(self.center_y - sp(16))\n\n# =============================================================================\n# Screen Manager\n# =============================================================================\n\n<ScreenManager>:\n    canvas.before:\n        StencilPush\n        Rectangle:\n            pos: self.pos\n            size: self.size\n        StencilUse\n    canvas.after:\n        StencilUnUse\n        Rectangle:\n            pos: self.pos\n            size: self.size\n        StencilPop\n"
  },
  {
    "path": "gui/kivy/i18n.py",
    "content": "import gettext\n\nclass _(str):\n\n    observers = set()\n    lang = None\n\n    def __new__(cls, s, *args, **kwargs):\n        if _.lang is None:\n            _.switch_lang('en')\n        t = _.translate(s, *args, **kwargs)\n        o = super(_, cls).__new__(cls, t)\n        o.source_text = s\n        return o\n\n    @staticmethod\n    def translate(s, *args, **kwargs):\n        return _.lang(s).format(args, kwargs)\n\n    @staticmethod\n    def bind(label):\n        try:\n            _.observers.add(label)\n        except:\n            pass\n        # garbage collection\n        new = set()\n        for label in _.observers:\n            try:\n                new.add(label)\n            except:\n                pass\n        _.observers = new\n\n    @staticmethod\n    def switch_lang(lang):\n        # get the right locales directory, and instanciate a gettext\n        from electrum.i18n import LOCALE_DIR\n        locales = gettext.translation('electrum', LOCALE_DIR, languages=[lang], fallback=True)\n        _.lang = locales.gettext\n        for label in _.observers:\n            try:\n                label.text = _(label.text.source_text)\n            except:\n                pass\n"
  },
  {
    "path": "gui/kivy/main.kv",
    "content": "#:import Clock kivy.clock.Clock\n#:import Window kivy.core.window.Window\n#:import Factory kivy.factory.Factory\n#:import _ electrum_gui.kivy.i18n._\n\n\n###########################\n#     Global Defaults\n###########################\n\n<Label>\n    markup: True\n    font_name: 'Roboto'\n    font_size: '16sp'\n    bound: False\n    on_text: if isinstance(self.text, _) and not self.bound: self.bound=True; _.bind(self)\n\n<TextInput>\n    on_focus: app._focused_widget = root\n    font_size: '18sp'\n\n<Button>\n    on_parent: self.MIN_STATE_TIME = 0.1\n\n<ListItemButton>\n    font_size: '12sp'\n\n<Carousel>:\n    canvas.before:\n        Color:\n            rgba: 0.1, 0.1, 0.1, 1\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<ActionView>:\n    canvas.before:\n        Color:\n            rgba: 0.1, 0.1, 0.1, 1\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n\n# Custom Global Widgets\n\n<TopLabel@Label>\n    size_hint_y: None\n    text_size: self.width, None\n    height: self.texture_size[1]\n\n<EmptyLabel@Label>\n    color: (0.8, 0.8, 0.8, 1)\n    size_hint_y: None\n    text_size: self.width, None\n    height: self.texture_size[1]\n\n<VGridLayout@GridLayout>:\n    rows: 1\n    size_hint: 1, None\n    height: self.minimum_height\n\n\n\n<IconButton@Button>:\n    icon: ''\n    AnchorLayout:\n        pos: self.parent.pos\n        size: self.parent.size\n        orientation: 'lr-tb'\n        Image:\n            source: self.parent.parent.icon\n            size_hint_x: None\n            size: '30dp', '30dp'\n\n\n\n#########################\n#       Dialogs\n#########################\n<BoxLabel@BoxLayout>\n    text: ''\n    value: ''\n    size_hint_y: None\n    height: max(lbl1.height, lbl2.height)\n    TopLabel\n        id: lbl1\n        text: root.text\n        pos_hint: {'top':1}\n    TopLabel\n        id: lbl2\n        text: root.value\n\n<OutputItem>\n    address: ''\n    value: ''\n    size_hint_y: None\n    height: max(lbl1.height, lbl2.height)\n    TopLabel\n        id: lbl1\n        text: '[ref=%s]%s[/ref]'%(root.address, root.address)\n        font_size: '6pt'\n        shorten: True\n        size_hint_x: 0.65\n        on_ref_press:\n            app._clipboard.copy(root.address)\n            app.show_info(_('Address copied to clipboard') + ' ' + root.address)\n    TopLabel\n        id: lbl2\n        text: root.value\n        font_size: '6pt'\n        size_hint_x: 0.35\n        halign: 'right'\n\n\n<OutputList>\n    height: self.minimum_height\n    size_hint_y: None\n    cols: 1\n    spacing: '10dp'\n    padding: '10dp'\n    canvas.before:\n        Color:\n            rgb: .3, .3, .3\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<RefLabel@TopLabel>\n    font_size: '6pt'\n    name: ''\n    data: ''\n    text: self.data\n    touched: False\n    padding: '10dp', '10dp'\n    on_touch_down:\n        touch = args[1]\n        if self.collide_point(*touch.pos): app.on_ref_label(self, touch)\n        else: self.touched = False\n    canvas.before:\n        Color:\n            rgb: .3, .3, .3\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<TxHashLabel@RefLabel>\n    data: ''\n    text: ' '.join(map(''.join, zip(*[iter(self.data)]*4))) if self.data else ''\n\n<InfoBubble>\n    size_hint: None, None\n    width: '270dp' if root.fs else min(self.width, dp(270))\n    height: self.width if self.fs else (lbl.texture_size[1] + dp(27))\n    BoxLayout:\n        padding: '5dp' if root.fs else 0\n        Widget:\n            size_hint: None, 1\n            width: '4dp' if root.fs else '2dp'\n        Image:\n            id: img\n            source: root.icon\n            mipmap: True\n            size_hint: None, 1\n            width: (root.width - dp(20)) if root.fs  else (0 if not root.icon else '32dp')\n        Widget:\n            size_hint_x: None\n            width: '5dp'\n        Label:\n            id: lbl\n            markup: True\n            font_size: '12sp'\n            text: root.message\n            text_size: self.width, None\n            valign: 'middle'\n            size_hint: 1, 1\n            width: 0 if root.fs else (root.width - img.width)\n\n\n<SendReceiveBlueBottom@GridLayout>\n    item_height: dp(42)\n    foreground_color: .843, .914, .972, 1\n    cols: 1\n    padding: '12dp', 0\n    canvas.before:\n        Color:\n            rgba: 0.192, .498, 0.745, 1\n        BorderImage:\n            source: 'atlas://gui/kivy/theming/light/card_bottom'\n            size: self.size\n            pos: self.pos\n\n\n<AddressFilter@GridLayout>\n\titem_height: dp(42)\n\titem_width: dp(60)\n\tforeground_color: .843, .914, .972, 1\n\tcols: 1\n\tcanvas.before:\n\t\tColor:\n\t\t\trgba: 0.192, .498, 0.745, 1\n\t\tBorderImage:\n\t\t\tsource: 'atlas://gui/kivy/theming/light/card_bottom'\n\t\t\tsize: self.size\n\t\t\tpos: self.pos\n\n<SearchBox@GridLayout>\n\titem_height: dp(42)\n\tforeground_color: .843, .914, .972, 1\n\tcols: 1\n\tpadding: '12dp', 0\n\tcanvas.before:\n\t\tColor:\n\t\t\trgba: 0.192, .498, 0.745, 1\n        BorderImage:\n            source: 'atlas://gui/kivy/theming/light/card_bottom'\n            size: self.size\n            pos: self.pos\n\n<CardSeparator@Widget>\n    size_hint: 1, None\n    height: dp(1)\n    color: .909, .909, .909, 1\n    canvas:\n        Color:\n            rgba: root.color if root.color else (0, 0, 0, 0)\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<CardItem@ToggleButtonBehavior+BoxLayout>\n    size_hint: 1, None\n    height: '65dp'\n    group: 'requests'\n    padding: dp(12)\n    spacing: dp(5)\n    screen: None\n    on_release:\n        self.screen.show_menu(args[0]) if self.state == 'down' else self.screen.hide_menu()\n    canvas.before:\n        Color:\n            rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 1)\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<BlueButton@Button>:\n    background_color: 1, .585, .878, 0\n    halign: 'left'\n    text_size: (self.width-10, None)\n    size_hint: 0.5, None\n    default_text: ''\n    text: self.default_text\n    padding: '5dp', '5dp'\n    height: '40dp'\n    text_color: self.foreground_color\n    disabled_color: 1, 1, 1, 1\n    foreground_color: 1, 1, 1, 1\n    canvas.before:\n        Color:\n            rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color\n        Rectangle:\n            size: self.size\n            pos: self.pos\n\n<AddressButton@Button>:\n\tbackground_color: 1, .585, .878, 0\n\thalign: 'center'\n\ttext_size: (self.width, None)\n\tshorten: True\n\tsize_hint: 0.5, None\n\tdefault_text: ''\n\ttext: self.default_text\n\tpadding: '5dp', '5dp'\n\theight: '40dp'\n\ttext_color: self.foreground_color\n\tdisabled_color: 1, 1, 1, 1\n\tforeground_color: 1, 1, 1, 1\n\tcanvas.before:\n\t\tColor:\n\t\t\trgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color\n\t\tRectangle:\n\t\t\tsize: self.size\n\t\t\tpos: self.pos\n\n<KButton@Button>:\n    size_hint: 1, None\n    height: '48dp'\n    on_release:\n        self.parent.update_amount(self.text)\n\n\n<StripLayout>\n    padding: 0, 0, 0, 0\n\n<TabbedPanelStrip>:\n    on_parent:\n        if self.parent: self.parent.bar_width = 0\n        if self.parent: self.parent.scroll_x = 0.5\n\n\n<TabbedCarousel>\n    carousel: carousel\n    do_default_tab: False\n    Carousel:\n        scroll_timeout: 250\n        scroll_distance: '100dp'\n        anim_type: 'out_quart'\n        min_move: .05\n        anim_move_duration: .1\n        anim_cancel_duration: .54\n        on_index: root.on_index(*args)\n        id: carousel\n\n\n\n<CleanHeader@TabbedPanelHeader>\n    border: 16, 0, 16, 0\n    markup: False\n    text_size: self.size\n    halign: 'center'\n    valign: 'middle'\n    bold: True\n    font_size: '12.5sp'\n    background_normal: 'atlas://gui/kivy/theming/light/tab_btn'\n    background_down: 'atlas://gui/kivy/theming/light/tab_btn_pressed'\n\n\n<ColoredLabel@Label>:\n    font_size: '48sp'\n    color: (.6, .6, .6, 1)\n    canvas.before:\n        Color:\n            rgb: (.9, .9, .9)\n        Rectangle:\n            pos: self.x + sp(2), self.y + sp(2)\n            size: self.width - sp(4), self.height - sp(4)\n\n\n<SettingsItem@ButtonBehavior+BoxLayout>\n    orientation: 'vertical'\n    title: ''\n    description: ''\n    size_hint: 1, None\n    height: '60dp'\n    canvas.before:\n        Color:\n            rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0)\n        Rectangle:\n            size: self.size\n            pos: self.pos\n    on_release:\n        Clock.schedule_once(self.action)\n    Widget\n    TopLabel:\n        id: title\n        text: self.parent.title\n        bold: True\n        halign: 'left'\n    TopLabel:\n        text: self.parent.description\n        color: 0.8, 0.8, 0.8, 1\n        halign: 'left'\n    Widget\n\n\n\n\n<ScreenTabs@Screen>\n    TabbedCarousel:\n        id: panel\n        tab_height: '48dp'\n        tab_width: panel.width/3\n        strip_border: 0, 0, 0, 0\n        InvoicesScreen:\n            id: invoices_screen\n            tab: invoices_tab\n        SendScreen:\n            id: send_screen\n            tab: send_tab\n        HistoryScreen:\n            id: history_screen\n            tab: history_tab\n        ReceiveScreen:\n            id: receive_screen\n            tab: receive_tab\n        AddressScreen:\n            id: address_screen\n            tab: address_tab\n        CleanHeader:\n            id: invoices_tab\n            text: _('Invoices')\n            slide: 0\n        CleanHeader:\n            id: send_tab\n            text: _('Send')\n            slide: 1\n        CleanHeader:\n            id: history_tab\n            text: _('History')\n            slide: 2\n        CleanHeader:\n            id: receive_tab\n            text: _('Receive')\n            slide: 3\n        CleanHeader:\n            id: address_tab\n            text: _('Addresses')\n            slide: 4\n\n\n<ActionOvrButton@ActionButton>\n    #on_release:\n        # fixme: the following line was commented out because it does no seem to do what it is intended\n        # Clock.schedule_once(lambda dt: self.parent.parent.dismiss() if self.parent else None, 0.05)\n    on_press:\n        Clock.schedule_once(lambda dt: app.popup_dialog(self.name), 0.05)\n        self.state = 'normal'\n\n\nBoxLayout:\n    orientation: 'vertical'\n\n    canvas.before:\n        Color:\n            rgb: .6, .6, .6\n        Rectangle:\n            size: self.size\n            source: 'gui/kivy/data/background.png'\n\n    ActionBar:\n\n        ActionView:\n            id: av\n            ActionPrevious:\n                app_icon: 'atlas://gui/kivy/theming/light/logo'\n                app_icon_width: '100dp'\n                with_previous: False\n                size_hint_x: None\n                on_release: app.popup_dialog('network')\n\n            ActionButton:\n                id: action_status\n                important: True\n                size_hint: 1, 1\n                bold: True\n                color: 0.7, 0.7, 0.7, 1\n                text: app.status\n                font_size: '22dp'\n                #minimum_width: '1dp'\n                on_release: app.popup_dialog('status')\n\n            ActionOverflow:\n                id: ao\n                ActionOvrButton:\n                    name: 'about'\n                    text: _('About')\n                ActionOvrButton:\n                    name: 'wallets'\n                    text: _('Wallets')\n                ActionOvrButton:\n                    name: 'network'\n                    text: _('Network')\n                ActionOvrButton:\n                    name: 'settings'\n                    text: _('Settings')\n                    on_parent:\n                        # when widget overflow drop down is shown, adjust the width\n                        parent = args[1]\n                        if parent: ao._dropdown.width = sp(200)\n    ScreenManager:\n        id: manager\n        ScreenTabs:\n            id: tabs\n"
  },
  {
    "path": "gui/kivy/main_window.py",
    "content": "import re\nimport os\nimport sys\nimport time\nimport datetime\nimport traceback\nfrom decimal import Decimal\nimport threading\n\nimport electrum\nfrom electrum.bitcoin import TYPE_ADDRESS\nfrom electrum import WalletStorage, Wallet\nfrom electrum_gui.kivy.i18n import _\nfrom electrum.paymentrequest import InvoiceStore\nfrom electrum.util import profiler, InvalidPassword\nfrom electrum.plugins import run_hook\nfrom electrum.util import format_satoshis, format_satoshis_plain\nfrom electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED\n\nfrom kivy.app import App\nfrom kivy.core.window import Window\nfrom kivy.logger import Logger\nfrom kivy.utils import platform\nfrom kivy.properties import (OptionProperty, AliasProperty, ObjectProperty,\n                             StringProperty, ListProperty, BooleanProperty, NumericProperty)\nfrom kivy.cache import Cache\nfrom kivy.clock import Clock\nfrom kivy.factory import Factory\nfrom kivy.metrics import inch\nfrom kivy.lang import Builder\n\n## lazy imports for factory so that widgets can be used in kv\n#Factory.register('InstallWizard', module='electrum_gui.kivy.uix.dialogs.installwizard')\n#Factory.register('InfoBubble', module='electrum_gui.kivy.uix.dialogs')\n#Factory.register('OutputList', module='electrum_gui.kivy.uix.dialogs')\n#Factory.register('OutputItem', module='electrum_gui.kivy.uix.dialogs')\n\nfrom .uix.dialogs.installwizard import InstallWizard\nfrom .uix.dialogs import InfoBubble\nfrom .uix.dialogs import OutputList, OutputItem\n\n#from kivy.core.window import Window\n#Window.softinput_mode = 'below_target'\n\n# delayed imports: for startup speed on android\nnotification = app = ref = None\nutil = False\n\n# register widget cache for keeping memory down timeout to forever to cache\n# the data\nCache.register('electrum_widgets', timeout=0)\n\nfrom kivy.uix.screenmanager import Screen\nfrom kivy.uix.tabbedpanel import TabbedPanel\nfrom kivy.uix.label import Label\nfrom kivy.core.clipboard import Clipboard\n\nFactory.register('TabbedCarousel', module='electrum_gui.kivy.uix.screens')\n\n# Register fonts without this you won't be able to use bold/italic...\n# inside markup.\nfrom kivy.core.text import Label\nLabel.register('Roboto',\n               'gui/kivy/data/fonts/Roboto.ttf',\n               'gui/kivy/data/fonts/Roboto.ttf',\n               'gui/kivy/data/fonts/Roboto-Bold.ttf',\n               'gui/kivy/data/fonts/Roboto-Bold.ttf')\n\n\nfrom electrum.util import base_units\n\n\nclass ElectrumWindow(App):\n\n    electrum_config = ObjectProperty(None)\n    language = StringProperty('en')\n\n    # properties might be updated by the network\n    num_blocks = NumericProperty(0)\n    num_nodes = NumericProperty(0)\n    server_host = StringProperty('')\n    server_port = StringProperty('')\n    num_chains = NumericProperty(0)\n    blockchain_name = StringProperty('')\n    blockchain_checkpoint = NumericProperty(0)\n\n    auto_connect = BooleanProperty(False)\n    def on_auto_connect(self, instance, x):\n        host, port, protocol, proxy, auto_connect = self.network.get_parameters()\n        self.network.set_parameters(host, port, protocol, proxy, self.auto_connect)\n    def toggle_auto_connect(self, x):\n        self.auto_connect = not self.auto_connect\n\n    def choose_server_dialog(self, popup):\n        from .uix.dialogs.choice_dialog import ChoiceDialog\n        protocol = 's'\n        def cb2(host):\n            from electrum.bitcoin import NetworkConstants\n            pp = servers.get(host, NetworkConstants.DEFAULT_PORTS)\n            port = pp.get(protocol, '')\n            popup.ids.host.text = host\n            popup.ids.port.text = port\n        servers = self.network.get_servers()\n        ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open()\n\n    def choose_blockchain_dialog(self, dt):\n        from .uix.dialogs.choice_dialog import ChoiceDialog\n        chains = self.network.get_blockchains()\n        def cb(name):\n            for index, b in self.network.blockchains.items():\n                if name == self.network.get_blockchain_name(b):\n                    self.network.follow_chain(index)\n                    #self.block\n        names = [self.network.blockchains[b].get_name() for b in chains]\n        if len(names) >1:\n            ChoiceDialog(_('Choose your chain'), names, '', cb).open()\n\n    use_rbf = BooleanProperty(False)\n    def on_use_rbf(self, instance, x):\n        self.electrum_config.set_key('use_rbf', self.use_rbf, True)\n\n    use_change = BooleanProperty(False)\n    def on_use_change(self, instance, x):\n        self.electrum_config.set_key('use_change', self.use_change, True)\n\n    use_unconfirmed = BooleanProperty(False)\n    def on_use_unconfirmed(self, instance, x):\n        self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True)\n\n    def set_URI(self, uri):\n        self.switch_to('send')\n        self.send_screen.set_URI(uri)\n\n    def on_new_intent(self, intent):\n        if intent.getScheme() != 'bitcoin':\n            return\n        uri = intent.getDataString()\n        self.set_URI(uri)\n\n    def on_language(self, instance, language):\n        Logger.info('language: {}'.format(language))\n        _.switch_lang(language)\n\n    def update_history(self, *dt):\n        if self.history_screen:\n            self.history_screen.update()\n\n    def on_quotes(self, d):\n        Logger.info(\"on_quotes\")\n        self._trigger_update_history()\n\n    def on_history(self, d):\n        Logger.info(\"on_history\")\n        self._trigger_update_history()\n\n    def _get_bu(self):\n        return self.electrum_config.get('base_unit', 'BTCP')\n\n    def _set_bu(self, value):\n        assert value in base_units.keys()\n        self.electrum_config.set_key('base_unit', value, True)\n        self._trigger_update_status()\n        self._trigger_update_history()\n\n    base_unit = AliasProperty(_get_bu, _set_bu)\n    status = StringProperty('')\n    fiat_unit = StringProperty('')\n\n    def on_fiat_unit(self, a, b):\n        self._trigger_update_history()\n\n    def decimal_point(self):\n        return base_units[self.base_unit]\n\n    def btc_to_fiat(self, amount_str):\n        if not amount_str:\n            return ''\n        rate = self.fx.exchange_rate()\n        if not rate:\n            return ''\n        fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)\n        return \"{:.2f}\".format(fiat_amount).rstrip('0').rstrip('.')\n\n    def fiat_to_btc(self, fiat_amount):\n        if not fiat_amount:\n            return ''\n        rate = self.fx.exchange_rate()\n        if not rate:\n            return ''\n        satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))\n        return format_satoshis_plain(satoshis, self.decimal_point())\n\n    def get_amount(self, amount_str):\n        a, u = amount_str.split()\n        assert u == self.base_unit\n        try:\n            x = Decimal(a)\n        except:\n            return None\n        p = pow(10, self.decimal_point())\n        return int(p * x)\n\n\n    _orientation = OptionProperty('landscape',\n                                 options=('landscape', 'portrait'))\n\n    def _get_orientation(self):\n        return self._orientation\n\n    orientation = AliasProperty(_get_orientation,\n                                None,\n                                bind=('_orientation',))\n    '''Tries to ascertain the kind of device the app is running on.\n    Cane be one of `tablet` or `phone`.\n\n    :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape'\n    '''\n\n    _ui_mode = OptionProperty('phone', options=('tablet', 'phone'))\n\n    def _get_ui_mode(self):\n        return self._ui_mode\n\n    ui_mode = AliasProperty(_get_ui_mode,\n                            None,\n                            bind=('_ui_mode',))\n    '''Defines tries to ascertain the kind of device the app is running on.\n    Cane be one of `tablet` or `phone`.\n\n    :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone'\n    '''\n\n    def __init__(self, **kwargs):\n        # initialize variables\n        self._clipboard = Clipboard\n        self.info_bubble = None\n        self.nfcscanner = None\n        self.tabs = None\n        self.is_exit = False\n        self.wallet = None\n\n        App.__init__(self)#, **kwargs)\n\n        title = _('Bitcoin Private Electrum')\n        self.electrum_config = config = kwargs.get('config', None)\n        self.language = config.get('language', 'en')\n        self.network = network = kwargs.get('network', None)\n        if self.network:\n            self.num_blocks = self.network.get_local_height()\n            self.num_nodes = len(self.network.get_interfaces())\n            host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()\n            self.server_host = host\n            self.server_port = port\n            self.auto_connect = auto_connect\n            self.proxy_config = proxy_config if proxy_config else {}\n\n        self.plugins = kwargs.get('plugins', [])\n        self.gui_object = kwargs.get('gui_object', None)\n        self.daemon = self.gui_object.daemon\n        self.fx = self.daemon.fx\n\n        self.use_rbf = config.get('use_rbf', True)\n        self.use_change = config.get('use_change', True)\n        self.use_unconfirmed = not config.get('confirmed_only', False)\n\n        # create triggers so as to minimize updation a max of 2 times a sec\n        self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)\n        self._trigger_update_status = Clock.create_trigger(self.update_status, .5)\n        self._trigger_update_history = Clock.create_trigger(self.update_history, .5)\n        self._trigger_update_interfaces = Clock.create_trigger(self.update_interfaces, .5)\n        # cached dialogs\n        self._settings_dialog = None\n        self._password_dialog = None\n\n    def wallet_name(self):\n        return os.path.basename(self.wallet.storage.path) if self.wallet else ' '\n\n    def on_pr(self, pr):\n        if pr.verify(self.wallet.contacts):\n            key = self.wallet.invoices.add(pr)\n            if self.invoices_screen:\n                self.invoices_screen.update()\n            status = self.wallet.invoices.get_status(key)\n            if status == PR_PAID:\n                self.show_error(\"Invoice already paid\")\n                self.send_screen.do_clear()\n            else:\n                if pr.has_expired():\n                    self.show_error(_('Payment request has expired.'))\n                else:\n                    self.switch_to('send')\n                    self.send_screen.set_request(pr)\n        else:\n            self.show_error(\"Invoice error:\" + pr.error)\n            self.send_screen.do_clear()\n\n    def on_qr(self, data):\n        from electrum.bitcoin import base_decode, is_address\n        data = data.strip()\n        if is_address(data):\n            self.set_URI(data)\n            return\n        if data.startswith('bitcoin:'):\n            self.set_URI(data)\n            return\n        # try to decode transaction\n        from electrum.transaction import Transaction\n        from electrum.util import bh2u\n        try:\n            text = bh2u(base_decode(data, None, base=43))\n            tx = Transaction(text)\n            tx.deserialize()\n        except:\n            tx = None\n        if tx:\n            self.tx_dialog(tx)\n            return\n        # show error\n        self.show_error(\"Unable to decode QR data\")\n\n    def update_tab(self, name):\n        s = getattr(self, name + '_screen', None)\n        if s:\n            s.update()\n\n    @profiler\n    def update_tabs(self):\n        for tab in ['invoices', 'send', 'history', 'receive', 'address']:\n            self.update_tab(tab)\n\n    def switch_to(self, name):\n        s = getattr(self, name + '_screen', None)\n        if s is None:\n            s = self.tabs.ids[name + '_screen']\n            s.load_screen()\n        panel = self.tabs.ids.panel\n        tab = self.tabs.ids[name + '_tab']\n        panel.switch_to(tab)\n\n    def show_request(self, addr):\n        self.switch_to('receive')\n        self.receive_screen.screen.address = addr\n\n    def show_pr_details(self, req, status, is_invoice):\n        from electrum.util import format_time\n        requestor = req.get('requestor')\n        exp = req.get('exp')\n        memo = req.get('memo')\n        amount = req.get('amount')\n        fund = req.get('fund')\n        popup = Builder.load_file('gui/kivy/uix/ui_screens/invoice.kv')\n        popup.is_invoice = is_invoice\n        popup.amount = amount\n        popup.requestor = requestor if is_invoice else req.get('address')\n        popup.exp = format_time(exp) if exp else ''\n        popup.description = memo if memo else ''\n        popup.signature = req.get('signature', '')\n        popup.status = status\n        popup.fund = fund if fund else 0\n        txid = req.get('txid')\n        popup.tx_hash = txid or ''\n        popup.on_open = lambda: popup.ids.output_list.update(req.get('outputs', []))\n        popup.export = self.export_private_keys\n        popup.open()\n\n    def show_addr_details(self, req, status):\n        from electrum.util import format_time\n        fund = req.get('fund')\n        isaddr = 'y'\n        popup = Builder.load_file('gui/kivy/uix/ui_screens/invoice.kv')\n        popup.isaddr = isaddr\n        popup.is_invoice = False\n        popup.status = status\n        popup.requestor = req.get('address')\n        popup.fund = fund if fund else 0\n        popup.export = self.export_private_keys\n        popup.open()\n\n    def qr_dialog(self, title, data, show_text=False):\n        from .uix.dialogs.qr_dialog import QRDialog\n        popup = QRDialog(title, data, show_text)\n        popup.open()\n\n    def scan_qr(self, on_complete):\n        if platform != 'android':\n            return\n        from jnius import autoclass, cast\n        from android import activity\n        PythonActivity = autoclass('org.kivy.android.PythonActivity')\n        SimpleScannerActivity = autoclass(\"org.electrum.qr.SimpleScannerActivity\")\n        Intent = autoclass('android.content.Intent')\n        intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)\n\n        def on_qr_result(requestCode, resultCode, intent):\n            if resultCode == -1:  # RESULT_OK:\n                #  this doesn't work due to some bug in jnius:\n                # contents = intent.getStringExtra(\"text\")\n                String = autoclass(\"java.lang.String\")\n                contents = intent.getStringExtra(String(\"text\"))\n                on_complete(contents)\n        activity.bind(on_activity_result=on_qr_result)\n        PythonActivity.mActivity.startActivityForResult(intent, 0)\n\n    def do_share(self, data, title):\n        if platform != 'android':\n            return\n        from jnius import autoclass, cast\n        JS = autoclass('java.lang.String')\n        Intent = autoclass('android.content.Intent')\n        sendIntent = Intent()\n        sendIntent.setAction(Intent.ACTION_SEND)\n        sendIntent.setType(\"text/plain\")\n        sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data))\n        PythonActivity = autoclass('org.kivy.android.PythonActivity')\n        currentActivity = cast('android.app.Activity', PythonActivity.mActivity)\n        it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title)))\n        currentActivity.startActivity(it)\n\n    def build(self):\n        return Builder.load_file('gui/kivy/main.kv')\n\n    def _pause(self):\n        if platform == 'android':\n            # move activity to back\n            from jnius import autoclass\n            python_act = autoclass('org.kivy.android.PythonActivity')\n            mActivity = python_act.mActivity\n            mActivity.moveTaskToBack(True)\n\n    def on_start(self):\n        ''' This is the start point of the kivy ui\n        '''\n        import time\n        Logger.info('Time to on_start: {} <<<<<<<<'.format(time.clock()))\n        win = Window\n        win.bind(size=self.on_size, on_keyboard=self.on_keyboard)\n        win.bind(on_key_down=self.on_key_down)\n        #win.softinput_mode = 'below_target'\n        self.on_size(win, win.size)\n        self.init_ui()\n        self.load_wallet_by_name(self.electrum_config.get_wallet_path())\n        # init plugins\n        run_hook('init_kivy', self)\n        # fiat currency\n        self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''\n        # default tab\n        self.switch_to('history')\n        # bind intent for bitcoin: URI scheme\n        if platform == 'android':\n            from android import activity\n            from jnius import autoclass\n            PythonActivity = autoclass('org.kivy.android.PythonActivity')\n            mactivity = PythonActivity.mActivity\n            self.on_new_intent(mactivity.getIntent())\n            activity.bind(on_new_intent=self.on_new_intent)\n        # connect callbacks\n        if self.network:\n            interests = ['updated', 'status', 'new_transaction', 'verified', 'interfaces']\n            self.network.register_callback(self.on_network_event, interests)\n            self.network.register_callback(self.on_quotes, ['on_quotes'])\n            self.network.register_callback(self.on_history, ['on_history'])\n        # URI passed in config\n        uri = self.electrum_config.get('url')\n        if uri:\n            self.set_URI(uri)\n\n\n    def get_wallet_path(self):\n        if self.wallet:\n            return self.wallet.storage.path\n        else:\n            return ''\n\n    def on_wizard_complete(self, instance, wallet):\n        if wallet:\n            wallet.start_threads(self.daemon.network)\n            self.daemon.add_wallet(wallet)\n            self.load_wallet(wallet)\n        self.on_resume()\n\n    def load_wallet_by_name(self, path):\n        if not path:\n            return\n        wallet = self.daemon.load_wallet(path, None)\n        if wallet:\n            if wallet != self.wallet:\n                self.stop_wallet()\n                self.load_wallet(wallet)\n                self.on_resume()\n        else:\n            Logger.debug('Electrum: Wallet not found. Launching install wizard')\n            storage = WalletStorage(path)\n            wizard = Factory.InstallWizard(self.electrum_config, storage)\n            wizard.bind(on_wizard_complete=self.on_wizard_complete)\n            action = wizard.storage.get_action()\n            wizard.run(action)\n\n    def on_stop(self):\n        self.stop_wallet()\n\n    def stop_wallet(self):\n        if self.wallet:\n            self.daemon.stop_wallet(self.wallet.storage.path)\n            self.wallet = None\n\n    def on_key_down(self, instance, key, keycode, codepoint, modifiers):\n        if 'ctrl' in modifiers:\n            # q=24 w=25\n            if keycode in (24, 25):\n                self.stop()\n            elif keycode == 27:\n                # r=27\n                # force update wallet\n                self.update_wallet()\n            elif keycode == 112:\n                # pageup\n                #TODO move to next tab\n                pass\n            elif keycode == 117:\n                # pagedown\n                #TODO move to prev tab\n                pass\n        #TODO: alt+tab_number to activate the particular tab\n\n    def on_keyboard(self, instance, key, keycode, codepoint, modifiers):\n        if key == 27 and self.is_exit is False:\n            self.is_exit = True\n            self.show_info(_('Press again to exit'))\n            return True\n        # override settings button\n        if key in (319, 282): #f1/settings button on android\n            #self.gui.main_gui.toggle_settings(self)\n            return True\n\n    def settings_dialog(self):\n        from .uix.dialogs.settings import SettingsDialog\n        if self._settings_dialog is None:\n            self._settings_dialog = SettingsDialog(self)\n        self._settings_dialog.update()\n        self._settings_dialog.open()\n\n    def popup_dialog(self, name):\n        if name == 'settings':\n            self.settings_dialog()\n        elif name == 'wallets':\n            from .uix.dialogs.wallets import WalletDialog\n            d = WalletDialog()\n            d.open()\n        else:\n            popup = Builder.load_file('gui/kivy/uix/ui_screens/'+name+'.kv')\n            popup.open()\n\n    @profiler\n    def init_ui(self):\n        ''' Initialize The Ux part of electrum. This function performs the basic\n        tasks of setting up the ui.\n        '''\n        #from weakref import ref\n\n        self.funds_error = False\n        # setup UX\n        self.screens = {}\n\n        #setup lazy imports for mainscreen\n        Factory.register('AnimatedPopup',\n                         module='electrum_gui.kivy.uix.dialogs')\n        Factory.register('QRCodeWidget',\n                         module='electrum_gui.kivy.uix.qrcodewidget')\n\n        # preload widgets. Remove this if you want to load the widgets on demand\n        #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup())\n        #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget())\n\n        # load and focus the ui\n        self.root.manager = self.root.ids['manager']\n\n        self.history_screen = None\n        self.contacts_screen = None\n        self.send_screen = None\n        self.invoices_screen = None\n        self.receive_screen = None\n        self.requests_screen = None\n        self.address_screen = None\n        self.icon = \"icons/electrum.png\"\n        self.tabs = self.root.ids['tabs']\n\n    def update_interfaces(self, dt):\n        self.num_nodes = len(self.network.get_interfaces())\n        self.num_chains = len(self.network.get_blockchains())\n        chain = self.network.blockchain()\n        self.blockchain_checkpoint = chain.get_checkpoint()\n        self.blockchain_name = chain.get_name()\n        if self.network.interface:\n            self.server_host = self.network.interface.host\n\n    def on_network_event(self, event, *args):\n        Logger.info('network event: '+ event)\n        if event == 'interfaces':\n            self._trigger_update_interfaces()\n        elif event == 'updated':\n            self._trigger_update_wallet()\n            self._trigger_update_status()\n        elif event == 'status':\n            self._trigger_update_status()\n        elif event == 'new_transaction':\n            self._trigger_update_wallet()\n        elif event == 'verified':\n            self._trigger_update_wallet()\n\n    @profiler\n    def load_wallet(self, wallet):\n        self.wallet = wallet\n        self.update_wallet()\n        # Once GUI has been initialized check if we want to announce something\n        # since the callback has been called before the GUI was initialized\n        if self.receive_screen:\n            self.receive_screen.clear()\n        self.update_tabs()\n        run_hook('load_wallet', wallet, self)\n\n    def update_status(self, *dt):\n        self.num_blocks = self.network.get_local_height()\n        if not self.wallet:\n            self.status = _(\"No Wallet\")\n            return\n        if self.network is None or not self.network.is_running():\n            status = _(\"Offline\")\n        elif self.network.is_connected():\n            server_height = self.network.get_server_height()\n            server_lag = self.network.get_local_height() - server_height\n            if not self.wallet.up_to_date or server_height == 0:\n                status = _(\"Synchronizing...\")\n            elif server_lag > 1:\n                status = _(\"Server lagging (%d blocks)\"%server_lag)\n            else:\n                c, u, x = self.wallet.get_balance()\n                text = self.format_amount(c+x+u)\n                status = str(text.strip() + ' ' + self.base_unit)\n        else:\n            status = _(\"Disconnected\")\n\n        n = self.wallet.basename()\n        self.status = '[size=15dp]%s[/size]\\n%s' %(n, status)\n        #fiat_balance = self.fx.format_amount_and_units(c+u+x) or ''\n\n    def get_max_amount(self):\n        inputs = self.wallet.get_spendable_coins(None, self.electrum_config)\n        addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()\n        outputs = [(TYPE_ADDRESS, addr, '!')]\n        tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)\n        amount = tx.output_value()\n        return format_satoshis_plain(amount, self.decimal_point())\n\n    def format_amount(self, x, is_diff=False, whitespaces=False):\n        return format_satoshis(x, is_diff, 0, self.decimal_point(), whitespaces)\n\n    def format_amount_and_units(self, x):\n        return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit\n\n    #@profiler\n    def update_wallet(self, *dt):\n        self._trigger_update_status()\n        if self.wallet and (self.wallet.up_to_date or not self.network or not self.network.is_connected()):\n            self.update_tabs()\n\n    def notify(self, message):\n        try:\n            global notification, os\n            if not notification:\n                from plyer import notification\n            icon = (os.path.dirname(os.path.realpath(__file__))\n                    + '/../../' + self.icon)\n            notification.notify('Electrum', message,\n                            app_icon=icon, app_name='Electrum')\n        except ImportError:\n            Logger.Error('Notification: needs plyer; `sudo pip install plyer`')\n\n    def on_pause(self):\n        # pause nfc\n        if self.nfcscanner:\n            self.nfcscanner.nfc_disable()\n        return True\n\n    def on_resume(self):\n        if self.nfcscanner:\n            self.nfcscanner.nfc_enable()\n        # workaround p4a bug:\n        # show an empty info bubble, to refresh the display\n        self.show_info_bubble('', duration=0.1, pos=(0,0), width=1, arrow_pos=None)\n\n    def on_size(self, instance, value):\n        width, height = value\n        self._orientation = 'landscape' if width > height else 'portrait'\n        self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone'\n\n    def on_ref_label(self, label, touch):\n        if label.touched:\n            label.touched = False\n            self.qr_dialog(label.name, label.data, True)\n        else:\n            label.touched = True\n            self._clipboard.copy(label.data)\n            Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\\nTap again to display it as QR code.')))\n\n    def set_send(self, address, amount, label, message):\n        self.send_payment(address, amount=amount, label=label, message=message)\n\n    def show_error(self, error, width='200dp', pos=None, arrow_pos=None,\n        exit=False, icon='atlas://gui/kivy/theming/light/error', duration=0,\n        modal=False):\n        ''' Show a error Message Bubble.\n        '''\n        self.show_info_bubble( text=error, icon=icon, width=width,\n            pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit,\n            duration=duration, modal=modal)\n\n    def show_info(self, error, width='200dp', pos=None, arrow_pos=None,\n        exit=False, duration=0, modal=False):\n        ''' Show a Info Message Bubble.\n        '''\n        self.show_error(error, icon='atlas://gui/kivy/theming/light/important',\n            duration=duration, modal=modal, exit=exit, pos=pos,\n            arrow_pos=arrow_pos)\n\n    def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0,\n        arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False):\n        '''Method to show a Information Bubble\n\n        .. parameters::\n            text: Message to be displayed\n            pos: position for the bubble\n            duration: duration the bubble remains on screen. 0 = click to hide\n            width: width of the Bubble\n            arrow_pos: arrow position for the bubble\n        '''\n        info_bubble = self.info_bubble\n        if not info_bubble:\n            info_bubble = self.info_bubble = Factory.InfoBubble()\n\n        win = Window\n        if info_bubble.parent:\n            win.remove_widget(info_bubble\n                                 if not info_bubble.modal else\n                                 info_bubble._modal_view)\n\n        if not arrow_pos:\n            info_bubble.show_arrow = False\n        else:\n            info_bubble.show_arrow = True\n            info_bubble.arrow_pos = arrow_pos\n        img = info_bubble.ids.img\n        if text == 'texture':\n            # icon holds a texture not a source image\n            # display the texture in full screen\n            text = ''\n            img.texture = icon\n            info_bubble.fs = True\n            info_bubble.show_arrow = False\n            img.allow_stretch = True\n            info_bubble.dim_background = True\n            info_bubble.background_image = 'atlas://gui/kivy/theming/light/card'\n        else:\n            info_bubble.fs = False\n            info_bubble.icon = icon\n            #if img.texture and img._coreimage:\n            #    img.reload()\n            img.allow_stretch = False\n            info_bubble.dim_background = False\n            info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble'\n        info_bubble.message = text\n        if not pos:\n            pos = (win.center[0], win.center[1] - (info_bubble.height/2))\n        info_bubble.show(pos, duration, width, modal=modal, exit=exit)\n\n    def tx_dialog(self, tx):\n        from .uix.dialogs.tx_dialog import TxDialog\n        d = TxDialog(self, tx)\n        d.open()\n\n    def sign_tx(self, *args):\n        threading.Thread(target=self._sign_tx, args=args).start()\n\n    def _sign_tx(self, tx, password, on_success, on_failure):\n        try:\n            self.wallet.sign_transaction(tx, password)\n        except InvalidPassword:\n            Clock.schedule_once(lambda dt: on_failure(_(\"Invalid PIN\")))\n            return\n        Clock.schedule_once(lambda dt: on_success(tx))\n\n    def _broadcast_thread(self, tx, on_complete):\n        ok, txid = self.network.broadcast(tx)\n        Clock.schedule_once(lambda dt: on_complete(ok, txid))\n\n    def broadcast(self, tx, pr=None):\n        def on_complete(ok, msg):\n            if ok:\n                self.show_info(_('Payment sent.'))\n                if self.send_screen:\n                    self.send_screen.do_clear()\n                if pr:\n                    self.wallet.invoices.set_paid(pr, tx.txid())\n                    self.wallet.invoices.save()\n                    self.update_tab('invoices')\n            else:\n                self.show_error(msg)\n\n        if self.network and self.network.is_connected():\n            self.show_info(_('Sending'))\n            threading.Thread(target=self._broadcast_thread, args=(tx, on_complete)).start()\n        else:\n            self.show_info(_('Cannot broadcast transaction') + ':\\n' + _('Not connected'))\n\n    def description_dialog(self, screen):\n        from .uix.dialogs.label_dialog import LabelDialog\n        text = screen.message\n        def callback(text):\n            screen.message = text\n        d = LabelDialog(_('Enter description'), text, callback)\n        d.open()\n\n    @profiler\n    def amount_dialog(self, screen, show_max):\n        from .uix.dialogs.amount_dialog import AmountDialog\n        amount = screen.amount\n        if amount:\n            amount, u = str(amount).split()\n            assert u == self.base_unit\n        def cb(amount):\n            screen.amount = amount\n        popup = AmountDialog(show_max, amount, cb)\n        popup.open()\n\n    def protected(self, msg, f, args):\n        if self.wallet.has_password():\n            self.password_dialog(msg, f, args)\n        else:\n            f(*(args + (None,)))\n\n    def delete_wallet(self):\n        from .uix.dialogs.question import Question\n        basename = os.path.basename(self.wallet.storage.path)\n        d = Question(_('Delete wallet?') + '\\n' + basename, self._delete_wallet)\n        d.open()\n\n    def _delete_wallet(self, b):\n        if b:\n            basename = os.path.basename(self.wallet.storage.path)\n            self.protected(_(\"Enter your PIN code to confirm deletion of %s\") % basename, self.__delete_wallet, ())\n\n    def __delete_wallet(self, pw):\n        wallet_path = self.get_wallet_path()\n        dirname = os.path.dirname(wallet_path)\n        basename = os.path.basename(wallet_path)\n        if self.wallet.has_password():\n            try:\n                self.wallet.check_password(pw)\n            except:\n                self.show_error(\"Invalid PIN\")\n                return\n        self.stop_wallet()\n        os.unlink(wallet_path)\n        self.show_error(\"Wallet removed:\" + basename)\n        d = os.listdir(dirname)\n        name = 'default_wallet'\n        new_path = os.path.join(dirname, name)\n        self.load_wallet_by_name(new_path)\n\n    def show_seed(self, label):\n        self.protected(_(\"Enter your PIN code in order to decrypt your seed\"), self._show_seed, (label,))\n\n    def _show_seed(self, label, password):\n        if self.wallet.has_password() and password is None:\n            return\n        keystore = self.wallet.keystore\n        try:\n            seed = keystore.get_seed(password)\n            passphrase = keystore.get_passphrase(password)\n        except:\n            self.show_error(\"Invalid PIN\")\n            return\n        label.text = _('Seed') + ':\\n' + seed\n        if passphrase:\n            label.text += '\\n\\n' + _('Passphrase') + ': ' + passphrase\n\n    def change_password(self, cb):\n        if self.wallet.has_password():\n            self.protected(_(\"Changing PIN code.\") + '\\n' + _(\"Enter your current PIN:\"), self._change_password, (cb,))\n        else:\n            self._change_password(cb, None)\n\n    def _change_password(self, cb, old_password):\n        if self.wallet.has_password():\n            if old_password is None:\n                return\n            try:\n                self.wallet.check_password(old_password)\n            except InvalidPassword:\n                self.show_error(\"Invalid PIN\")\n                return\n        self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,))\n\n    def _change_password2(self, cb, old_password, new_password):\n        self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password))\n\n    def _change_password3(self, cb, old_password, new_password, confirmed_password):\n        if new_password == confirmed_password:\n            self.wallet.update_password(old_password, new_password)\n            cb()\n        else:\n            self.show_error(\"PIN numbers do not match\")\n\n    def password_dialog(self, msg, f, args):\n        from .uix.dialogs.password_dialog import PasswordDialog\n        def callback(pw):\n            Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1)\n        if self._password_dialog is None:\n            self._password_dialog = PasswordDialog()\n        self._password_dialog.init(msg, callback)\n        self._password_dialog.open()\n\n    def export_private_keys(self, pk_label, addr):\n        if self.wallet.is_watching_only():\n            self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))\n            return\n        def show_private_key(addr, pk_label, password):\n            if self.wallet.has_password() and password is None:\n                return\n            if not self.wallet.can_export():\n                return\n            key = str(self.wallet.export_private_key(addr, password)[0])\n            pk_label.data = key\n        self.protected(_(\"Enter your PIN code in order to decrypt your private key\"), show_private_key, (addr, pk_label))\n"
  },
  {
    "path": "gui/kivy/nfc_scanner/__init__.py",
    "content": "__all__ = ('NFCBase', 'NFCScanner')\n\nclass NFCBase(Widget):\n    ''' This is the base Abstract definition class that the actual hardware dependent\n    implementations would be based on. If you want to define a feature that is\n    accissible and implemented by every platform implementation then define that\n    method in this class.\n    '''\n\n    payload = ObjectProperty(None)\n    '''This is the data gotten from the tag. \n    '''\n\n    def nfc_init(self):\n        ''' Initialize the adapter.\n        '''\n        pass\n\n    def nfc_disable(self):\n        ''' Disable scanning\n        '''\n        pass\n\n    def nfc_enable(self):\n        ''' Enable Scanning\n        '''\n        pass\n\n    def nfc_enable_exchange(self, data):\n        ''' Enable P2P Ndef exchange\n        '''\n        pass\n\n    def nfc_disable_exchange(self):\n        ''' Disable/Stop P2P Ndef exchange\n        '''\n        pass\n\n# load NFCScanner implementation\n\nNFCScanner = core_select_lib('nfc_manager', (\n    # keep the dummy implementtation as the last one to make it the fallback provider.NFCScanner = core_select_lib('nfc_scanner', (\n    ('android', 'scanner_android', 'ScannerAndroid'),\n    ('dummy', 'scanner_dummy', 'ScannerDummy')), True, 'electrum_gui.kivy')\n"
  },
  {
    "path": "gui/kivy/nfc_scanner/scanner_android.py",
    "content": "'''This is the Android implementatoin of NFC Scanning using the\nbuilt in NFC adapter of some android phones.\n'''\n\nfrom kivy.app import App\nfrom kivy.clock import Clock\n#Detect which platform we are on\nfrom kivy.utils import platform\nif platform != 'android':\n    raise ImportError\nimport threading\n\nfrom electrum_gui.kivy.nfc_scanner import NFCBase\nfrom jnius import autoclass, cast\nfrom android.runnable import run_on_ui_thread\nfrom android import activity\n\nBUILDVERSION = autoclass('android.os.Build$VERSION').SDK_INT\nNfcAdapter = autoclass('android.nfc.NfcAdapter')\nPythonActivity = autoclass('org.kivy.android.PythonActivity')\nJString = autoclass('java.lang.String')\nCharset = autoclass('java.nio.charset.Charset')\nlocale = autoclass('java.util.Locale')\nIntent = autoclass('android.content.Intent')\nIntentFilter = autoclass('android.content.IntentFilter')\nPendingIntent = autoclass('android.app.PendingIntent')\nNdef = autoclass('android.nfc.tech.Ndef')\nNdefRecord = autoclass('android.nfc.NdefRecord')\nNdefMessage = autoclass('android.nfc.NdefMessage')\n\napp = None\n\n\n\nclass ScannerAndroid(NFCBase):\n    ''' This is the class responsible for handling the interace with the\n    Android NFC adapter. See Module Documentation for deatils.\n    '''\n\n    name = 'NFCAndroid'\n\n    def nfc_init(self):\n        ''' This is where we initialize NFC adapter.\n        '''\n        # Initialize NFC\n        global app\n        app = App.get_running_app()\n\n        # Make sure we are listening to new intent \n        activity.bind(on_new_intent=self.on_new_intent)\n\n        # Configure nfc\n        self.j_context = context = PythonActivity.mActivity\n        self.nfc_adapter = NfcAdapter.getDefaultAdapter(context)\n        # Check if adapter exists\n        if not self.nfc_adapter:\n            return False\n        \n        # specify that we want our activity to remain on top whan a new intent\n        # is fired\n        self.nfc_pending_intent = PendingIntent.getActivity(context, 0,\n            Intent(context, context.getClass()).addFlags(\n                Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)\n\n        # Filter for different types of action, by default we enable all.\n        # These are only for handling different NFC technologies when app is in foreground\n        self.ndef_detected = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)\n        #self.tech_detected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)\n        #self.tag_detected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)\n\n        # setup tag discovery for ourt tag type\n        try:\n            self.ndef_detected.addCategory(Intent.CATEGORY_DEFAULT)\n            # setup the foreground dispatch to detect all mime types\n            self.ndef_detected.addDataType('*/*')\n\n            self.ndef_exchange_filters = [self.ndef_detected]\n        except Exception as err:\n            raise Exception(repr(err))\n        return True\n\n    def get_ndef_details(self, tag):\n        ''' Get all the details from the tag.\n        '''\n        details = {}\n\n        try:\n            #print 'id'\n            details['uid'] = ':'.join(['{:02x}'.format(bt & 0xff) for bt in tag.getId()])\n            #print 'technologies'\n            details['Technologies'] = tech_list = [tech.split('.')[-1] for tech in tag.getTechList()]\n            #print 'get NDEF tag details'\n            ndefTag = cast('android.nfc.tech.Ndef', Ndef.get(tag))\n            #print 'tag size'\n            details['MaxSize'] = ndefTag.getMaxSize()\n            #details['usedSize'] = '0'\n            #print 'is tag writable?'\n            details['writable'] = ndefTag.isWritable()\n            #print 'Data format'\n            # Can be made readonly\n            # get NDEF message details\n            ndefMesg = ndefTag.getCachedNdefMessage()\n            # get size of current records\n            details['consumed'] = len(ndefMesg.toByteArray())\n            #print 'tag type'\n            details['Type'] = ndefTag.getType()\n\n            # check if tag is empty\n            if not ndefMesg:\n                details['Message'] = None\n                return details\n\n            ndefrecords =  ndefMesg.getRecords()\n            length = len(ndefrecords)\n            #print 'length', length\n            # will contain the NDEF record types\n            recTypes = []\n            for record in ndefrecords:\n                recTypes.append({\n                    'type': ''.join(map(unichr, record.getType())),\n                    'payload': ''.join(map(unichr, record.getPayload()))\n                    })\n\n            details['recTypes'] = recTypes\n        except Exception as err:\n            print(str(err))\n\n        return details\n\n    def on_new_intent(self, intent):\n        ''' This functions is called when the application receives a\n        new intent, for the ones the application has registered previously,\n        either in the manifest or in the foreground dispatch setup in the\n        nfc_init function above. \n        '''\n\n        action_list = (NfcAdapter.ACTION_NDEF_DISCOVERED,)\n        # get TAG\n        #tag = cast('android.nfc.Tag', intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))\n\n        #details = self.get_ndef_details(tag)\n\n        if intent.getAction() not in action_list:\n            print('unknow action, avoid.')\n            return\n\n        rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)\n        if not rawmsgs:\n            return\n        for message in rawmsgs:\n            message = cast(NdefMessage, message)\n            payload = message.getRecords()[0].getPayload()\n            print('payload: {}'.format(''.join(map(chr, payload))))\n\n    def nfc_disable(self):\n        '''Disable app from handling tags.\n        '''\n        self.disable_foreground_dispatch()\n\n    def nfc_enable(self):\n        '''Enable app to handle tags when app in foreground.\n        '''\n        self.enable_foreground_dispatch()\n\n    def create_AAR(self):\n        '''Create the record responsible for linking our application to the tag.\n        '''\n        return NdefRecord.createApplicationRecord(JString(\"org.electrum.kivy\"))\n\n    def create_TNF_EXTERNAL(self, data):\n        '''Create our actual payload record.\n        '''\n        if BUILDVERSION >= 14:\n            domain = \"org.electrum\"\n            stype = \"externalType\"\n            extRecord = NdefRecord.createExternal(domain, stype, data)\n        else:\n            # Creating the NdefRecord manually:\n            extRecord = NdefRecord(\n                NdefRecord.TNF_EXTERNAL_TYPE,\n                \"org.electrum:externalType\",\n                '',\n                data)\n        return extRecord\n\n    def create_ndef_message(self, *recs):\n        ''' Create the Ndef message that will written to tag\n        '''\n        records = []\n        for record in recs:\n            if record:\n                records.append(record)\n\n        return NdefMessage(records)\n\n\n    @run_on_ui_thread\n    def disable_foreground_dispatch(self):\n        '''Disable foreground dispatch when app is paused.\n        '''\n        self.nfc_adapter.disableForegroundDispatch(self.j_context)\n\n    @run_on_ui_thread\n    def enable_foreground_dispatch(self):\n        '''Start listening for new tags\n        '''\n        self.nfc_adapter.enableForegroundDispatch(self.j_context,\n                self.nfc_pending_intent, self.ndef_exchange_filters, self.ndef_tech_list)\n\n    @run_on_ui_thread\n    def _nfc_enable_ndef_exchange(self, data):\n        # Enable p2p exchange\n        # Create record\n        ndef_record = NdefRecord(\n                NdefRecord.TNF_MIME_MEDIA,\n                'org.electrum.kivy', '', data)\n        \n        # Create message\n        ndef_message = NdefMessage([ndef_record])\n\n        # Enable ndef push\n        self.nfc_adapter.enableForegroundNdefPush(self.j_context, ndef_message)\n\n        # Enable dispatch\n        self.nfc_adapter.enableForegroundDispatch(self.j_context,\n                self.nfc_pending_intent, self.ndef_exchange_filters, [])\n\n    @run_on_ui_thread\n    def _nfc_disable_ndef_exchange(self):\n        # Disable p2p exchange\n        self.nfc_adapter.disableForegroundNdefPush(self.j_context)\n        self.nfc_adapter.disableForegroundDispatch(self.j_context)\n\n    def nfc_enable_exchange(self, data):\n        '''Enable Ndef exchange for p2p\n        '''\n        self._nfc_enable_ndef_exchange()\n\n    def nfc_disable_exchange(self):\n        ''' Disable Ndef exchange for p2p\n        '''\n        self._nfc_disable_ndef_exchange()\n"
  },
  {
    "path": "gui/kivy/nfc_scanner/scanner_dummy.py",
    "content": "''' Dummy NFC Provider to be used on desktops in case no other provider is found\n'''\nfrom electrum_gui.kivy.nfc_scanner import NFCBase\nfrom kivy.clock import Clock\nfrom kivy.logger import Logger\n\nclass ScannerDummy(NFCBase):\n    '''This is the dummy interface that gets selected in case any other\n    hardware interface to NFC is not available.\n    '''\n\n    _initialised = False\n\n    name = 'NFCDummy'\n\n    def nfc_init(self):\n        # print 'nfc_init()'\n\n        Logger.debug('NFC: configure nfc')\n        self._initialised = True\n        self.nfc_enable()\n        return True\n\n    def on_new_intent(self, dt):\n        tag_info = {'type': 'dymmy',\n                    'message': 'dummy',\n                    'extra details': None}\n\n        # let Main app know that a tag has been detected\n        app = App.get_running_app()\n        app.tag_discovered(tag_info)\n        app.show_info('New tag detected.', duration=2)\n        Logger.debug('NFC: got new dummy tag')\n\n    def nfc_enable(self):\n        Logger.debug('NFC: enable')\n        if self._initialised:\n            Clock.schedule_interval(self.on_new_intent, 22)\n\n    def nfc_disable(self):\n        # print 'nfc_enable()'\n        Clock.unschedule(self.on_new_intent)\n\n    def nfc_enable_exchange(self, data):\n        ''' Start sending data\n        '''\n        Logger.debug('NFC: sending data {}'.format(data))\n\n    def nfc_disable_exchange(self):\n        ''' Disable/Stop ndef exchange\n        '''\n        Logger.debug('NFC: disable nfc exchange')\n"
  },
  {
    "path": "gui/kivy/tools/bitcoin_intent.xml",
    "content": "<intent-filter >\n  <action android:name=\"android.intent.action.VIEW\" />\n  <action android:name=\"android.nfc.action.NDEF_DISCOVERED\"/>\n  <category android:name=\"android.intent.category.DEFAULT\" />\n  <category android:name=\"android.intent.category.BROWSABLE\" />\n  <data android:scheme=\"bitcoin\" />\n</intent-filter>\n"
  },
  {
    "path": "gui/kivy/tools/blacklist.txt",
    "content": "# eggs\n*.egg-info\n\n# unit test\nunittest/*\n\n# python config\nconfig/makesetup\n\n# unused pygame files\npygame/_camera_*\npygame/camera.pyo\npygame/*.html\npygame/*.bmp\npygame/*.svg\npygame/cdrom.so\npygame/pygame_icon.icns\npygame/LGPL\npygame/threads/Py25Queue.pyo\npygame/*.ttf\npygame/mac*\npygame/_numpy*\npygame/sndarray.pyo\npygame/surfarray.pyo\npygame/_arraysurfarray.pyo\n\n# unused kivy files (platform specific)\nkivy/input/providers/wm_*\nkivy/input/providers/mactouch*\nkivy/input/providers/probesysfs*\nkivy/input/providers/mtdev*\nkivy/input/providers/hidinput*\nkivy/core/camera/camera_videocapture*\nkivy/core/spelling/*osx*\nkivy/core/video/video_pyglet*\n\nkivy/adapters\nkivy/modules\nkivy/uix/sandbox\nkivy/uix/pagelayout\nkivy/uix/video\nkivy/uix/vkeyboard\nkivy/uix/videoplayer\n\n# unused encodings\nlib-dynload/*codec*\nencodings/cp*.pyo\nencodings/tis*\nencodings/shift*\nencodings/bz2*\nencodings/iso*\nencodings/undefined*\nencodings/johab*\nencodings/p*\nencodings/m*\nencodings/euc*\nencodings/k*\nencodings/unicode_internal*\nencodings/quo*\nencodings/gb*\nencodings/big5*\nencodings/hp*\nencodings/hz*\n\n# unused python modules\nbsddb/*\nwsgiref/*\nhotshot/*\npydoc_data/*\ntty.pyo\n#anydbm.pyo\nnturl2path.pyo\nLICENCE.txt\nmacurl2path.pyo\ndummy_threading.pyo\naudiodev.pyo\nantigravity.pyo\n#dumbdbm.pyo\nsndhdr.pyo\n__phello__.foo.pyo\nsunaudio.pyo\nos2emxpath.pyo\nmultiprocessing/dummy*\n\n# unused binaries python modules\nlib-dynload/termios.so\nlib-dynload/_lsprof.so\nlib-dynload/*audioop.so\n#lib-dynload/mmap.so\nlib-dynload/_hotshot.so\n#lib-dynload/_csv.so\nlib-dynload/future_builtins.so\nlib-dynload/_heapq.so\nlib-dynload/_json.so\nlib-dynload/grp.so\nlib-dynload/resource.so\nlib-dynload/pyexpat.so\n\n# odd files\nplat-linux3/regen\n\n#>sqlite3\n# conditionnal include depending if some recipes are included or not.\n#sqlite3/*\n#lib-dynload/_sqlite3.so\n#<sqlite3\n\n"
  },
  {
    "path": "gui/kivy/tools/buildozer.spec",
    "content": "[app]\n\n# (str) Title of your application\ntitle = Electrum\n\n# (str) Package name\npackage.name = Electrum\n\n# (str) Package domain (needed for android/ios packaging)\npackage.domain = org.electrum\n\n# (str) Source code where the main.py live\nsource.dir = .\n\n# (list) Source files to include (let empty to include all the files)\nsource.include_exts = py,png,jpg,kv,atlas,ttf,txt,gif,pem,mo,vs,fs,json\n\n# (list) Source files to exclude (let empty to not exclude anything)\nsource.exclude_exts = spec\n\n# (list) List of directory to exclude (let empty to not exclude anything)\nsource.exclude_dirs = bin, build, dist, contrib, gui/qt, gui/kivy/tools, gui/kivy/theming/light\n# (list) List of exclusions using pattern matching\nsource.exclude_patterns = Makefile,setup*\n\n# (str) Application versioning (method 1)\nversion.regex = version_apk = '(.*)'\nversion.filename = %(source.dir)s/contrib/versions.py\n\n# (str) Application versioning (method 2)\n#version = 1.9.8\n\n# (list) Application requirements\nrequirements = python3crystax, android, openssl, plyer, kivy==master\n\n# (str) Presplash of the application\n#presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png\npresplash.filename = %(source.dir)s/icons/electrum_presplash.png\n\n# (str) Icon of the application\nicon.filename = %(source.dir)s/icons/electrum_launcher.png\n\n# (str) Supported orientation (one of landscape, portrait or all)\norientation = portrait\n\n# (bool) Indicate if the application should be fullscreen or not\nfullscreen = False\n\n\n#\n# Android specific\n#\n\n# (list) Permissions\nandroid.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE, CAMERA\n\n# (int) Android API to use\n#android.api = 14\n\n# (int) Minimum API required (8 = Android 2.2 devices)\n#android.minapi = 8\n\n# (int) Android SDK version to use\n#android.sdk = 21\n\n# (str) Android NDK version to use\n#android.ndk = 9\n\n# (bool) Use --private data storage (True) or --dir public storage (False)\nandroid.private_storage = True\n\n# (str) Android NDK directory (if empty, it will be automatically downloaded.)\nandroid.ndk_path = /opt/crystax-ndk-10.3.2\n\n# (str) Android SDK directory (if empty, it will be automatically downloaded.)\n#android.sdk_path =\n\n# (str) Android entry point, default is ok for Kivy-based app\n#android.entrypoint = org.renpy.android.PythonActivity\n\n# (list) List of Java .jar files to add to the libs so that pyjnius can access\n# their classes. Don't add jars that you do not need, since extra jars can slow\n# down the build process. Allows wildcards matching, for example:\n# OUYA-ODK/libs/*.jar\n#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar\n#android.add_jars = lib/android/zbar.jar\n\n# (list) List of Java files to add to the android project (can be java or a\n# directory containing the files)\nandroid.add_src = gui/kivy/data/java-classes/\n\nandroid.gradle_dependencies = me.dm7.barcodescanner:zxing:1.9.8\n\nandroid.add_activities = org.electrum.qr.SimpleScannerActivity\n\n# (str) python-for-android branch to use, if not master, useful to try\n# not yet merged features.\n#android.branch = master\n\n# (str) OUYA Console category. Should be one of GAME or APP\n# If you leave this blank, OUYA support will not be enabled\n#android.ouya.category = GAME\n\n# (str) Filename of OUYA Console icon. It must be a 732x412 png image.\n#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png\n\n# (str) XML file to include as an intent filters in <activity> tag\nandroid.manifest.intent_filters = gui/kivy/tools/bitcoin_intent.xml\n\n# (list) Android additionnal libraries to copy into libs/armeabi\n#android.add_libs_armeabi = lib/android/*.so\n\n# (bool) Indicate whether the screen should stay on\n# Don't forget to add the WAKE_LOCK permission if you set this to True\n#android.wakelock = False\n\n# (list) Android application meta-data to set (key=value format)\n#android.meta_data =\n\n# (list) Android library project to add (will be added in the\n# project.properties automatically.)\n#android.library_references =\n\nandroid.whitelist = lib-dynload/_csv.so\n\n# local version that merges branch 866\np4a.source_dir = /opt/python-for-android\n\n#\n# iOS specific\n#\n\n# (str) Name of the certificate to use for signing the debug version\n# Get a list of available identities: buildozer ios list_identities\n#ios.codesign.debug = \"iPhone Developer: <lastname> <firstname> (<hexstring>)\"\n\n# (str) Name of the certificate to use for signing the release version\n#ios.codesign.release = %(ios.codesign.debug)s\n\n\n\n[buildozer]\n\n# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))\nlog_level = 2\n\n\n# -----------------------------------------------------------------------------\n# List as sections\n#\n# You can define all the \"list\" as [section:key].\n# Each line will be considered as a option to the list.\n# Let's take [app] / source.exclude_patterns.\n# Instead of doing:\n#\n#     [app]\n#     source.exclude_patterns = license,data/audio/*.wav,data/images/original/*\n#\n# This can be translated into:\n#\n#     [app:source.exclude_patterns]\n#     license\n#     data/audio/*.wav\n#     data/images/original/*\n#\n\n# -----------------------------------------------------------------------------\n# Profiles\n#\n# You can extend section / key with a profile\n# For example, you want to deploy a demo version of your application without\n# HD content. You could first change the title to add \"(demo)\" in the name\n# and extend the excluded directories to remove the HD content.\n#\n#     [app@demo]\n#     title = My Application (demo)\n#\n#     [app:source.exclude_patterns@demo]\n#     images/hd/*\n#\n# Then, invoke the command line with the \"demo\" profile:\n#\n#     buildozer --profile demo android debug\n"
  },
  {
    "path": "gui/kivy/uix/__init__.py",
    "content": " \n"
  },
  {
    "path": "gui/kivy/uix/combobox.py",
    "content": "'''\nComboBox\n=======\n\nBased on Spinner\n'''\n\n__all__ = ('ComboBox', 'ComboBoxOption')\n\nfrom kivy.properties import ListProperty, ObjectProperty, BooleanProperty\nfrom kivy.uix.button import Button\nfrom kivy.uix.dropdown import DropDown\nfrom kivy.lang import Builder\n\n\nBuilder.load_string('''\n<ComboBoxOption>:\n    size_hint_y: None\n    height: 44\n\n<ComboBox>:\n    background_normal: 'atlas://data/images/defaulttheme/spinner'\n    background_down: 'atlas://data/images/defaulttheme/spinner_pressed'\n    on_key:\n        if self.items: x, y = zip(*self.items); self.text = y[x.index(args[1])]\n''')\n\n\nclass ComboBoxOption(Button):\n    pass\n\n\nclass ComboBox(Button):\n    items = ListProperty()\n    key = ObjectProperty()\n\n    option_cls = ObjectProperty(ComboBoxOption)\n\n    dropdown_cls = ObjectProperty(DropDown)\n\n    is_open = BooleanProperty(False)\n\n    def __init__(self, **kwargs):\n        self._dropdown = None\n        super(ComboBox, self).__init__(**kwargs)\n        self.items_dict = dict(self.items)\n        self.bind(\n            on_release=self._toggle_dropdown,\n            dropdown_cls=self._build_dropdown,\n            option_cls=self._build_dropdown,\n            items=self._update_dropdown,\n            key=self._update_text)\n        self._build_dropdown()\n        self._update_text()\n\n    def _update_text(self, *largs):\n        try:\n            self.text = self.items_dict[self.key]\n        except KeyError:\n            pass\n\n    def _build_dropdown(self, *largs):\n        if self._dropdown:\n            self._dropdown.unbind(on_select=self._on_dropdown_select)\n            self._dropdown.dismiss()\n            self._dropdown = None\n        self._dropdown = self.dropdown_cls()\n        self._dropdown.bind(on_select=self._on_dropdown_select)\n        self._update_dropdown()\n\n    def _update_dropdown(self, *largs):\n        dp = self._dropdown\n        cls = self.option_cls\n        dp.clear_widgets()\n        for key, value in self.items:\n            item = cls(text=value)\n            # extra attribute\n            item.key = key\n            item.bind(on_release=lambda option: dp.select(option.key))\n            dp.add_widget(item)\n\n    def _toggle_dropdown(self, *largs):\n        self.is_open = not self.is_open\n\n    def _on_dropdown_select(self, instance, data, *largs):\n        self.key = data\n        self.is_open = False\n\n    def on_is_open(self, instance, value):\n        if value:\n            self._dropdown.open(self)\n        else:\n            self._dropdown.dismiss()\n"
  },
  {
    "path": "gui/kivy/uix/context_menu.py",
    "content": "#!python\n#!/usr/bin/env python\nfrom kivy.app import App\nfrom kivy.uix.bubble import Bubble\nfrom kivy.animation import Animation\nfrom kivy.uix.floatlayout import FloatLayout\nfrom kivy.lang import Builder\nfrom kivy.factory import Factory\nfrom kivy.clock import Clock\n\nfrom electrum_gui.kivy.i18n import _\n\nBuilder.load_string('''\n<MenuItem@Button>\n    background_normal: ''\n    background_color: (0.192, .498, 0.745, 1)\n    height: '48dp'\n    size_hint: 1, None\n\n<ContextMenu>\n    size_hint: 1, None\n    height: '48dp'\n    pos: (0, 0)\n    show_arrow: False\n    arrow_pos: 'top_mid'\n    padding: 0\n    orientation: 'horizontal'\n    BoxLayout:\n        size_hint: 1, 1\n        height: '48dp'\n        padding: '12dp', '0dp'\n        spacing: '3dp'\n        orientation: 'horizontal'\n        id: buttons\n''')\n\n\nclass MenuItem(Factory.Button):\n    pass\n\nclass ContextMenu(Bubble):\n\n    def __init__(self, obj, action_list):\n        Bubble.__init__(self)\n        self.obj = obj\n        for k, v in action_list:\n            l = MenuItem()\n            l.text = _(k)\n            def func(f=v):\n                Clock.schedule_once(lambda dt: self.hide(), 0.1)\n                Clock.schedule_once(lambda dt: f(obj), 0.15)\n            l.on_release = func\n            self.ids.buttons.add_widget(l)\n\n    def hide(self):\n        if self.parent:\n            self.parent.hide_menu()\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/__init__.py",
    "content": "from kivy.app import App\nfrom kivy.clock import Clock\nfrom kivy.factory import Factory\nfrom kivy.properties import NumericProperty, StringProperty, BooleanProperty\nfrom kivy.core.window import Window\n\nfrom electrum_gui.kivy.i18n import _\n\n\n\nclass AnimatedPopup(Factory.Popup):\n    ''' An Animated Popup that animates in and out.\n    '''\n\n    anim_duration = NumericProperty(.36)\n    '''Duration of animation to be used\n    '''\n\n    __events__ = ['on_activate', 'on_deactivate']\n\n\n    def on_activate(self):\n        '''Base function to be overridden on inherited classes.\n        Called when the popup is done animating.\n        '''\n        pass\n\n    def on_deactivate(self):\n        '''Base function to be overridden on inherited classes.\n        Called when the popup is done animating.\n        '''\n        pass\n\n    def open(self):\n        '''Do the initialization of incoming animation here.\n        Override to set your custom animation.\n        '''\n        def on_complete(*l):\n            self.dispatch('on_activate')\n\n        self.opacity = 0\n        super(AnimatedPopup, self).open()\n        anim = Factory.Animation(opacity=1, d=self.anim_duration)\n        anim.bind(on_complete=on_complete)\n        anim.start(self)\n\n    def dismiss(self):\n        '''Do the initialization of incoming animation here.\n        Override to set your custom animation.\n        '''\n        def on_complete(*l):\n            super(AnimatedPopup, self).dismiss()\n            self.dispatch('on_deactivate')\n\n        anim = Factory.Animation(opacity=0, d=.25)\n        anim.bind(on_complete=on_complete)\n        anim.start(self)\n\nclass EventsDialog(Factory.Popup):\n    ''' Abstract Popup that provides the following events\n    .. events::\n        `on_release`\n        `on_press`\n    '''\n\n    __events__ = ('on_release', 'on_press')\n\n    def __init__(self, **kwargs):\n        super(EventsDialog, self).__init__(**kwargs)\n\n    def on_release(self, instance):\n        pass\n\n    def on_press(self, instance):\n        pass\n\n    def close(self):\n        self.dismiss()\n\n\nclass SelectionDialog(EventsDialog):\n\n    def add_widget(self, widget, index=0):\n        if self.content:\n            self.content.add_widget(widget, index)\n            return\n        super(SelectionDialog, self).add_widget(widget)\n\n\nclass InfoBubble(Factory.Bubble):\n    '''Bubble to be used to display short Help Information'''\n\n    message = StringProperty(_('Nothing set !'))\n    '''Message to be displayed; defaults to \"nothing set\"'''\n\n    icon = StringProperty('')\n    ''' Icon to be displayed along with the message defaults to ''\n\n    :attr:`icon` is a  `StringProperty` defaults to `''`\n    '''\n\n    fs = BooleanProperty(False)\n    ''' Show Bubble in half screen mode\n\n    :attr:`fs` is a `BooleanProperty` defaults to `False`\n    '''\n\n    modal = BooleanProperty(False)\n    ''' Allow bubble to be hidden on touch.\n\n    :attr:`modal` is a `BooleanProperty` defauult to `False`.\n    '''\n\n    exit = BooleanProperty(False)\n    '''Indicates whether to exit app after bubble is closed.\n\n    :attr:`exit` is a `BooleanProperty` defaults to False.\n    '''\n\n    dim_background = BooleanProperty(False)\n    ''' Indicates Whether to draw a background on the windows behind the bubble.\n\n    :attr:`dim` is a `BooleanProperty` defaults to `False`.\n    '''\n\n    def on_touch_down(self, touch):\n        if self.modal:\n            return True\n        self.hide()\n        if self.collide_point(*touch.pos):\n            return True\n\n    def show(self, pos, duration, width=None, modal=False, exit=False):\n        '''Animate the bubble into position'''\n        self.modal, self.exit = modal, exit\n        if width:\n            self.width = width\n        if self.modal:\n            from kivy.uix.modalview import ModalView\n            self._modal_view = m = ModalView(background_color=[.5, .5, .5, .2])\n            Window.add_widget(m)\n            m.add_widget(self)\n        else:\n            Window.add_widget(self)\n\n        # wait for the bubble to adjust it's size according to text then animate\n        Clock.schedule_once(lambda dt: self._show(pos, duration))\n\n    def _show(self, pos, duration):\n\n        def on_stop(*l):\n            if duration:\n                Clock.schedule_once(self.hide, duration + .5)\n\n        self.opacity = 0\n        arrow_pos = self.arrow_pos\n        if arrow_pos[0] in ('l', 'r'):\n            pos = pos[0], pos[1] - (self.height/2)\n        else:\n            pos = pos[0] - (self.width/2), pos[1]\n\n        self.limit_to = Window\n\n        anim = Factory.Animation(opacity=1, pos=pos, d=.32)\n        anim.bind(on_complete=on_stop)\n        anim.cancel_all(self)\n        anim.start(self)\n\n\n    def hide(self, now=False):\n        ''' Auto fade out the Bubble\n        '''\n        def on_stop(*l):\n            if self.modal:\n                m = self._modal_view\n                m.remove_widget(self)\n                Window.remove_widget(m)\n            Window.remove_widget(self)\n            if self.exit:\n                App.get_running_app().stop()\n                import sys\n                sys.exit()\n            else:\n                App.get_running_app().is_exit = False\n\n        if now:\n            return on_stop()\n\n        anim = Factory.Animation(opacity=0, d=.25)\n        anim.bind(on_complete=on_stop)\n        anim.cancel_all(self)\n        anim.start(self)\n\n\n\nclass OutputItem(Factory.BoxLayout):\n    pass\n\nclass OutputList(Factory.GridLayout):\n\n    def __init__(self, **kwargs):\n        super(Factory.GridLayout, self).__init__(**kwargs)\n        self.app = App.get_running_app()\n\n    def update(self, outputs):\n        self.clear_widgets()\n        for (type, address, amount) in outputs:\n            self.add_output(address, amount)\n\n    def add_output(self, address, amount):\n        b = Factory.OutputItem()\n        b.address = address\n        b.value = self.app.format_amount_and_units(amount)\n        self.add_widget(b)\n\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/amount_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\nfrom decimal import Decimal\n\nBuilder.load_string('''\n\n<AmountDialog@Popup>\n    id: popup\n    title: _('Amount')\n    AnchorLayout:\n        anchor_x: 'center'\n        BoxLayout:\n            orientation: 'vertical'\n            size_hint: 0.8, 1\n            BoxLayout:\n                size_hint: 1, None\n                height: '80dp'\n                Label:\n                    id: a\n                    btc_text: (kb.amount + ' ' + app.base_unit) if kb.amount else ''\n                    fiat_text: (kb.fiat_amount + ' ' + app.fiat_unit) if kb.fiat_amount else ''\n                    text1: ((self.fiat_text if kb.is_fiat else self.btc_text) if app.fiat_unit else self.btc_text) if self.btc_text else ''\n                    text2: ((self.btc_text if kb.is_fiat else self.fiat_text) if app.fiat_unit else '') if self.btc_text else ''\n                    text: self.text1 + \"\\\\n\" + \"[color=#8888ff]\" + self.text2 + \"[/color]\"\n                    halign: 'right'\n                    size_hint: 1, None\n                    font_size: '22dp'\n                    height: '80dp'\n            Widget:\n                size_hint: 1, 0.2\n            GridLayout:\n                id: kb\n                amount: ''\n                fiat_amount: ''\n                is_fiat: False\n                on_fiat_amount: if self.is_fiat: self.amount = app.fiat_to_btc(self.fiat_amount)\n                on_amount: if not self.is_fiat: self.fiat_amount = app.btc_to_fiat(self.amount)\n                size_hint: 1, None\n                update_amount: popup.update_amount\n                height: '300dp'\n                cols: 3\n                KButton:\n                    text: '1'\n                KButton:\n                    text: '2'\n                KButton:\n                    text: '3'\n                KButton:\n                    text: '4'\n                KButton:\n                    text: '5'\n                KButton:\n                    text: '6'\n                KButton:\n                    text: '7'\n                KButton:\n                    text: '8'\n                KButton:\n                    text: '9'\n                KButton:\n                    text: '.'\n                KButton:\n                    text: '0'\n                KButton:\n                    text: '<'\n                Button:\n                    id: but_max\n                    opacity: 1 if root.show_max else 0\n                    disabled: not root.show_max\n                    size_hint: 1, None\n                    height: '48dp'\n                    text: 'Max'\n                    on_release:\n                        kb.is_fiat = False\n                        kb.amount = app.get_max_amount()\n                Button:\n                    id: button_fiat\n                    size_hint: 1, None\n                    height: '48dp'\n                    text: (app.base_unit if not kb.is_fiat else app.fiat_unit) if app.fiat_unit else ''\n                    on_release:\n                        if app.fiat_unit: popup.toggle_fiat(kb)\n                Button:\n                    size_hint: 1, None\n                    height: '48dp'\n                    text: 'Clear'\n                    on_release:\n                        kb.amount = ''\n                        kb.fiat_amount = ''\n            Widget:\n                size_hint: 1, 0.2\n            BoxLayout:\n                size_hint: 1, None\n                height: '48dp'\n                Widget:\n                    size_hint: 1, None\n                    height: '48dp'\n                Button:\n                    size_hint: 1, None\n                    height: '48dp'\n                    text: _('OK')\n                    on_release:\n                        root.callback(a.btc_text)\n                        popup.dismiss()\n''')\n\nfrom kivy.properties import BooleanProperty\n\nclass AmountDialog(Factory.Popup):\n    show_max = BooleanProperty(False)\n    def __init__(self, show_max, amount, cb):\n        Factory.Popup.__init__(self)\n        self.show_max = show_max\n        self.callback = cb\n        if amount:\n            self.ids.kb.amount = amount\n\n    def toggle_fiat(self, a):\n        a.is_fiat = not a.is_fiat\n\n    def update_amount(self, c):\n        kb = self.ids.kb\n        amount = kb.fiat_amount if kb.is_fiat else kb.amount\n        if c == '<':\n            amount = amount[:-1]\n        elif c == '.' and amount in ['0', '']:\n            amount = '0.'\n        elif amount == '0':\n            amount = c\n        else:\n            try:\n                Decimal(amount+c)\n                amount += c\n            except:\n                pass\n        if kb.is_fiat:\n            kb.fiat_amount = amount\n        else:\n            kb.amount = amount\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/bump_fee_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nfrom electrum.util import fee_levels\nfrom electrum_gui.kivy.i18n import _\n\nBuilder.load_string('''\n<BumpFeeDialog@Popup>\n    title: _('Bump fee')\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        padding: '10dp'\n\n        GridLayout:\n            height: self.minimum_height\n            size_hint_y: None\n            cols: 1\n            spacing: '10dp'\n            BoxLabel:\n                id: old_fee\n                text: _('Current Fee')\n                value: ''\n            BoxLabel:\n                id: new_fee\n                text: _('New Fee')\n                value: ''\n        Label:\n            id: tooltip\n            text: ''\n            size_hint_y: None\n        Slider:\n            id: slider\n            range: 0, 4\n            step: 1\n            on_value: root.on_slider(self.value)\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Label:\n                text: _('Final')\n            CheckBox:\n                id: final_cb\n        Widget:\n            size_hint: 1, 1\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.5\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: root.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.dismiss()\n                    root.on_ok()\n''')\n\nclass BumpFeeDialog(Factory.Popup):\n\n    def __init__(self, app, fee, size, callback):\n        Factory.Popup.__init__(self)\n        self.app = app\n        self.init_fee = fee\n        self.tx_size = size\n        self.callback = callback\n        self.config = app.electrum_config\n        self.fee_step = self.config.max_fee_rate() / 10\n        self.dynfees = self.config.get('dynamic_fees', True) and self.app.network\n        self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee)\n        self.update_slider()\n        self.update_text()\n\n    def update_text(self):\n        value = int(self.ids.slider.value)\n        self.ids.new_fee.value = self.app.format_amount_and_units(self.get_fee())\n        if self.dynfees:\n            value = int(self.ids.slider.value)\n            self.ids.tooltip.text = fee_levels[value]\n\n    def update_slider(self):\n        slider = self.ids.slider\n        if self.dynfees:\n            slider.range = (0, 4)\n            slider.step = 1\n            slider.value = 3\n        else:\n            slider.range = (1, 10)\n            slider.step = 1\n            rate = self.init_fee*1000//self.tx_size\n            slider.value = min( rate * 2 // self.fee_step, 10)\n\n    def get_fee(self):\n        value = int(self.ids.slider.value)\n        if self.dynfees:\n            if self.config.has_fee_estimates():\n                dynfee = self.config.dynfee(value)\n                return int(dynfee * self.tx_size // 1000)\n        else:\n            return int(value*self.fee_step * self.tx_size // 1000)\n\n    def on_ok(self):\n        new_fee = self.get_fee()\n        is_final = self.ids.final_cb.active\n        self.callback(self.init_fee, new_fee, is_final)\n\n    def on_slider(self, value):\n        self.update_text()\n\n    def on_checkbox(self, b):\n        self.dynfees = b\n        self.update_text()\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/checkbox_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nBuilder.load_string('''\n<CheckBoxDialog@Popup>\n    id: popup\n    title: ''\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        Label:\n            id: description\n            text: ''\n            halign: 'left'\n            text_size: self.width, None\n            size: self.texture_size\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Label:\n                text: _('Enable')\n            CheckBox:\n                id:cb\n        Widget:\n            size_hint: 1, 0.1\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(cb.active)\n                    popup.dismiss()\n''')\n\nclass CheckBoxDialog(Factory.Popup):\n    def __init__(self, title, text, status, callback):\n        Factory.Popup.__init__(self)\n        self.ids.cb.active = status\n        self.ids.description.text = text\n        self.callback = callback\n        self.title = title\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/choice_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\nfrom kivy.uix.checkbox import CheckBox\nfrom kivy.uix.label import Label\nfrom kivy.uix.widget import Widget\n\nBuilder.load_string('''\n<ChoiceDialog@Popup>\n    id: popup\n    title: ''\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        Widget:\n            size_hint: 1, 0.1\n        ScrollView:\n            orientation: 'vertical'\n            size_hint: 1, 0.8\n            GridLayout:\n                row_default_height: '48dp'\n                orientation: 'vertical'\n                id: choices\n                cols: 2\n                size_hint: 1, None\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(popup.value)\n                    popup.dismiss()\n''')\n\nclass ChoiceDialog(Factory.Popup):\n\n    def __init__(self, title, choices, key, callback):\n        Factory.Popup.__init__(self)\n        print(choices, type(choices))\n        if type(choices) is list:\n            choices = dict(map(lambda x: (x,x), choices))\n        layout = self.ids.choices\n        layout.bind(minimum_height=layout.setter('height'))\n        for k, v in sorted(choices.items()):\n            l = Label(text=v)\n            l.height = '48dp'\n            l.size_hint_x = 4\n            cb = CheckBox(group='choices')\n            cb.value = k\n            cb.height = '48dp'\n            cb.size_hint_x = 1\n            def f(cb, x):\n                if x: self.value = cb.value\n            cb.bind(active=f)\n            if k == key:\n                cb.active = True\n            layout.add_widget(l)\n            layout.add_widget(cb)\n        layout.add_widget(Widget(size_hint_y=1))\n        self.callback = callback\n        self.title = title\n        self.value = key\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/fee_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nfrom electrum.util import fee_levels\nfrom electrum_gui.kivy.i18n import _\n\nBuilder.load_string('''\n<FeeDialog@Popup>\n    id: popup\n    title: _('Transaction Fees')\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.5\n            Label:\n                id: fee_per_kb\n                text: ''\n        Slider:\n            id: slider\n            range: 0, 4\n            step: 1\n            on_value: root.on_slider(self.value)\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.5\n            Label:\n                text: _('Dynamic Fees')\n            CheckBox:\n                id: dynfees\n                on_active: root.on_checkbox(self.active)\n        Widget:\n            size_hint: 1, 1\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.5\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.on_ok()\n                    root.dismiss()\n''')\n\nclass FeeDialog(Factory.Popup):\n\n    def __init__(self, app, config, callback):\n        Factory.Popup.__init__(self)\n        self.app = app\n        self.config = config\n        self.fee_rate = self.config.fee_per_kb()\n        self.callback = callback\n        self.dynfees = self.config.get('dynamic_fees', True)\n        self.ids.dynfees.active = self.dynfees\n        self.update_slider()\n        self.update_text()\n\n    def update_text(self):\n        value = int(self.ids.slider.value)\n        self.ids.fee_per_kb.text = self.get_fee_text(value)\n\n    def update_slider(self):\n        slider = self.ids.slider\n        if self.dynfees:\n            slider.range = (0, 4)\n            slider.step = 1\n            slider.value = self.config.get('fee_level', 2)\n        else:\n            slider.range = (0, 9)\n            slider.step = 1\n            slider.value = self.config.static_fee_index(self.fee_rate)\n\n    def get_fee_text(self, value):\n        if self.ids.dynfees.active:\n            tooltip = fee_levels[value]\n            if self.config.has_fee_estimates():\n                dynfee = self.config.dynfee(value)\n                tooltip += '\\n' + (self.app.format_amount_and_units(dynfee)) + '/kB'\n        else:\n            fee_rate = self.config.static_fee(value)\n            tooltip = self.app.format_amount_and_units(fee_rate) + '/kB'\n            if self.config.has_fee_estimates():\n                i = self.config.reverse_dynfee(fee_rate)\n                tooltip += '\\n' + (_('low fee') if i < 0 else 'Within %d blocks'%i)\n        return tooltip\n\n    def on_ok(self):\n        value = int(self.ids.slider.value)\n        self.config.set_key('dynamic_fees', self.dynfees, False)\n        if self.dynfees:\n            self.config.set_key('fee_level', value, True)\n        else:\n            self.config.set_key('fee_per_kb', self.config.static_fee(value), True)\n        self.callback()\n\n    def on_slider(self, value):\n        self.update_text()\n\n    def on_checkbox(self, b):\n        self.dynfees = b\n        self.update_slider()\n        self.update_text()\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/fx_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nBuilder.load_string('''\n<FxDialog@Popup>\n    id: popup\n    title: 'Fiat Currency'\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n\n        Widget:\n            size_hint: 1, 0.1\n\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.1\n            Label:\n                text: _('Currency')\n                height: '48dp'\n            Spinner:\n                height: '48dp'\n                id: ccy\n                on_text: popup.on_currency(self.text)\n\n        Widget:\n            size_hint: 1, 0.1\n\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.1\n            Label:\n                text: _('Source')\n                height: '48dp'\n            Spinner:\n                height: '48dp'\n                id: exchanges\n                on_text: popup.on_exchange(self.text)\n\n        Widget:\n            size_hint: 1, 0.2\n\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback()\n                    popup.dismiss()\n''')\n\n\nfrom kivy.uix.label import Label\nfrom kivy.uix.checkbox import CheckBox\nfrom kivy.uix.widget import Widget\nfrom kivy.clock import Clock\n\nfrom electrum_gui.kivy.i18n import _\nfrom functools import partial\n\nclass FxDialog(Factory.Popup):\n\n    def __init__(self, app, plugins, config, callback):\n        Factory.Popup.__init__(self)\n        self.app = app\n        self.config = config\n        self.callback = callback\n        self.fx = self.app.fx\n        self.fx.set_history_config(True)\n        self.add_currencies()\n\n    def add_exchanges(self):\n        exchanges = sorted(self.fx.get_exchanges_by_ccy(self.fx.get_currency(), True)) if self.fx.is_enabled() else []\n        mx = self.fx.exchange.name() if self.fx.is_enabled() else ''\n        ex = self.ids.exchanges\n        ex.values = exchanges\n        ex.text = (mx if mx in exchanges else exchanges[0]) if self.fx.is_enabled() else ''\n\n    def on_exchange(self, text):\n        if not text:\n            return\n        if self.fx.is_enabled() and text != self.fx.exchange.name():\n            self.fx.set_exchange(text)\n\n    def add_currencies(self):\n        currencies = [_('None')] + self.fx.get_currencies(True)\n        my_ccy = self.fx.get_currency() if self.fx.is_enabled() else _('None')\n        self.ids.ccy.values = currencies\n        self.ids.ccy.text = my_ccy\n\n    def on_currency(self, ccy):\n        b = (ccy != _('None'))\n        self.fx.set_enabled(b)\n        if b:\n            if ccy != self.fx.get_currency():\n                self.fx.set_currency(ccy)\n            self.app.fiat_unit = ccy\n        Clock.schedule_once(lambda dt: self.add_exchanges())\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/installwizard.py",
    "content": "\nfrom functools import partial\nimport threading\n\nfrom kivy.app import App\nfrom kivy.clock import Clock\nfrom kivy.lang import Builder\nfrom kivy.properties import ObjectProperty, StringProperty, OptionProperty\nfrom kivy.core.window import Window\nfrom kivy.uix.button import Button\nfrom kivy.utils import platform\nfrom kivy.uix.widget import Widget\nfrom kivy.core.window import Window\nfrom kivy.clock import Clock\nfrom kivy.utils import platform\n\nfrom electrum.base_wizard import BaseWizard\n\n\nfrom . import EventsDialog\nfrom ...i18n import _\nfrom .password_dialog import PasswordDialog\n\n# global Variables\nis_test = (platform == \"linux\")\ntest_seed = \"time taxi field recycle tiny license olive virus report rare steel portion achieve\"\ntest_xpub = \"xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL\"\n\nBuilder.load_string('''\n#:import Window kivy.core.window.Window\n#:import _ electrum_gui.kivy.i18n._\n\n\n<WizardTextInput@TextInput>\n    border: 4, 4, 4, 4\n    font_size: '15sp'\n    padding: '15dp', '15dp'\n    background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)\n    foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)\n    hint_text_color: self.foreground_color\n    background_active: 'atlas://gui/kivy/theming/light/create_act_text_active'\n    background_normal: 'atlas://gui/kivy/theming/light/create_act_text_active'\n    size_hint_y: None\n    height: '48sp'\n\n<WizardButton@Button>:\n    root: None\n    size_hint: 1, None\n    height: '48sp'\n    on_press: if self.root: self.root.dispatch('on_press', self)\n    on_release: if self.root: self.root.dispatch('on_release', self)\n\n<BigLabel@Label>\n    color: .854, .925, .984, 1\n    size_hint: 1, None\n    text_size: self.width, None\n    height: self.texture_size[1]\n    bold: True\n\n<-WizardDialog>\n    text_color: .854, .925, .984, 1\n    value: ''\n    #auto_dismiss: False\n    size_hint: None, None\n    canvas.before:\n        Color:\n            rgba: 0, 0, 0, .9\n        Rectangle:\n            size: Window.size\n        Color:\n            rgba: .239, .588, .882, 1\n        Rectangle:\n            size: Window.size\n\n    crcontent: crcontent\n    # add electrum icon\n    BoxLayout:\n        orientation: 'vertical' if self.width < self.height else 'horizontal'\n        padding:\n            min(dp(27), self.width/32), min(dp(27), self.height/32),\\\n            min(dp(27), self.width/32), min(dp(27), self.height/32)\n        spacing: '10dp'\n        GridLayout:\n            id: grid_logo\n            cols: 1\n            pos_hint: {'center_y': .5}\n            size_hint: 1, None\n            height: self.minimum_height\n            Label:\n                color: root.text_color\n                text: 'ELECTRUM'\n                size_hint: 1, None\n                height: self.texture_size[1] if self.opacity else 0\n                font_size: '33sp'\n                font_name: 'gui/kivy/data/fonts/tron/Tr2n.ttf'\n        GridLayout:\n            cols: 1\n            id: crcontent\n            spacing: '1dp'\n        Widget:\n            size_hint: 1, 0.3\n        GridLayout:\n            rows: 1\n            spacing: '12dp'\n            size_hint: 1, None\n            height: self.minimum_height\n            WizardButton:\n                id: back\n                text: _('Back')\n                root: root\n            WizardButton:\n                id: next\n                text: _('Next')\n                root: root\n                disabled: root.value == ''\n\n\n<WizardMultisigDialog>\n    value: 'next'\n    Widget\n        size_hint: 1, 1\n    Label:\n        color: root.text_color\n        size_hint: 1, None\n        text_size: self.width, None\n        height: self.texture_size[1]\n        text: _(\"Choose the number of signatures needed to unlock funds in your wallet\")\n    Widget\n        size_hint: 1, 1\n    GridLayout:\n        orientation: 'vertical'\n        cols: 2\n        spacing: '14dp'\n        size_hint: 1, 1\n        height: self.minimum_height\n        Label:\n            color: root.text_color\n            text: _('From %d cosigners')%n.value\n        Slider:\n            id: n\n            range: 2, 5\n            step: 1\n            value: 2\n        Label:\n            color: root.text_color\n            text: _('Require %d signatures')%m.value\n        Slider:\n            id: m\n            range: 1, n.value\n            step: 1\n            value: 2\n\n\n<WizardChoiceDialog>\n    message : ''\n    Widget:\n        size_hint: 1, 1\n    Label:\n        color: root.text_color\n        size_hint: 1, None\n        text_size: self.width, None\n        height: self.texture_size[1]\n        text: root.message\n    Widget\n        size_hint: 1, 1\n    GridLayout:\n        row_default_height: '48dp'\n        orientation: 'vertical'\n        id: choices\n        cols: 1\n        spacing: '14dp'\n        size_hint: 1, None\n\n<MButton@Button>:\n    size_hint: 1, None\n    height: '33dp'\n    on_release:\n        self.parent.update_amount(self.text)\n\n<WordButton@Button>:\n    size_hint: None, None\n    padding: '5dp', '5dp'\n    text_size: None, self.height\n    width: self.texture_size[0]\n    height: '30dp'\n    on_release:\n        self.parent.new_word(self.text)\n\n\n<SeedButton@Button>:\n    height: dp(100)\n    border: 4, 4, 4, 4\n    halign: 'justify'\n    valign: 'top'\n    font_size: '18dp'\n    text_size: self.width - dp(24), self.height - dp(12)\n    color: .1, .1, .1, 1\n    background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top'\n    background_down: self.background_normal\n    size_hint_y: None\n\n\n<SeedLabel@Label>:\n    font_size: '12sp'\n    text_size: self.width, None\n    size_hint: 1, None\n    height: self.texture_size[1]\n    halign: 'justify'\n    valign: 'middle'\n    border: 4, 4, 4, 4\n\n\n<RestoreSeedDialog>\n    message: ''\n    word: ''\n    BigLabel:\n        text: \"ENTER YOUR SEED PHRASE\"\n    GridLayout\n        cols: 1\n        padding: 0, '12dp'\n        orientation: 'vertical'\n        spacing: '12dp'\n        size_hint: 1, None\n        height: self.minimum_height\n        SeedButton:\n            id: text_input_seed\n            text: ''\n            on_text: Clock.schedule_once(root.on_text)\n            on_release: root.options_dialog()\n        SeedLabel:\n            text: root.message\n        BoxLayout:\n            id: suggestions\n            height: '35dp'\n            size_hint: 1, None\n            new_word: root.on_word\n        BoxLayout:\n            id: line1\n            update_amount: root.update_text\n            size_hint: 1, None\n            height: '30dp'\n            MButton:\n                text: 'Q'\n            MButton:\n                text: 'W'\n            MButton:\n                text: 'E'\n            MButton:\n                text: 'R'\n            MButton:\n                text: 'T'\n            MButton:\n                text: 'Y'\n            MButton:\n                text: 'U'\n            MButton:\n                text: 'I'\n            MButton:\n                text: 'O'\n            MButton:\n                text: 'P'\n        BoxLayout:\n            id: line2\n            update_amount: root.update_text\n            size_hint: 1, None\n            height: '30dp'\n            Widget:\n                size_hint: 0.5, None\n                height: '33dp'\n            MButton:\n                text: 'A'\n            MButton:\n                text: 'S'\n            MButton:\n                text: 'D'\n            MButton:\n                text: 'F'\n            MButton:\n                text: 'G'\n            MButton:\n                text: 'H'\n            MButton:\n                text: 'J'\n            MButton:\n                text: 'K'\n            MButton:\n                text: 'L'\n            Widget:\n                size_hint: 0.5, None\n                height: '33dp'\n        BoxLayout:\n            id: line3\n            update_amount: root.update_text\n            size_hint: 1, None\n            height: '30dp'\n            Widget:\n                size_hint: 1, None\n            MButton:\n                text: 'Z'\n            MButton:\n                text: 'X'\n            MButton:\n                text: 'C'\n            MButton:\n                text: 'V'\n            MButton:\n                text: 'B'\n            MButton:\n                text: 'N'\n            MButton:\n                text: 'M'\n            MButton:\n                text: ' '\n            MButton:\n                text: '<'\n\n<AddXpubDialog>\n    title: ''\n    message: ''\n    BigLabel:\n        text: root.title\n    GridLayout\n        cols: 1\n        padding: 0, '12dp'\n        orientation: 'vertical'\n        spacing: '12dp'\n        size_hint: 1, None\n        height: self.minimum_height\n        SeedButton:\n            id: text_input\n            text: ''\n            on_text: Clock.schedule_once(root.check_text)\n        SeedLabel:\n            text: root.message\n    GridLayout\n        rows: 1\n        spacing: '12dp'\n        size_hint: 1, None\n        height: self.minimum_height\n        IconButton:\n            id: scan\n            height: '48sp'\n            on_release: root.scan_xpub()\n            icon: 'atlas://gui/kivy/theming/light/camera'\n            size_hint: 1, None\n        WizardButton:\n            text: _('Paste')\n            on_release: root.do_paste()\n        WizardButton:\n            text: _('Clear')\n            on_release: root.do_clear()\n\n\n<ShowXpubDialog>\n    xpub: ''\n    message: _('Here is your master public key. Share it with your cosigners.')\n    BigLabel:\n        text: \"MASTER PUBLIC KEY\"\n    GridLayout\n        cols: 1\n        padding: 0, '12dp'\n        orientation: 'vertical'\n        spacing: '12dp'\n        size_hint: 1, None\n        height: self.minimum_height\n        SeedButton:\n            id: text_input\n            text: root.xpub\n        SeedLabel:\n            text: root.message\n    GridLayout\n        rows: 1\n        spacing: '12dp'\n        size_hint: 1, None\n        height: self.minimum_height\n        WizardButton:\n            text: _('QR code')\n            on_release: root.do_qr()\n        WizardButton:\n            text: _('Copy')\n            on_release: root.do_copy()\n        WizardButton:\n            text: _('Share')\n            on_release: root.do_share()\n\n\n<ShowSeedDialog>\n    spacing: '12dp'\n    value: 'next'\n    BigLabel:\n        text: \"PLEASE WRITE DOWN YOUR SEED PHRASE\"\n    GridLayout:\n        id: grid\n        cols: 1\n        pos_hint: {'center_y': .5}\n        size_hint_y: None\n        height: self.minimum_height\n        orientation: 'vertical'\n        spacing: '12dp'\n        SeedButton:\n            text: root.seed_text\n            on_release: root.options_dialog()\n        SeedLabel:\n            text: root.message\n\n\n<LineDialog>\n\n    BigLabel:\n        text: root.title\n    SeedLabel:\n        text: root.message\n    TextInput:\n        id: passphrase_input\n        multiline: False\n        size_hint: 1, None\n        height: '27dp'\n    SeedLabel:\n        text: root.warning\n\n''')\n\n\n\nclass WizardDialog(EventsDialog):\n    ''' Abstract dialog to be used as the base for all Create Account Dialogs\n    '''\n    crcontent = ObjectProperty(None)\n\n    def __init__(self, wizard, **kwargs):\n        super(WizardDialog, self).__init__()\n        self.wizard = wizard\n        self.ids.back.disabled = not wizard.can_go_back()\n        self.app = App.get_running_app()\n        self.run_next = kwargs['run_next']\n        _trigger_size_dialog = Clock.create_trigger(self._size_dialog)\n        Window.bind(size=_trigger_size_dialog,\n                    rotation=_trigger_size_dialog)\n        _trigger_size_dialog()\n        self._on_release = False\n\n    def _size_dialog(self, dt):\n        app = App.get_running_app()\n        if app.ui_mode[0] == 'p':\n            self.size = Window.size\n        else:\n            #tablet\n            if app.orientation[0] == 'p':\n                #portrait\n                self.size = Window.size[0]/1.67, Window.size[1]/1.4\n            else:\n                self.size = Window.size[0]/2.5, Window.size[1]\n\n    def add_widget(self, widget, index=0):\n        if not self.crcontent:\n            super(WizardDialog, self).add_widget(widget)\n        else:\n            self.crcontent.add_widget(widget, index=index)\n\n    def on_dismiss(self):\n        app = App.get_running_app()\n        if app.wallet is None and not self._on_release:\n            app.stop()\n\n    def get_params(self, button):\n        return (None,)\n\n    def on_release(self, button):\n        self._on_release = True\n        self.close()\n        if not button:\n            self.parent.dispatch('on_wizard_complete', None)\n            return\n        if button is self.ids.back:\n            self.wizard.go_back()\n            return\n        params = self.get_params(button)\n        self.run_next(*params)\n\n\nclass WizardMultisigDialog(WizardDialog):\n\n    def get_params(self, button):\n        m = self.ids.m.value\n        n = self.ids.n.value\n        return m, n\n\nclass WizardChoiceDialog(WizardDialog):\n\n    def __init__(self, wizard, **kwargs):\n        super(WizardChoiceDialog, self).__init__(wizard, **kwargs)\n        self.message = kwargs.get('message', '')\n        choices = kwargs.get('choices', [])\n        layout = self.ids.choices\n        layout.bind(minimum_height=layout.setter('height'))\n        for action, text in choices:\n            l = WizardButton(text=text)\n            l.action = action\n            l.height = '48dp'\n            l.root = self\n            layout.add_widget(l)\n\n    def on_parent(self, instance, value):\n        if value:\n            app = App.get_running_app()\n            self._back = _back = partial(app.dispatch, 'on_back')\n\n    def get_params(self, button):\n        return (button.action,)\n\n\n\nclass LineDialog(WizardDialog):\n    title = StringProperty('')\n    message = StringProperty('')\n    warning = StringProperty('')\n\n    def __init__(self, wizard, **kwargs):\n        WizardDialog.__init__(self, wizard, **kwargs)\n        self.ids.next.disabled = False\n\n    def get_params(self, b):\n        return (self.ids.passphrase_input.text,)\n\nclass ShowSeedDialog(WizardDialog):\n    seed_text = StringProperty('')\n    message = _(\"If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.\")\n    ext = False\n\n    def __init__(self, wizard, **kwargs):\n        super(ShowSeedDialog, self).__init__(wizard, **kwargs)\n        self.seed_text = kwargs['seed_text']\n\n    def on_parent(self, instance, value):\n        if value:\n            app = App.get_running_app()\n            self._back = _back = partial(self.ids.back.dispatch, 'on_release')\n\n    def options_dialog(self):\n        from .seed_options import SeedOptionsDialog\n        def callback(status):\n            self.ext = status\n        d = SeedOptionsDialog(self.ext, callback)\n        d.open()\n\n    def get_params(self, b):\n        return (self.ext,)\n\n\nclass WordButton(Button):\n    pass\n\nclass WizardButton(Button):\n    pass\n\n\nclass RestoreSeedDialog(WizardDialog):\n\n    def __init__(self, wizard, **kwargs):\n        super(RestoreSeedDialog, self).__init__(wizard, **kwargs)\n        self._test = kwargs['test']\n        from electrum.mnemonic import Mnemonic\n        from electrum.old_mnemonic import words as old_wordlist\n        self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))\n        self.ids.text_input_seed.text = test_seed if is_test else ''\n        self.message = _('Please type your seed phrase using the virtual keyboard.')\n        self.title = _('Enter Seed')\n        self.ext = False\n\n    def options_dialog(self):\n        from .seed_options import SeedOptionsDialog\n        def callback(status):\n            self.ext = status\n        d = SeedOptionsDialog(self.ext, callback)\n        d.open()\n\n    def get_suggestions(self, prefix):\n        for w in self.words:\n            if w.startswith(prefix):\n                yield w\n\n    def on_text(self, dt):\n        self.ids.next.disabled = not bool(self._test(self.get_text()))\n\n        text = self.ids.text_input_seed.text\n        if not text:\n            last_word = ''\n        elif text[-1] == ' ':\n            last_word = ''\n        else:\n            last_word = text.split(' ')[-1]\n\n        enable_space = False\n        self.ids.suggestions.clear_widgets()\n        suggestions = [x for x in self.get_suggestions(last_word)]\n\n        if last_word in suggestions:\n            b = WordButton(text=last_word)\n            self.ids.suggestions.add_widget(b)\n            enable_space = True\n\n        for w in suggestions:\n            if w != last_word and len(suggestions) < 10:\n                b = WordButton(text=w)\n                self.ids.suggestions.add_widget(b)\n\n        i = len(last_word)\n        p = set()\n        for x in suggestions:\n            if len(x)>i: p.add(x[i])\n\n        for line in [self.ids.line1, self.ids.line2, self.ids.line3]:\n            for c in line.children:\n                if isinstance(c, Button):\n                    if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':\n                        c.disabled = (c.text.lower() not in p) and last_word\n                    elif c.text == ' ':\n                        c.disabled = not enable_space\n\n    def on_word(self, w):\n        text = self.get_text()\n        words = text.split(' ')\n        words[-1] = w\n        text = ' '.join(words)\n        self.ids.text_input_seed.text = text + ' '\n        self.ids.suggestions.clear_widgets()\n\n    def get_text(self):\n        ti = self.ids.text_input_seed\n        return ' '.join(ti.text.strip().split())\n\n    def update_text(self, c):\n        c = c.lower()\n        text = self.ids.text_input_seed.text\n        if c == '<':\n            text = text[:-1]\n        else:\n            text += c\n        self.ids.text_input_seed.text = text\n\n    def on_parent(self, instance, value):\n        if value:\n            tis = self.ids.text_input_seed\n            tis.focus = True\n            #tis._keyboard.bind(on_key_down=self.on_key_down)\n            self._back = _back = partial(self.ids.back.dispatch,\n                                         'on_release')\n            app = App.get_running_app()\n\n    def on_key_down(self, keyboard, keycode, key, modifiers):\n        if keycode[0] in (13, 271):\n            self.on_enter()\n            return True\n\n    def on_enter(self):\n        #self._remove_keyboard()\n        # press next\n        next = self.ids.next\n        if not next.disabled:\n            next.dispatch('on_release')\n\n    def _remove_keyboard(self):\n        tis = self.ids.text_input_seed\n        if tis._keyboard:\n            tis._keyboard.unbind(on_key_down=self.on_key_down)\n            tis.focus = False\n\n    def get_params(self, b):\n        return (self.get_text(), False, self.ext)\n\n\nclass ConfirmSeedDialog(RestoreSeedDialog):\n    def get_params(self, b):\n        return (self.get_text(),)\n    def options_dialog(self):\n        pass\n\n\nclass ShowXpubDialog(WizardDialog):\n\n    def __init__(self, wizard, **kwargs):\n        WizardDialog.__init__(self, wizard, **kwargs)\n        self.xpub = kwargs['xpub']\n        self.ids.next.disabled = False\n\n    def do_copy(self):\n        self.app._clipboard.copy(self.xpub)\n\n    def do_share(self):\n        self.app.do_share(self.xpub, _(\"Master Public Key\"))\n\n    def do_qr(self):\n        from .qr_dialog import QRDialog\n        popup = QRDialog(_(\"Master Public Key\"), self.xpub, True)\n        popup.open()\n\n\nclass AddXpubDialog(WizardDialog):\n\n    def __init__(self, wizard, **kwargs):\n        WizardDialog.__init__(self, wizard, **kwargs)\n        self.is_valid = kwargs['is_valid']\n        self.title = kwargs['title']\n        self.message = kwargs['message']\n        self.allow_multi = kwargs.get('allow_multi', False)\n\n    def check_text(self, dt):\n        self.ids.next.disabled = not bool(self.is_valid(self.get_text()))\n\n    def get_text(self):\n        ti = self.ids.text_input\n        return ti.text.strip()\n\n    def get_params(self, button):\n        return (self.get_text(),)\n\n    def scan_xpub(self):\n        def on_complete(text):\n            if self.allow_multi:\n                self.ids.text_input.text += text + '\\n'\n            else:\n                self.ids.text_input.text = text\n        self.app.scan_qr(on_complete)\n\n    def do_paste(self):\n        self.ids.text_input.text = test_xpub if is_test else self.app._clipboard.paste()\n\n    def do_clear(self):\n        self.ids.text_input.text = ''\n\n\n\n\nclass InstallWizard(BaseWizard, Widget):\n    '''\n    events::\n        `on_wizard_complete` Fired when the wizard is done creating/ restoring\n        wallet/s.\n    '''\n\n    __events__ = ('on_wizard_complete', )\n\n    def on_wizard_complete(self, wallet):\n        \"\"\"overriden by main_window\"\"\"\n        pass\n\n    def waiting_dialog(self, task, msg):\n        '''Perform a blocking task in the background by running the passed\n        method in a thread.\n        '''\n        def target():\n            # run your threaded function\n            try:\n                task()\n            except Exception as err:\n                self.show_error(str(err))\n            # on  completion hide message\n            Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)\n\n        app = App.get_running_app()\n        app.show_info_bubble(\n            text=msg, icon='atlas://gui/kivy/theming/light/important',\n            pos=Window.center, width='200sp', arrow_pos=None, modal=True)\n        t = threading.Thread(target = target)\n        t.start()\n\n    def terminate(self, **kwargs):\n        self.dispatch('on_wizard_complete', self.wallet)\n\n    def choice_dialog(self, **kwargs):\n        choices = kwargs['choices']\n        if len(choices) > 1:\n            WizardChoiceDialog(self, **kwargs).open()\n        else:\n            f = kwargs['run_next']\n            f(choices[0][0])\n\n    def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()\n    def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()\n    def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()\n\n    def confirm_seed_dialog(self, **kwargs):\n        kwargs['title'] = _('Confirm Seed')\n        kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')\n        ConfirmSeedDialog(self, **kwargs).open()\n\n    def restore_seed_dialog(self, **kwargs):\n        RestoreSeedDialog(self, **kwargs).open()\n\n    def add_xpub_dialog(self, **kwargs):\n        kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')\n        AddXpubDialog(self, **kwargs).open()\n\n    def add_cosigner_dialog(self, **kwargs):\n        kwargs['title'] = _(\"Add Cosigner\") + \" %d\"%kwargs['index']\n        kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')\n        AddXpubDialog(self, **kwargs).open()\n\n    def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()\n\n    def show_error(self, msg):\n        app = App.get_running_app()\n        Clock.schedule_once(lambda dt: app.show_error(msg))\n\n    def password_dialog(self, message, callback):\n        popup = PasswordDialog()\n        popup.init(message, callback)\n        popup.open()\n\n    def request_password(self, run_next):\n        def callback(pin):\n            if pin:\n                self.run('confirm_password', pin, run_next)\n            else:\n                run_next(None, None)\n        self.password_dialog('Choose a PIN code', callback)\n\n    def confirm_password(self, pin, run_next):\n        def callback(conf):\n            if conf == pin:\n                run_next(pin, False)\n            else:\n                self.show_error(_('PIN mismatch'))\n                self.run('request_password', run_next)\n        self.password_dialog('Confirm your PIN code', callback)\n\n    def action_dialog(self, action, run_next):\n        f = getattr(self, action)\n        f()\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/label_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nBuilder.load_string('''\n<LabelDialog@Popup>\n    id: popup\n    title: ''\n    size_hint: 0.8, 0.3\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        Widget:\n            size_hint: 1, 0.2\n        TextInput:\n            id:input\n            padding: '5dp'\n            size_hint: 1, None\n            height: '27dp'\n            pos_hint: {'center_y':.5}\n            text:''\n            multiline: False\n            background_normal: 'atlas://gui/kivy/theming/light/tab_btn'\n            background_active: 'atlas://gui/kivy/theming/light/textinput_active'\n            hint_text_color: self.foreground_color\n            foreground_color: 1, 1, 1, 1\n            font_size: '16dp'\n            focus: True\n        Widget:\n            size_hint: 1, 0.2\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.5\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(input.text)\n                    popup.dismiss()\n''')\n\nclass LabelDialog(Factory.Popup):\n\n    def __init__(self, title, text, callback):\n        Factory.Popup.__init__(self)\n        self.ids.input.text = text\n        self.callback = callback\n        self.title = title\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/nfc_transaction.py",
    "content": "class NFCTransactionDialog(AnimatedPopup):\n\n    mode = OptionProperty('send', options=('send','receive'))\n\n    scanner = ObjectProperty(None)\n\n    def __init__(self, **kwargs):\n        # Delayed Init\n        global NFCSCanner\n        if NFCSCanner is None:\n            from electrum_gui.kivy.nfc_scanner import NFCScanner\n        self.scanner = NFCSCanner\n\n        super(NFCTransactionDialog, self).__init__(**kwargs)\n        self.scanner.nfc_init()\n        self.scanner.bind()\n\n    def on_parent(self, instance, value):\n        sctr = self.ids.sctr\n        if value:\n            def _cmp(*l):\n                anim = Animation(rotation=2, scale=1, opacity=1)\n                anim.start(sctr)\n                anim.bind(on_complete=_start)\n\n            def _start(*l):\n                anim = Animation(rotation=350, scale=2, opacity=0)\n                anim.start(sctr)\n                anim.bind(on_complete=_cmp)\n            _start()\n            return\n        Animation.cancel_all(sctr)"
  },
  {
    "path": "gui/kivy/uix/dialogs/password_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\nfrom decimal import Decimal\nfrom kivy.clock import Clock\n\nBuilder.load_string('''\n\n<PasswordDialog@Popup>\n    id: popup\n    title: _('PIN Code')\n    message: ''\n    size_hint: 0.9, 0.9\n    BoxLayout:\n        orientation: 'vertical'\n        Widget:\n            size_hint: 1, 1\n        Label:\n            text: root.message\n            text_size: self.width, None\n            size: self.texture_size\n        Widget:\n            size_hint: 1, 1\n        Label:\n            id: a\n            text: ' * '*len(kb.password) + ' o '*(6-len(kb.password))\n        Widget:\n            size_hint: 1, 1\n        GridLayout:\n            id: kb\n            update_amount: popup.update_password\n            password: ''\n            on_password: popup.on_password(self.password)\n            size_hint: 1, None\n            height: '200dp'\n            cols: 3\n            KButton:\n                text: '1'\n            KButton:\n                text: '2'\n            KButton:\n                text: '3'\n            KButton:\n                text: '4'\n            KButton:\n                text: '5'\n            KButton:\n                text: '6'\n            KButton:\n                text: '7'\n            KButton:\n                text: '8'\n            KButton:\n                text: '9'\n            KButton:\n                text: 'Clear'\n            KButton:\n                text: '0'\n            KButton:\n                text: '<'\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Widget:\n                size_hint: 0.5, None\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('Cancel')\n                on_release:\n                    popup.dismiss()\n                    popup.callback(None)\n''')\n\n\nclass PasswordDialog(Factory.Popup):\n\n    #def __init__(self, message, callback):\n    #    Factory.Popup.__init__(self)\n\n    def init(self, message, callback):\n        self.message = message\n        self.callback = callback\n        self.ids.kb.password = ''\n\n    def update_password(self, c):\n        kb = self.ids.kb\n        text = kb.password\n        if c == '<':\n            text = text[:-1]\n        elif c == 'Clear':\n            text = ''\n        else:\n            text += c\n        kb.password = text\n\n    def on_password(self, pw):\n        if len(pw) == 6:\n            self.dismiss()\n            Clock.schedule_once(lambda dt: self.callback(pw), 0.1)\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/qr_dialog.py",
    "content": "from kivy.factory import Factory\nfrom kivy.lang import Builder\n\nBuilder.load_string('''\n<QRDialog@Popup>\n    id: popup\n    title: ''\n    data: ''\n    shaded: False\n    show_text: False\n    AnchorLayout:\n        anchor_x: 'center'\n        BoxLayout:\n            orientation: 'vertical'\n            size_hint: 1, 1\n            padding: '10dp'\n            spacing: '10dp'\n            QRCodeWidget:\n                id: qr\n            TopLabel:\n                text: root.data if root.show_text else ''\n            Widget:\n                size_hint: 1, 0.2\n            BoxLayout:\n                size_hint: 1, None\n                height: '48dp'\n                Widget:\n                    size_hint: 1, None\n                    height: '48dp'\n                Button:\n                    size_hint: 1, None\n                    height: '48dp'\n                    text: _('Close')\n                    on_release:\n                        popup.dismiss()\n''')\n\nclass QRDialog(Factory.Popup):\n    def __init__(self, title, data, show_text):\n        Factory.Popup.__init__(self)\n        self.title = title\n        self.data = data\n        self.show_text = show_text\n\n    def on_open(self):\n        self.ids.qr.set_data(self.data)\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/qr_scanner.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.lang import Builder\n\nFactory.register('QRScanner', module='electrum_gui.kivy.qr_scanner')\n\nclass QrScannerDialog(Factory.AnimatedPopup):\n\n    __events__ = ('on_complete', )\n\n    def on_symbols(self, instance, value):\n        instance.stop()\n        self.dismiss()\n        data = value[0].data\n        self.dispatch('on_complete', data)\n\n    def on_complete(self, x):\n        ''' Default Handler for on_complete event.\n        '''\n        print(x)\n\n\nBuilder.load_string('''\n<QrScannerDialog>\n    title:\n        _(\\\n        '[size=18dp]Hold your QRCode up to the camera[/size][size=7dp]\\\\n[/size]')\n    title_size: '24sp'\n    border: 7, 7, 7, 7\n    size_hint: None, None\n    size: '340dp', '290dp'\n    pos_hint: {'center_y': .53}\n    #separator_color: .89, .89, .89, 1\n    #separator_height: '1.2dp'\n    #title_color: .437, .437, .437, 1\n    #background: 'atlas://gui/kivy/theming/light/dialog'\n    on_activate:\n        qrscr.start()\n        qrscr.size = self.size\n    on_deactivate: qrscr.stop()\n    QRScanner:\n        id: qrscr\n        on_symbols: root.on_symbols(*args)\n''')\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/question.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\nfrom kivy.uix.checkbox import CheckBox\nfrom kivy.uix.label import Label\nfrom kivy.uix.widget import Widget\n\nfrom electrum_gui.kivy.i18n import _\n\nBuilder.load_string('''\n<Question@Popup>\n    id: popup\n    title: ''\n    message: ''\n    size_hint: 0.8, 0.5\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        Label:\n            id: label\n            text: root.message\n            text_size: self.width, None\n        Widget:\n            size_hint: 1, 0.1\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Button:\n                text: _('No')\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(False)\n                    popup.dismiss()\n            Button:\n                text: _('Yes')\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(True)\n                    popup.dismiss()\n''')\n\n\n\nclass Question(Factory.Popup):\n\n    def __init__(self, msg, callback):\n        Factory.Popup.__init__(self)\n        self.title = _('Question')\n        self.message = msg\n        self.callback = callback\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/seed_options.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nBuilder.load_string('''\n<SeedOptionsDialog@Popup>\n    id: popup\n    title: _('Seed Options')\n    size_hint: 0.8, 0.8\n    pos_hint: {'top':0.9}\n    BoxLayout:\n        orientation: 'vertical'\n        Label:\n            id: description\n            text: _('You may extend your seed with custom words')\n            halign: 'left'\n            text_size: self.width, None\n            size: self.texture_size\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Label:\n                text: _('Extend Seed')\n            CheckBox:\n                id:cb\n        Widget:\n            size_hint: 1, 0.1\n        BoxLayout:\n            orientation: 'horizontal'\n            size_hint: 1, 0.2\n            Button:\n                text: 'Cancel'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release: popup.dismiss()\n            Button:\n                text: 'OK'\n                size_hint: 0.5, None\n                height: '48dp'\n                on_release:\n                    root.callback(cb.active)\n                    popup.dismiss()\n''')\n\n\nclass SeedOptionsDialog(Factory.Popup):\n    def __init__(self, status, callback):\n        Factory.Popup.__init__(self)\n        self.ids.cb.active = status\n        self.callback = callback\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/settings.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nfrom electrum.util import base_units\nfrom electrum.i18n import languages\nfrom electrum_gui.kivy.i18n import _\nfrom electrum.plugins import run_hook\nfrom electrum import coinchooser\nfrom electrum.util import fee_levels\n\nfrom .choice_dialog import ChoiceDialog\n\nBuilder.load_string('''\n#:import partial functools.partial\n#:import _ electrum_gui.kivy.i18n._\n\n<SettingsDialog@Popup>\n    id: settings\n    title: _('Electrum Settings')\n    disable_pin: False\n    use_encryption: False\n    BoxLayout:\n        orientation: 'vertical'\n        ScrollView:\n            GridLayout:\n                id: scrollviewlayout\n                cols:1\n                size_hint: 1, None\n                height: self.minimum_height\n                padding: '10dp'\n                SettingsItem:\n                    lang: settings.get_language_name()\n                    title: 'Language' + ': ' + str(self.lang)\n                    description: _('Language')\n                    action: partial(root.language_dialog, self)\n                CardSeparator\n                SettingsItem:\n                    status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF')\n                    disabled: root.disable_pin\n                    title: _('PIN code') + ': ' + self.status\n                    description: _(\"Change your PIN code.\")\n                    action: partial(root.change_password, self)\n                CardSeparator\n                SettingsItem:\n                    bu: app.base_unit\n                    title: _('Denomination') + ': ' + self.bu\n                    description: _(\"Base unit for BTCP amounts.\")\n                    action: partial(root.unit_dialog, self)\n                CardSeparator\n                SettingsItem:\n                    status: root.fee_status()\n                    title: _('Fees') + ': ' + self.status\n                    description: _(\"Fees paid to the BTCP miners.\")\n                    action: partial(root.fee_dialog, self)\n                CardSeparator\n                SettingsItem:\n                    status: root.fx_status()\n                    title: _('Fiat Currency') + ': ' + self.status\n                    description: _(\"Display amounts in fiat currency.\")\n                    action: partial(root.fx_dialog, self)\n                CardSeparator\n                SettingsItem:\n                    status: 'ON' if bool(app.plugins.get('labels')) else 'OFF'\n                    title: _('Labels Sync') + ': ' + self.status\n                    description: _(\"Save and synchronize your labels.\")\n                    action: partial(root.plugin_dialog, 'labels', self)\n                CardSeparator\n                SettingsItem:\n                    status: 'ON' if app.use_rbf else 'OFF'\n                    title: _('Replace-by-fee') + ': ' + self.status\n                    description: _(\"Create replaceable transactions.\")\n                    message:\n                        _('If you check this box, your transactions will be marked as non-final,') \\\n                        + ' ' + _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pays higher fees.') \\\n                        + ' ' + _('Note that some merchants do not accept non-final transactions until they are confirmed.')\n                    action: partial(root.boolean_dialog, 'use_rbf', _('Replace by fee'), self.message)\n                CardSeparator\n                SettingsItem:\n                    status: _('Yes') if app.use_unconfirmed else _('No')\n                    title: _('Spend unconfirmed') + ': ' + self.status\n                    description: _(\"Use unconfirmed coins in transactions.\")\n                    message: _('Spend unconfirmed coins')\n                    action: partial(root.boolean_dialog, 'use_unconfirmed', _('Use unconfirmed'), self.message)\n                CardSeparator\n                SettingsItem:\n                    status: _('Yes') if app.use_change else _('No')\n                    title: _('Use change addresses') + ': ' + self.status\n                    description: _(\"Send your change to separate addresses.\")\n                    message: _('Send excess coins to change addresses')\n                    action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)\n\n                # disabled: there is currently only one coin selection policy\n                #CardSeparator\n                #SettingsItem:\n                #    status: root.coinselect_status()\n                #    title: _('Coin selection') + ': ' + self.status\n                #    description: \"Coin selection method\"\n                #    action: partial(root.coinselect_dialog, self)\n''')\n\n\n\nclass SettingsDialog(Factory.Popup):\n\n    def __init__(self, app):\n        self.app = app\n        self.plugins = self.app.plugins\n        self.config = self.app.electrum_config\n        Factory.Popup.__init__(self)\n        layout = self.ids.scrollviewlayout\n        layout.bind(minimum_height=layout.setter('height'))\n        # cached dialogs\n        self._fx_dialog = None\n        self._fee_dialog = None\n        self._proxy_dialog = None\n        self._language_dialog = None\n        self._unit_dialog = None\n        self._coinselect_dialog = None\n\n    def update(self):\n        self.wallet = self.app.wallet\n        self.disable_pin = self.wallet.is_watching_only() if self.wallet else True\n        self.use_encryption = self.wallet.has_password() if self.wallet else False\n\n    def get_language_name(self):\n        return languages.get(self.config.get('language', 'en_UK'), '')\n\n    def change_password(self, item, dt):\n        self.app.change_password(self.update)\n\n    def language_dialog(self, item, dt):\n        if self._language_dialog is None:\n            l = self.config.get('language', 'en_UK')\n            def cb(key):\n                self.config.set_key(\"language\", key, True)\n                item.lang = self.get_language_name()\n                self.app.language = key\n            self._language_dialog = ChoiceDialog(_('Language'), languages, l, cb)\n        self._language_dialog.open()\n\n    def unit_dialog(self, item, dt):\n        if self._unit_dialog is None:\n            def cb(text):\n                self.app._set_bu(text)\n                item.bu = self.app.base_unit\n            self._unit_dialog = ChoiceDialog(_('Denomination'), list(base_units.keys()), self.app.base_unit, cb)\n        self._unit_dialog.open()\n\n    def coinselect_status(self):\n        return coinchooser.get_name(self.app.electrum_config)\n\n    def coinselect_dialog(self, item, dt):\n        if self._coinselect_dialog is None:\n            choosers = sorted(coinchooser.COIN_CHOOSERS.keys())\n            chooser_name = coinchooser.get_name(self.config)\n            def cb(text):\n                self.config.set_key('coin_chooser', text)\n                item.status = text\n            self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb)\n        self._coinselect_dialog.open()\n\n    def proxy_status(self):\n        server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()\n        return proxy.get('host') +':' + proxy.get('port') if proxy else _('None')\n\n    def proxy_dialog(self, item, dt):\n        if self._proxy_dialog is None:\n            server, port, protocol, proxy, auto_connect = self.app.network.get_parameters()\n            def callback(popup):\n                if popup.ids.mode.text != 'None':\n                    proxy = {\n                        'mode':popup.ids.mode.text,\n                        'host':popup.ids.host.text,\n                        'port':popup.ids.port.text,\n                        'user':popup.ids.user.text,\n                        'password':popup.ids.password.text\n                    }\n                else:\n                    proxy = None\n                self.app.network.set_parameters(server, port, protocol, proxy, auto_connect)\n                item.status = self.proxy_status()\n            popup = Builder.load_file('gui/kivy/uix/ui_screens/proxy.kv')\n            popup.ids.mode.text = proxy.get('mode') if proxy else 'None'\n            popup.ids.host.text = proxy.get('host') if proxy else ''\n            popup.ids.port.text = proxy.get('port') if proxy else ''\n            popup.ids.user.text = proxy.get('user') if proxy else ''\n            popup.ids.password.text = proxy.get('password') if proxy else ''\n            popup.on_dismiss = lambda: callback(popup)\n            self._proxy_dialog = popup\n        self._proxy_dialog.open()\n\n    def plugin_dialog(self, name, label, dt):\n        from .checkbox_dialog import CheckBoxDialog\n        def callback(status):\n            self.plugins.enable(name) if status else self.plugins.disable(name)\n            label.status = 'ON' if status else 'OFF'\n        status = bool(self.plugins.get(name))\n        dd = self.plugins.descriptions.get(name)\n        descr = dd.get('description')\n        fullname = dd.get('fullname')\n        d = CheckBoxDialog(fullname, descr, status, callback)\n        d.open()\n\n    def fee_status(self):\n        if self.config.get('dynamic_fees', True):\n            return fee_levels[self.config.get('fee_level', 2)]\n        else:\n            return self.app.format_amount_and_units(self.config.fee_per_kb()) + '/kB'\n\n    def fee_dialog(self, label, dt):\n        if self._fee_dialog is None:\n            from .fee_dialog import FeeDialog\n            def cb():\n                label.status = self.fee_status()\n            self._fee_dialog = FeeDialog(self.app, self.config, cb)\n        self._fee_dialog.open()\n\n    def boolean_dialog(self, name, title, message, dt):\n        from .checkbox_dialog import CheckBoxDialog\n        CheckBoxDialog(title, message, getattr(self.app, name), lambda x: setattr(self.app, name, x)).open()\n\n    def fx_status(self):\n        fx = self.app.fx\n        if fx.is_enabled():\n            source = fx.exchange.name()\n            ccy = fx.get_currency()\n            return '%s [%s]' %(ccy, source)\n        else:\n            return _('None')\n\n    def fx_dialog(self, label, dt):\n        if self._fx_dialog is None:\n            from .fx_dialog import FxDialog\n            def cb():\n                label.status = self.fx_status()\n            self._fx_dialog = FxDialog(self.app, self.plugins, self.config, cb)\n        self._fx_dialog.open()\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/tx_dialog.py",
    "content": "from kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\nfrom kivy.clock import Clock\nfrom kivy.uix.label import Label\n\nfrom electrum_gui.kivy.i18n import _\nfrom datetime import datetime\nfrom electrum.util import InvalidPassword\n\nBuilder.load_string('''\n\n<TxDialog>\n    id: popup\n    title: _('Transaction')\n    is_mine: True\n    can_sign: False\n    can_broadcast: False\n    can_rbf: False\n    fee_str: ''\n    date_str: ''\n    amount_str: ''\n    tx_hash: ''\n    status_str: ''\n    description: ''\n    outputs_str: ''\n    BoxLayout:\n        orientation: 'vertical'\n        ScrollView:\n            GridLayout:\n                height: self.minimum_height\n                size_hint_y: None\n                cols: 1\n                spacing: '10dp'\n                padding: '10dp'\n                GridLayout:\n                    height: self.minimum_height\n                    size_hint_y: None\n                    cols: 1\n                    spacing: '10dp'\n                    BoxLabel:\n                        text: _('Status')\n                        value: root.status_str\n                    BoxLabel:\n                        text: _('Description') if root.description else ''\n                        value: root.description\n                    BoxLabel:\n                        text: _('Date') if root.date_str else ''\n                        value: root.date_str\n                    BoxLabel:\n                        text: _('Amount sent') if root.is_mine else _('Amount received')\n                        value: root.amount_str\n                    BoxLabel:\n                        text: _('Transaction fee') if root.fee_str else ''\n                        value: root.fee_str\n                TopLabel:\n                    text: _('Outputs') + ':'\n                OutputList:\n                    height: self.minimum_height\n                    size_hint: 1, None\n                    id: output_list\n                TopLabel:\n                    text: _('Transaction ID') + ':' if root.tx_hash else ''\n                TxHashLabel:\n                    data: root.tx_hash\n                    name: _('Transaction ID')\n        Widget:\n            size_hint: 1, 0.1\n\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('Sign') if root.can_sign else _('Broadcast') if root.can_broadcast else _('Bump fee') if root.can_rbf else ''\n                disabled: not(root.can_sign or root.can_broadcast or root.can_rbf)\n                opacity: 0 if self.disabled else 1\n                on_release:\n                    if root.can_sign: root.do_sign()\n                    if root.can_broadcast: root.do_broadcast()\n                    if root.can_rbf: root.do_rbf()\n            IconButton:\n                size_hint: 0.5, None\n                height: '48dp'\n                icon: 'atlas://gui/kivy/theming/light/qrcode'\n                on_release: root.show_qr()\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('Close')\n                on_release: root.dismiss()\n''')\n\n\nclass TxDialog(Factory.Popup):\n\n    def __init__(self, app, tx):\n        Factory.Popup.__init__(self)\n        self.app = app\n        self.wallet = self.app.wallet\n        self.tx = tx\n\n    def on_open(self):\n        self.update()\n\n    def update(self):\n        format_amount = self.app.format_amount_and_units\n        tx_hash, self.status_str, self.description, self.can_broadcast, self.can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)\n        self.tx_hash = tx_hash or ''\n        if timestamp:\n            self.date_str = datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]\n        elif exp_n:\n            self.date_str = _('Within %d blocks') % exp_n if exp_n > 0 else _('unknown (low fee)')\n        else:\n            self.date_str = ''\n\n        if amount is None:\n            self.amount_str = _(\"Transaction unrelated to your wallet\")\n        elif amount > 0:\n            self.is_mine = False\n            self.amount_str = format_amount(amount)\n        else:\n            self.is_mine = True\n            self.amount_str = format_amount(-amount)\n        self.fee_str = format_amount(fee) if fee is not None else _('unknown')\n        self.can_sign = self.wallet.can_sign(self.tx)\n        self.ids.output_list.update(self.tx.outputs())\n\n    def do_rbf(self):\n        from .bump_fee_dialog import BumpFeeDialog\n        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(self.tx)\n        size = self.tx.estimated_size()\n        d = BumpFeeDialog(self.app, fee, size, self._do_rbf)\n        d.open()\n\n    def _do_rbf(self, old_fee, new_fee, is_final):\n        if new_fee is None:\n            return\n        delta = new_fee - old_fee\n        if delta < 0:\n            self.app.show_error(\"fee too low\")\n            return\n        try:\n            new_tx = self.wallet.bump_fee(self.tx, delta)\n        except BaseException as e:\n            self.app.show_error(str(e))\n            return\n        if is_final:\n            new_tx.set_rbf(False)\n        self.tx = new_tx\n        self.update()\n        self.do_sign()\n\n    def do_sign(self):\n        self.app.protected(_(\"Enter your PIN code in order to sign this transaction\"), self._do_sign, ())\n\n    def _do_sign(self, password):\n        self.status_str = _('Signing') + '...'\n        Clock.schedule_once(lambda dt: self.__do_sign(password), 0.1)\n\n    def __do_sign(self, password):\n        try:\n            self.app.wallet.sign_transaction(self.tx, password)\n        except InvalidPassword:\n            self.app.show_error(_(\"Invalid PIN\"))\n        self.update()\n\n    def do_broadcast(self):\n        self.app.broadcast(self.tx)\n\n    def show_qr(self):\n        from electrum.bitcoin import base_encode, bfh\n        text = bfh(str(self.tx))\n        text = base_encode(text, base=43)\n        self.app.qr_dialog(_(\"Raw Transaction\"), text)\n"
  },
  {
    "path": "gui/kivy/uix/dialogs/wallets.py",
    "content": "import os\n\nfrom kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import ObjectProperty\nfrom kivy.lang import Builder\n\nfrom electrum.util import base_units\n\nfrom ...i18n import _\nfrom .label_dialog import LabelDialog\n\nBuilder.load_string('''\n#:import os os\n<WalletDialog@Popup>:\n    title: _('Wallets')\n    id: popup\n    path: os.path.dirname(app.get_wallet_path())\n    BoxLayout:\n        orientation: 'vertical'\n        padding: '10dp'\n        FileChooserListView:\n            id: wallet_selector\n            dirselect: False\n            filter_dirs: True\n            filter: '*.*'\n            path: root.path\n            rootpath: root.path\n            size_hint_y: 0.6\n        Widget\n            size_hint_y: 0.1\n        GridLayout:\n            cols: 3\n            size_hint_y: 0.1\n            Button:\n                id: open_button\n                size_hint: 0.1, None\n                height: '48dp'\n                text: _('New')\n                on_release:\n                    popup.dismiss()\n                    root.new_wallet(app, wallet_selector.path)\n            Button:\n                id: open_button\n                size_hint: 0.1, None\n                height: '48dp'\n                text: _('Open')\n                disabled: not wallet_selector.selection\n                on_release:\n                    popup.dismiss()\n                    root.open_wallet(app)\n''')\n\nclass WalletDialog(Factory.Popup):\n\n    def new_wallet(self, app, dirname):\n        def cb(text):\n            if text:\n                app.load_wallet_by_name(os.path.join(dirname, text))\n        d = LabelDialog(_('Enter wallet name'), '', cb)\n        d.open()\n\n    def open_wallet(self, app):\n        app.load_wallet_by_name(self.ids.wallet_selector.selection[0])\n\n"
  },
  {
    "path": "gui/kivy/uix/drawer.py",
    "content": "'''Drawer Widget to hold the main window and the menu/hidden section that\ncan be swiped in from the left. This Menu would be only hidden in phone mode\nand visible in Tablet Mode.\n\nThis class is specifically in lined to save on start up speed(minimize i/o).\n'''\n\nfrom kivy.app import App\nfrom kivy.factory import Factory\nfrom kivy.properties import OptionProperty, NumericProperty, ObjectProperty\nfrom kivy.clock import Clock\nfrom kivy.lang import Builder\n\nimport gc\n\n# delayed imports\napp = None\n\n\nclass Drawer(Factory.RelativeLayout):\n    '''Drawer Widget to hold the main window and the menu/hidden section that\n    can be swiped in from the left. This Menu would be only hidden in phone mode\n    and visible in Tablet Mode.\n\n    '''\n\n    state = OptionProperty('closed',\n                            options=('closed', 'open', 'opening', 'closing'))\n    '''This indicates the current state the drawer is in.\n\n    :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of\n    `closed`, `open`, `opening`, `closing`.\n    '''\n\n    scroll_timeout = NumericProperty(200)\n    '''Timeout allowed to trigger the :data:`scroll_distance`,\n    in milliseconds. If the user has not moved :data:`scroll_distance`\n    within the timeout, the scrolling will be disabled and the touch event\n    will go to the children.\n\n    :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`\n    and defaults to 200 (milliseconds)\n    '''\n\n    scroll_distance = NumericProperty('9dp')\n    '''Distance to move before scrolling the :class:`Drawer` in pixels.\n    As soon as the distance has been traveled, the :class:`Drawer` will\n    start to scroll, and no touch event will go to children.\n    It is advisable that you base this value on the dpi of your target\n    device's screen.\n\n    :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`\n    and defaults to 20dp.\n    '''\n\n    drag_area = NumericProperty('9dp')\n    '''The percentage of area on the left edge that triggers the opening of\n    the drawer. from 0-1\n\n    :attr:`drag_area` is a `NumericProperty` defaults to 2\n    '''\n\n    hidden_widget = ObjectProperty(None)\n    ''' This is the widget that is hidden in phone mode on the left side of\n    drawer or displayed on the left of the overlay widget in tablet mode.\n\n    :attr:`hidden_widget` is a `ObjectProperty` defaults to None.\n    '''\n\n    overlay_widget = ObjectProperty(None)\n    '''This a pointer to the default widget that is overlayed either on top or\n    to the right of the hidden widget.\n    '''\n\n    def __init__(self, **kwargs):\n        super(Drawer, self).__init__(**kwargs)\n\n        self._triigger_gc = Clock.create_trigger(self._re_enable_gc, .2)\n\n    def toggle_drawer(self):\n        if app.ui_mode[0] == 't':\n            return\n        Factory.Animation.cancel_all(self.overlay_widget)\n        anim = Factory.Animation(x=self.hidden_widget.width\n                            if self.state in ('opening', 'closed') else 0,\n                            d=.1, t='linear')\n        anim.bind(on_complete = self._complete_drawer_animation)\n        anim.start(self.overlay_widget)\n\n    def _re_enable_gc(self, dt):\n        global gc\n        gc.enable()\n\n    def on_touch_down(self, touch):\n        if self.disabled:\n            return\n\n        if not self.collide_point(*touch.pos):\n            return\n\n        touch.grab(self)\n\n        # disable gc for smooth interaction\n        # This is still not enough while wallet is synchronising\n        # look into pausing all background tasks while ui interaction like this\n        gc.disable()\n\n        global app\n        if not app:\n            app = App.get_running_app()\n\n        # skip on tablet mode\n        if app.ui_mode[0] == 't':\n            return super(Drawer, self).on_touch_down(touch)\n\n        state = self.state\n        touch.ud['send_touch_down'] = False\n        start = 0 #if state[0] == 'c' else self.hidden_widget.right\n        drag_area = self.drag_area\\\n           if self.state[0] == 'c' else\\\n           (self.overlay_widget.x)\n\n        if touch.x < start or touch.x > drag_area:\n            if self.state == 'open':\n                self.toggle_drawer()\n                return\n            return super(Drawer, self).on_touch_down(touch)\n\n        self._touch = touch\n        Clock.schedule_once(self._change_touch_mode,\n                            self.scroll_timeout/1000.)\n        touch.ud['in_drag_area'] = True\n        touch.ud['send_touch_down'] = True\n        return\n\n    def on_touch_move(self, touch):\n        if not touch.grab_current is self:\n            return\n        self._touch = False\n        # skip on tablet mode\n        if app.ui_mode[0] == 't':\n            return super(Drawer, self).on_touch_move(touch)\n\n        if not touch.ud.get('in_drag_area', None):\n            return super(Drawer, self).on_touch_move(touch)\n\n        ov = self.overlay_widget\n        ov.x=min(self.hidden_widget.width,\n            max(ov.x + touch.dx*2, 0))\n\n        #_anim = Animation(x=x, duration=1/2, t='in_out_quart')\n        #_anim.cancel_all(ov)\n        #_anim.start(ov)\n\n        if abs(touch.x - touch.ox) < self.scroll_distance:\n            return\n\n        touch.ud['send_touch_down'] = False\n        Clock.unschedule(self._change_touch_mode)\n        self._touch = None\n        self.state = 'opening' if touch.dx > 0 else 'closing'\n        touch.ox = touch.x\n        return\n\n    def _change_touch_mode(self, *args):\n        if not self._touch:\n            return\n        touch = self._touch\n        touch.ungrab(self)\n        touch.ud['in_drag_area'] = False\n        touch.ud['send_touch_down'] = False\n        self._touch = None\n        super(Drawer, self).on_touch_down(touch)\n        return\n\n    def on_touch_up(self, touch):\n        if not touch.grab_current is self:\n            return\n\n        self._triigger_gc()\n\n        touch.ungrab(self)\n        touch.grab_current = None\n\n        # skip on tablet mode\n        get  = touch.ud.get\n        if app.ui_mode[0] == 't':\n            return super(Drawer, self).on_touch_up(touch)\n\n        self.old_x = [1, ] * 10\n        self.speed = sum((\n            (self.old_x[x + 1] - self.old_x[x]) for x in range(9))) / 9.\n\n        if get('send_touch_down', None):\n            # touch up called before moving\n            Clock.unschedule(self._change_touch_mode)\n            self._touch = None\n            Clock.schedule_once(\n                lambda dt: super(Drawer, self).on_touch_down(touch))\n        if get('in_drag_area', None):\n            if abs(touch.x - touch.ox) < self.scroll_distance:\n                anim_to = (0 if self.state[0] == 'c'\n                      else self.hidden_widget.width)\n                Factory.Animation(x=anim_to, d=.1).start(self.overlay_widget)\n                return\n            touch.ud['in_drag_area'] = False\n            if not get('send_touch_down', None):\n                self.toggle_drawer()\n        Clock.schedule_once(lambda dt: super(Drawer, self).on_touch_up(touch))\n\n    def _complete_drawer_animation(self, *args):\n        self.state = 'open' if self.state in ('opening', 'closed') else 'closed'\n\n    def add_widget(self, widget, index=1):\n        if not widget:\n            return\n\n        iget = self.ids.get\n        if not iget('hidden_widget') or not iget('overlay_widget'):\n            super(Drawer, self).add_widget(widget)\n            return\n\n        if not self.hidden_widget:\n            self.hidden_widget = self.ids.hidden_widget\n        if not self.overlay_widget:\n            self.overlay_widget = self.ids.overlay_widget\n\n        if self.overlay_widget.children and self.hidden_widget.children:\n            Logger.debug('Drawer: Accepts only two widgets. discarding rest')\n            return\n\n        if not self.hidden_widget.children:\n            self.hidden_widget.add_widget(widget)\n        else:\n            self.overlay_widget.add_widget(widget)\n            widget.x = 0\n\n    def remove_widget(self, widget):\n        if self.overlay_widget.children[0] == widget:\n            self.overlay_widget.clear_widgets()\n            return\n        if widget == self.hidden_widget.children:\n            self.hidden_widget.clear_widgets()\n            return\n\n    def clear_widgets(self):\n        self.overlay_widget.clear_widgets()\n        self.hidden_widget.clear_widgets()\n\nif __name__ == '__main__':\n    from kivy.app import runTouchApp\n    from kivy.lang import Builder\n    runTouchApp(Builder.load_string('''\nDrawer:\n    Button:\n    Button\n'''))"
  },
  {
    "path": "gui/kivy/uix/gridview.py",
    "content": "from kivy.uix.boxlayout import BoxLayout\nfrom kivy.adapters.dictadapter import DictAdapter\nfrom kivy.adapters.listadapter import ListAdapter\nfrom kivy.properties import ObjectProperty, ListProperty, AliasProperty\nfrom kivy.uix.listview import (ListItemButton, ListItemLabel, CompositeListItem,\n                               ListView)\nfrom kivy.lang import Builder\nfrom kivy.metrics import dp, sp\n\nBuilder.load_string('''\n<GridView>\n    header_view: header_view\n    content_view: content_view\n    BoxLayout:\n        orientation: 'vertical'\n        padding: '0dp', '2dp'\n        BoxLayout:\n            id: header_box\n            orientation: 'vertical'\n            size_hint: 1, None\n            height: '30dp'\n            ListView:\n                id: header_view\n        BoxLayout:\n            id: content_box\n            orientation: 'vertical'\n            ListView:\n                id: content_view\n\n<-HorizVertGrid>\n    header_view: header_view\n    content_view: content_view\n    ScrollView:\n        id: scrl\n        do_scroll_y: False\n        RelativeLayout:\n            size_hint_x: None\n            width: max(scrl.width, dp(sum(root.widths)))\n            BoxLayout:\n                orientation: 'vertical'\n                padding: '0dp', '2dp'\n                BoxLayout:\n                    id: header_box\n                    orientation: 'vertical'\n                    size_hint: 1, None\n                    height: '30dp'\n                    ListView:\n                        id: header_view\n                BoxLayout:\n                    id: content_box\n                    orientation: 'vertical'\n                    ListView:\n                        id: content_view\n\n''')\n\nclass GridView(BoxLayout):\n    \"\"\"Workaround solution for grid view by using 2 list view.\n    Sometimes the height of lines is shown properly.\"\"\"\n\n    def _get_hd_adpt(self):\n        return self.ids.header_view.adapter\n\n    header_adapter = AliasProperty(_get_hd_adpt, None)\n    '''\n    '''\n\n    def _get_cnt_adpt(self):\n        return self.ids.content_view.adapter\n\n    content_adapter = AliasProperty(_get_cnt_adpt, None)\n    '''\n    '''\n\n    headers = ListProperty([])\n    '''\n    '''\n\n    widths = ListProperty([])\n    '''\n    '''\n\n    data = ListProperty([])\n    '''\n    '''\n\n    getter = ObjectProperty(lambda item, i: item[i])\n    '''\n    '''\n    on_context_menu = ObjectProperty(None)\n\n    def __init__(self, **kwargs):\n        self._from_widths = False\n        super(GridView, self).__init__(**kwargs)\n        #self.on_headers(self, self.headers)\n\n    def on_widths(self, instance, value):\n        if not self.get_root_window():\n            return\n        self._from_widths = True\n        self.on_headers(instance, self.headers)\n        self._from_widths = False\n\n    def on_headers(self, instance, value):\n        if not self._from_widths:\n            return\n        if not (value and self.canvas and self.headers):\n            return\n        widths = self.widths\n        if len(self.widths) != len(value):\n            return\n        #if widths is not None:\n        #    widths = ['%sdp' % i for i in widths]\n\n        def generic_args_converter(row_index,\n                                   item,\n                                   is_header=True,\n                                   getter=self.getter):\n            cls_dicts = []\n            _widths = self.widths\n            getter = self.getter\n            on_context_menu = self.on_context_menu\n\n            for i, header in enumerate(self.headers):\n                kwargs = {\n                    'padding': ('2dp','2dp'),\n                    'halign': 'center',\n                    'valign': 'middle',\n                    'size_hint_y': None,\n                    'shorten': True,\n                    'height': '30dp',\n                    'text_size': (_widths[i], dp(30)),\n                    'text': getter(item, i),\n                }\n\n                kwargs['font_size'] = '9sp'\n                if is_header:\n                    kwargs['deselected_color'] = kwargs['selected_color']  =\\\n                        [0, 1, 1, 1]\n                else:  # this is content\n                    kwargs['deselected_color'] = 1, 1, 1, 1\n                    if on_context_menu is not None:\n                        kwargs['on_press'] = on_context_menu\n\n                if widths is not None:  # set width manually\n                    kwargs['size_hint_x'] = None\n                    kwargs['width'] = widths[i]\n\n                cls_dicts.append({\n                    'cls': ListItemButton,\n                    'kwargs': kwargs,\n                })\n\n            return {\n                'id': item[-1],\n                'size_hint_y': None,\n                'height': '30dp',\n                'cls_dicts': cls_dicts,\n            }\n\n        def header_args_converter(row_index, item):\n            return generic_args_converter(row_index, item)\n\n        def content_args_converter(row_index, item):\n            return generic_args_converter(row_index, item, is_header=False)\n\n\n        self.ids.header_view.adapter = ListAdapter(data=[self.headers],\n                                   args_converter=header_args_converter,\n                                   selection_mode='single',\n                                   allow_empty_selection=False,\n                                   cls=CompositeListItem)\n\n        self.ids.content_view.adapter = ListAdapter(data=self.data,\n                                   args_converter=content_args_converter,\n                                   selection_mode='single',\n                                   allow_empty_selection=False,\n                                   cls=CompositeListItem)\n        self.content_adapter.bind_triggers_to_view(self.ids.content_view._trigger_reset_populate)\n\nclass HorizVertGrid(GridView):\n    pass\n\n\nif __name__ == \"__main__\":\n    from kivy.app import App\n    class MainApp(App):\n\n        def build(self):\n            data = []\n            for i in range(90):\n                data.append((str(i), str(i)))\n            self.data = data\n            return Builder.load_string('''\nBoxLayout:\n    orientation: 'vertical'\n    HorizVertGrid:\n        on_parent: if args[1]: self.content_adapter.data = app.data\n        headers:['Address', 'Previous output']\n        widths: [400, 500]\n\n<Label>\n    font_size: '16sp'\n''')\n    MainApp().run()\n"
  },
  {
    "path": "gui/kivy/uix/menus.py",
    "content": "from functools import partial\n\nfrom kivy.animation import Animation\nfrom kivy.core.window import Window\nfrom kivy.clock import Clock\nfrom kivy.uix.bubble import Bubble, BubbleButton\nfrom kivy.properties import ListProperty\nfrom kivy.uix.widget import Widget\n\nfrom electrum_gui.i18n import _\n\nclass ContextMenuItem(Widget):\n    '''abstract class\n    '''\n\nclass ContextButton(ContextMenuItem, BubbleButton):\n    pass\n\nclass ContextMenu(Bubble):\n\n    buttons = ListProperty([_('ok'), _('cancel')])\n    '''List of Buttons to be displayed at the bottom'''\n\n    __events__ = ('on_press', 'on_release')\n\n    def __init__(self, **kwargs):\n        self._old_buttons = self.buttons\n        super(ContextMenu, self).__init__(**kwargs)\n        self.on_buttons(self, self.buttons)\n\n    def on_touch_down(self, touch):\n        if not self.collide_point(*touch.pos):\n            self.hide()\n            return\n        return super(ContextMenu, self).on_touch_down(touch)\n\n    def on_buttons(self, _menu, value):\n        if 'menu_content' not in self.ids.keys():\n            return\n        if value == self._old_buttons:\n            return\n        blayout = self.ids.menu_content\n        blayout.clear_widgets()\n        for btn in value:\n            ib = ContextButton(text=btn)\n            ib.bind(on_press=partial(self.dispatch, 'on_press'))\n            ib.bind(on_release=partial(self.dispatch, 'on_release'))\n            blayout.add_widget(ib)\n        self._old_buttons = value\n\n    def on_press(self, instance):\n        pass\n\n    def on_release(self, instance):\n        pass\n\n    def show(self, pos, duration=0):\n        Window.add_widget(self)\n        # wait for the bubble to adjust it's size according to text then animate\n        Clock.schedule_once(lambda dt: self._show(pos, duration))\n\n    def _show(self, pos, duration):\n        def on_stop(*l):\n            if duration:\n                Clock.schedule_once(self.hide, duration + .5)\n\n        self.opacity = 0\n        arrow_pos = self.arrow_pos\n        if arrow_pos[0] in ('l', 'r'):\n            pos = pos[0], pos[1] - (self.height/2)\n        else:\n            pos = pos[0] - (self.width/2), pos[1]\n\n        self.limit_to = Window\n\n        anim = Animation(opacity=1, pos=pos, d=.32)\n        anim.bind(on_complete=on_stop)\n        anim.cancel_all(self)\n        anim.start(self)\n\n\n    def hide(self, *dt):\n\n        def on_stop(*l):\n            Window.remove_widget(self)\n        anim = Animation(opacity=0, d=.25)\n        anim.bind(on_complete=on_stop)\n        anim.cancel_all(self)\n        anim.start(self)\n\n    def add_widget(self, widget, index=0):\n        if not isinstance(widget, ContextMenuItem):\n            super(ContextMenu, self).add_widget(widget, index)\n            return\n        menu_content.add_widget(widget, index)\n"
  },
  {
    "path": "gui/kivy/uix/qrcodewidget.py",
    "content": "''' Kivy Widget that accepts data and displays qrcode\n'''\n\nfrom threading import Thread\nfrom functools import partial\n\nimport qrcode\n\nfrom kivy.uix.floatlayout import FloatLayout\nfrom kivy.graphics.texture import Texture\nfrom kivy.properties import StringProperty\nfrom kivy.properties import ObjectProperty, StringProperty, ListProperty,\\\n    BooleanProperty\nfrom kivy.lang import Builder\nfrom kivy.clock import Clock\n\n\n\nBuilder.load_string('''\n<QRCodeWidget>\n    canvas.before:\n        # Draw white Rectangle\n        Color:\n            rgba: root.background_color\n        Rectangle:\n            size: self.size\n            pos: self.pos\n    canvas.after:\n        Color:\n            rgba: root.foreground_color\n        Rectangle:\n            size: self.size\n            pos: self.pos\n    Image\n        id: qrimage\n        pos_hint: {'center_x': .5, 'center_y': .5}\n        allow_stretch: True\n        size_hint: None, None\n        size: root.width * .9, root.height * .9\n''')\n\nclass QRCodeWidget(FloatLayout):\n\n    data = StringProperty(None, allow_none=True)\n    background_color = ListProperty((1, 1, 1, 1))\n    foreground_color = ListProperty((0, 0, 0, 0))\n\n    def __init__(self, **kwargs):\n        super(QRCodeWidget, self).__init__(**kwargs)\n        self.data = None\n        self.qr = None\n        self._qrtexture = None\n\n    def on_data(self, instance, value):\n        if not (self.canvas or value):\n            return\n        self.update_qr()\n\n    def set_data(self, data):\n        if self.data == data:\n            return\n        MinSize = 210 if len(data) < 128 else 500\n        self.setMinimumSize((MinSize, MinSize))\n        self.data = data\n        self.qr = None\n\n    def update_qr(self):\n        if not self.data and self.qr:\n            return\n        L = qrcode.constants.ERROR_CORRECT_L\n        data = self.data\n        self.qr = qr = qrcode.QRCode(\n            version=None,\n            error_correction=L,\n            box_size=10,\n            border=0,\n        )\n        qr.add_data(data)\n        qr.make(fit=True)\n        self.update_texture()\n\n    def setMinimumSize(self, size):\n        # currently unused, do we need this?\n        self._texture_size = size\n\n    def _create_texture(self, k):\n        self._qrtexture = texture = Texture.create(size=(k,k), colorfmt='rgb')\n        # don't interpolate texture\n        texture.min_filter = 'nearest'\n        texture.mag_filter = 'nearest'\n\n    def update_texture(self):\n        if not self.qr:\n            return\n        matrix = self.qr.get_matrix()\n        k = len(matrix)\n        # create the texture\n        self._create_texture(k)\n        buff = []\n        bext = buff.extend\n        cr, cg, cb, ca = self.background_color[:]\n        cr, cg, cb = cr*255, cg*255, cb*255\n        for r in range(k):\n            for c in range(k):\n                bext([0, 0, 0] if matrix[k-1-r][c] else [cr, cg, cb])\n        # then blit the buffer\n        buff = bytes(buff)\n        # update texture\n        self._upd_texture(buff)\n\n    def _upd_texture(self, buff):\n        texture = self._qrtexture\n        texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')\n        img = self.ids.qrimage\n        img.anim_delay = -1\n        img.texture = texture\n        img.canvas.ask_update()\n\nif __name__ == '__main__':\n    from kivy.app import runTouchApp\n    import sys\n    data = str(sys.argv[1:])\n    runTouchApp(QRCodeWidget(data=data))\n\n\n"
  },
  {
    "path": "gui/kivy/uix/screens.py",
    "content": "from weakref import ref\nfrom decimal import Decimal\nimport re\nimport datetime\nimport traceback, sys\n\nfrom kivy.app import App\nfrom kivy.cache import Cache\nfrom kivy.clock import Clock\nfrom kivy.compat import string_types\nfrom kivy.properties import (ObjectProperty, DictProperty, NumericProperty,\n                             ListProperty, StringProperty)\n\nfrom kivy.uix.label import Label\n\nfrom kivy.lang import Builder\nfrom kivy.factory import Factory\nfrom kivy.utils import platform\n\nfrom electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds\nfrom electrum import bitcoin\nfrom electrum.util import timestamp_to_datetime\nfrom electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED\n\nfrom .context_menu import ContextMenu\n\n\nfrom electrum_gui.kivy.i18n import _\n\nclass EmptyLabel(Factory.Label):\n    pass\n\nclass CScreen(Factory.Screen):\n    __events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')\n    action_view = ObjectProperty(None)\n    loaded = False\n    kvname = None\n    context_menu = None\n    menu_actions = []\n    app = App.get_running_app()\n\n    def _change_action_view(self):\n        app = App.get_running_app()\n        action_bar = app.root.manager.current_screen.ids.action_bar\n        _action_view = self.action_view\n\n        if (not _action_view) or _action_view.parent:\n            return\n        action_bar.clear_widgets()\n        action_bar.add_widget(_action_view)\n\n    def on_enter(self):\n        # FIXME: use a proper event don't use animation time of screen\n        Clock.schedule_once(lambda dt: self.dispatch('on_activate'), .25)\n        pass\n\n    def update(self):\n        pass\n\n    @profiler\n    def load_screen(self):\n        self.screen = Builder.load_file('gui/kivy/uix/ui_screens/' + self.kvname + '.kv')\n        self.add_widget(self.screen)\n        self.loaded = True\n        self.update()\n        setattr(self.app, self.kvname + '_screen', self)\n\n    def on_activate(self):\n        if self.kvname and not self.loaded:\n            self.load_screen()\n        #Clock.schedule_once(lambda dt: self._change_action_view())\n\n    def on_leave(self):\n        self.dispatch('on_deactivate')\n\n    def on_deactivate(self):\n        self.hide_menu()\n\n    def hide_menu(self):\n        if self.context_menu is not None:\n            self.remove_widget(self.context_menu)\n            self.context_menu = None\n\n    def show_menu(self, obj):\n        self.hide_menu()\n        self.context_menu = ContextMenu(obj, self.menu_actions)\n        self.add_widget(self.context_menu)\n\n\nTX_ICONS = [\n    \"close\",\n    \"close\",\n    \"close\",\n    \"unconfirmed\",\n    \"close\",\n    \"clock1\",\n    \"clock2\",\n    \"clock3\",\n    \"clock4\",\n    \"clock5\",\n    \"confirmed\",\n]\n\nclass HistoryScreen(CScreen):\n\n    tab = ObjectProperty(None)\n    kvname = 'history'\n    cards = {}\n\n    def __init__(self, **kwargs):\n        self.ra_dialog = None\n        super(HistoryScreen, self).__init__(**kwargs)\n        self.menu_actions = [ ('Label', self.label_dialog), ('Details', self.show_tx)]\n\n    def show_tx(self, obj):\n        tx_hash = obj.tx_hash\n        tx = self.app.wallet.transactions.get(tx_hash)\n        if not tx:\n            return\n        self.app.tx_dialog(tx)\n\n    def label_dialog(self, obj):\n        from .dialogs.label_dialog import LabelDialog\n        key = obj.tx_hash\n        text = self.app.wallet.get_label(key)\n        def callback(text):\n            self.app.wallet.set_label(key, text)\n            self.update()\n        d = LabelDialog(_('Enter Transaction Label'), text, callback)\n        d.open()\n\n    def get_card(self, tx_hash, height, conf, timestamp, value, balance):\n        status, status_str = self.app.wallet.get_tx_status(tx_hash, height, conf, timestamp)\n        icon = \"atlas://gui/kivy/theming/light/\" + TX_ICONS[status]\n        label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')\n        date = timestamp_to_datetime(timestamp)\n        ri = self.cards.get(tx_hash)\n        if ri is None:\n            ri = Factory.HistoryItem()\n            ri.screen = self\n            ri.tx_hash = tx_hash\n            self.cards[tx_hash] = ri\n        ri.icon = icon\n        ri.date = status_str\n        ri.message = label\n        ri.value = value or 0\n        ri.amount = self.app.format_amount(value, True) if value is not None else '--'\n        ri.confirmations = conf\n        if self.app.fiat_unit and date:\n            rate = self.app.fx.history_rate(date)\n            if rate:\n                s = self.app.fx.value_str(value, rate)\n                ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit\n        return ri\n\n    def update(self, see_all=False):\n        if self.app.wallet is None:\n            return\n        history = reversed(self.app.wallet.get_history())\n        history_card = self.screen.ids.history_container\n        history_card.clear_widgets()\n        count = 0\n        for item in history:\n            ri = self.get_card(*item)\n            count += 1\n            history_card.add_widget(ri)\n\n        if count == 0:\n            msg = _('This screen shows your list of transactions. It is currently empty.')\n            history_card.add_widget(EmptyLabel(text=msg))\n\n\nclass SendScreen(CScreen):\n\n    kvname = 'send'\n    payment_request = None\n\n    def set_URI(self, text):\n        import electrum\n        try:\n            uri = electrum.util.parse_URI(text, self.app.on_pr)\n        except:\n            self.app.show_info(_(\"Not a BTCP URI\"))\n            return\n        amount = uri.get('amount')\n        self.screen.address = uri.get('address', '')\n        self.screen.message = uri.get('message', '')\n        self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''\n        self.payment_request = None\n        self.screen.is_pr = False\n\n    def update(self):\n        pass\n\n    def do_clear(self):\n        self.screen.amount = ''\n        self.screen.message = ''\n        self.screen.address = ''\n        self.payment_request = None\n        self.screen.is_pr = False\n\n    def set_request(self, pr):\n        self.screen.address = pr.get_requestor()\n        amount = pr.get_amount()\n        self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''\n        self.screen.message = pr.get_memo()\n        if pr.is_pr():\n            self.screen.is_pr = True\n            self.payment_request = pr\n        else:\n            self.screen.is_pr = False\n            self.payment_request = None\n\n    def do_save(self):\n        if not self.screen.address:\n            return\n        if self.screen.is_pr:\n            # it sould be already saved\n            return\n        # save address as invoice\n        from electrum.paymentrequest import make_unsigned_request, PaymentRequest\n        req = {'address':self.screen.address, 'memo':self.screen.message}\n        amount = self.app.get_amount(self.screen.amount) if self.screen.amount else 0\n        req['amount'] = amount\n        pr = make_unsigned_request(req).SerializeToString()\n        pr = PaymentRequest(pr)\n        self.app.wallet.invoices.add(pr)\n        self.app.update_tab('invoices')\n        self.app.show_info(_(\"Invoice saved\"))\n        if pr.is_pr():\n            self.screen.is_pr = True\n            self.payment_request = pr\n        else:\n            self.screen.is_pr = False\n            self.payment_request = None\n\n    def do_paste(self):\n        contents = self.app._clipboard.paste()\n        if not contents:\n            self.app.show_info(_(\"Clipboard is empty\"))\n            return\n        self.set_URI(contents)\n\n    def do_send(self):\n        if self.screen.is_pr:\n            if self.payment_request.has_expired():\n                self.app.show_error(_('Payment request has expired.'))\n                return\n            outputs = self.payment_request.get_outputs()\n        else:\n            address = str(self.screen.address)\n            if not address:\n                self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a BTCP address or a payment request'))\n                return\n            if not bitcoin.is_address(address):\n                self.app.show_error(_('Invalid BTCP Address') + ':\\n' + address)\n                return\n            try:\n                amount = self.app.get_amount(self.screen.amount)\n            except:\n                self.app.show_error(_('Invalid Amount') + ':\\n' + self.screen.amount)\n                return\n            outputs = [(bitcoin.TYPE_ADDRESS, address, amount)]\n        message = self.screen.message\n        amount = sum(map(lambda x:x[2], outputs))\n        if self.app.electrum_config.get('use_rbf'):\n            from .dialogs.question import Question\n            d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))\n            d.open()\n        else:\n            self._do_send(amount, message, outputs, False)\n\n    def _do_send(self, amount, message, outputs, rbf):\n        # make unsigned transaction\n        config = self.app.electrum_config\n        coins = self.app.wallet.get_spendable_coins(None, config)\n        try:\n            tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)\n        except NotEnoughFunds:\n            self.app.show_error(_(\"Not enough funds\"))\n            return\n        except Exception as e:\n            traceback.print_exc(file=sys.stdout)\n            self.app.show_error(str(e))\n            return\n        if rbf:\n            tx.set_rbf(True)\n        fee = tx.get_fee()\n        msg = [\n            _(\"Amount to be sent\") + \": \" + self.app.format_amount_and_units(amount),\n            _(\"Mining fee\") + \": \" + self.app.format_amount_and_units(fee),\n        ]\n        if fee >= config.get('confirm_fee', 100000):\n            msg.append(_('Warning')+ ': ' + _(\"The fee for this transaction seems unusually high.\"))\n        msg.append(_(\"Enter your PIN code to proceed\"))\n        self.app.protected('\\n'.join(msg), self.send_tx, (tx, message))\n\n    def send_tx(self, tx, message, password):\n        if self.app.wallet.has_password() and password is None:\n            return\n        def on_success(tx):\n            if tx.is_complete():\n                self.app.broadcast(tx, self.payment_request)\n                self.app.wallet.set_label(tx.txid(), message)\n            else:\n                self.app.tx_dialog(tx)\n        def on_failure(error):\n            self.app.show_error(error)\n        if self.app.wallet.can_sign(tx):\n            self.app.show_info(\"Signing...\")\n            self.app.sign_tx(tx, password, on_success, on_failure)\n        else:\n            self.app.tx_dialog(tx)\n\n\nclass ReceiveScreen(CScreen):\n\n    kvname = 'receive'\n\n    def update(self):\n        if not self.screen.address:\n            self.get_new_address()\n        else:\n            status = self.app.wallet.get_request_status(self.screen.address)\n            self.screen.status = _('Payment Received') if status == PR_PAID else ''\n\n    def clear(self):\n        self.screen.address = ''\n        self.screen.amount = ''\n        self.screen.message = ''\n\n    def get_new_address(self):\n        if not self.app.wallet:\n            return False\n        self.clear()\n        addr = self.app.wallet.get_unused_address()\n        if addr is None:\n            addr = self.app.wallet.get_receiving_address() or ''\n            b = False\n        else:\n            b = True\n        self.screen.address = addr\n        return b\n\n    def on_address(self, addr):\n        req = self.app.wallet.get_payment_request(addr, self.app.electrum_config)\n        self.screen.status = ''\n        if req:\n            self.screen.message = req.get('memo', '')\n            amount = req.get('amount')\n            self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''\n            status = req.get('status', PR_UNKNOWN)\n            self.screen.status = _('Payment Received') if status == PR_PAID else ''\n        Clock.schedule_once(lambda dt: self.update_qr())\n\n    def get_URI(self):\n        from electrum.util import create_URI\n        amount = self.screen.amount\n        if amount:\n            a, u = self.screen.amount.split()\n            assert u == self.app.base_unit\n            amount = Decimal(a) * pow(10, self.app.decimal_point())\n        return create_URI(self.screen.address, amount, self.screen.message)\n\n    @profiler\n    def update_qr(self):\n        uri = self.get_URI()\n        qr = self.screen.ids.qr\n        qr.set_data(uri)\n\n    def do_share(self):\n        uri = self.get_URI()\n        self.app.do_share(uri, _(\"Share BTCP Request\"))\n\n    def do_copy(self):\n        uri = self.get_URI()\n        self.app._clipboard.copy(uri)\n        self.app.show_info(_('Request copied to clipboard'))\n\n    def save_request(self):\n        addr = self.screen.address\n        amount = self.screen.amount\n        message = self.screen.message\n        amount = self.app.get_amount(amount) if amount else 0\n        req = self.app.wallet.make_payment_request(addr, amount, message, None)\n        self.app.wallet.add_payment_request(req, self.app.electrum_config)\n        self.app.update_tab('requests')\n\n    def on_amount_or_message(self):\n        self.save_request()\n        Clock.schedule_once(lambda dt: self.update_qr())\n\n    def do_new(self):\n        addr = self.get_new_address()\n        if not addr:\n            self.app.show_info(_('Please use the existing requests first.'))\n        else:\n            self.save_request()\n            self.app.show_info(_('New request added to your list.'))\n\n\ninvoice_text = {\n    PR_UNPAID:_('Pending'),\n    PR_UNKNOWN:_('Unknown'),\n    PR_PAID:_('Paid'),\n    PR_EXPIRED:_('Expired')\n}\nrequest_text = {\n    PR_UNPAID: _('Pending'),\n    PR_UNKNOWN: _('Unknown'),\n    PR_PAID: _('Received'),\n    PR_EXPIRED: _('Expired')\n}\npr_icon = {\n    PR_UNPAID: 'atlas://gui/kivy/theming/light/important',\n    PR_UNKNOWN: 'atlas://gui/kivy/theming/light/important',\n    PR_PAID: 'atlas://gui/kivy/theming/light/confirmed',\n    PR_EXPIRED: 'atlas://gui/kivy/theming/light/close'\n}\n\n\nclass InvoicesScreen(CScreen):\n    kvname = 'invoices'\n    cards = {}\n\n    def get_card(self, pr):\n        key = pr.get_id()\n        ci = self.cards.get(key)\n        if ci is None:\n            ci = Factory.InvoiceItem()\n            ci.key = key\n            ci.screen = self\n            self.cards[key] = ci\n\n        ci.requestor = pr.get_requestor()\n        ci.memo = pr.get_memo()\n        amount = pr.get_amount()\n        if amount:\n            ci.amount = self.app.format_amount_and_units(amount)\n            status = self.app.wallet.invoices.get_status(ci.key)\n            ci.status = invoice_text[status]\n            ci.icon = pr_icon[status]\n        else:\n            ci.amount = _('No Amount')\n            ci.status = ''\n        exp = pr.get_expiration_date()\n        ci.date = format_time(exp) if exp else _('Never')\n        return ci\n\n    def update(self):\n        self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]\n        invoices_list = self.screen.ids.invoices_container\n        invoices_list.clear_widgets()\n        _list = self.app.wallet.invoices.sorted_list()\n        for pr in _list:\n            ci = self.get_card(pr)\n            invoices_list.add_widget(ci)\n        if not _list:\n            msg = _('This screen shows the list of payment requests that have been sent to you. You may also use it to store contact addresses.')\n            invoices_list.add_widget(EmptyLabel(text=msg))\n\n    def do_pay(self, obj):\n        pr = self.app.wallet.invoices.get(obj.key)\n        self.app.on_pr(pr)\n\n    def do_view(self, obj):\n        pr = self.app.wallet.invoices.get(obj.key)\n        pr.verify(self.app.wallet.contacts)\n        self.app.show_pr_details(pr.get_dict(), obj.status, True)\n\n    def do_delete(self, obj):\n        from .dialogs.question import Question\n        def cb(result):\n            if result:\n                self.app.wallet.invoices.remove(obj.key)\n                self.app.update_tab('invoices')\n        d = Question(_('Delete invoice?'), cb)\n        d.open()\n\n\naddress_icon = {\n    'Pending' : 'atlas://gui/kivy/theming/light/important',\n    'Paid' : 'atlas://gui/kivy/theming/light/confirmed'\n}\n \nclass AddressScreen(CScreen):\n    kvname = 'address'\n    cards = {}\n\n    def get_card(self, addr, balance, is_used, label):\n        ci = self.cards.get(addr)\n        if ci is None:\n            ci = Factory.AddressItem()\n            ci.screen = self\n            ci.address = addr\n            self.cards[addr] = ci\n\n        ci.memo = label\n        ci.amount = self.app.format_amount_and_units(balance)\n        request = self.app.wallet.get_payment_request(addr, self.app.electrum_config)\n        if is_used:\n            ci.status = _('Used')\n        elif request:\n            status, conf = self.app.wallet.get_request_status(addr)\n            requested_amount = request.get('amount')\n            # make sure that requested amount is > 0\n            if status == PR_PAID:\n                s = _('Request paid')\n            elif status == PR_UNPAID:\n                s = _('Request pending')\n            elif status == PR_EXPIRED:\n                s = _('Request expired')\n            else:\n                s = ''\n            ci.status = s + ': ' + self.app.format_amount_and_units(requested_amount)\n        else:\n            ci.status = _('Funded') if balance>0 else _('Unused')\n        return ci\n\n\n    def update(self):\n        self.menu_actions = [('Receive', self.do_show), ('Details', self.do_view)]\n        wallet = self.app.wallet\n        _list = wallet.get_change_addresses() if self.screen.show_change else wallet.get_receiving_addresses()\n        search = self.screen.message\n        container = self.screen.ids.search_container\n        container.clear_widgets()\n        n = 0\n        for address in _list:\n            label = wallet.labels.get(address, '')\n            balance = sum(wallet.get_addr_balance(address))\n            is_used = wallet.is_used(address)\n            if self.screen.show_used == 1 and (balance or is_used):\n                continue\n            if self.screen.show_used == 2 and balance == 0:\n                continue\n            if self.screen.show_used == 3 and not is_used:\n                continue\n            card = self.get_card(address, balance, is_used, label)\n            if search and not self.ext_search(card, search):\n                continue\n            container.add_widget(card)\n            n += 1\n        if not n:\n            msg = _('No address matching your search')\n            container.add_widget(EmptyLabel(text=msg))\n\n    def do_show(self, obj):\n        self.app.show_request(obj.address)\n\n    def do_view(self, obj):\n        req = self.app.wallet.get_payment_request(obj.address, self.app.electrum_config)\n        if req:\n            c, u, x = self.app.wallet.get_addr_balance(obj.address)\n            balance = c + u + x\n            if balance > 0:\n                req['fund'] = balance\n            status = req.get('status')\n            amount = req.get('amount')\n            address = req['address']\n            if amount:\n                status = req.get('status')\n                status = request_text[status]\n            else:\n                received_amount = self.app.wallet.get_addr_received(address)\n                status = self.app.format_amount_and_units(received_amount)\n            self.app.show_pr_details(req, status, False)\n\n        else:\n            req = { 'address': obj.address, 'status' : obj.status }\n            status = obj.status\n            c, u, x = self.app.wallet.get_addr_balance(obj.address)\n            balance = c + u + x\n            if balance > 0:\n                req['fund'] = balance\n            self.app.show_addr_details(req, status)\n\n    def do_delete(self, obj):\n        from .dialogs.question import Question\n        def cb(result):\n            if result:\n                self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config)\n                self.update()\n        d = Question(_('Delete request?'), cb)\n        d.open()\n\n    def ext_search(self, card, search):\n        return card.memo.find(search) >= 0 or card.amount.find(search) >= 0\n\n\n\n\nclass TabbedCarousel(Factory.TabbedPanel):\n    '''Custom TabbedPanel using a carousel used in the Main Screen\n    '''\n\n    carousel = ObjectProperty(None)\n\n    def animate_tab_to_center(self, value):\n        scrlv = self._tab_strip.parent\n        if not scrlv:\n            return\n        idx = self.tab_list.index(value)\n        n = len(self.tab_list)\n        if idx in [0, 1]:\n            scroll_x = 1\n        elif idx in [n-1, n-2]:\n            scroll_x = 0\n        else:\n            scroll_x = 1. * (n - idx - 1) / (n - 1)\n        mation = Factory.Animation(scroll_x=scroll_x, d=.25)\n        mation.cancel_all(scrlv)\n        mation.start(scrlv)\n\n    def on_current_tab(self, instance, value):\n        self.animate_tab_to_center(value)\n\n    def on_index(self, instance, value):\n        current_slide = instance.current_slide\n        if not hasattr(current_slide, 'tab'):\n            return\n        tab = current_slide.tab\n        ct = self.current_tab\n        try:\n            if ct.text != tab.text:\n                carousel = self.carousel\n                carousel.slides[ct.slide].dispatch('on_leave')\n                self.switch_to(tab)\n                carousel.slides[tab.slide].dispatch('on_enter')\n        except AttributeError:\n            current_slide.dispatch('on_enter')\n\n    def switch_to(self, header):\n        # we have to replace the functionality of the original switch_to\n        if not header:\n            return\n        if not hasattr(header, 'slide'):\n            header.content = self.carousel\n            super(TabbedCarousel, self).switch_to(header)\n            try:\n                tab = self.tab_list[-1]\n            except IndexError:\n                return\n            self._current_tab = tab\n            tab.state = 'down'\n            return\n\n        carousel = self.carousel\n        self.current_tab.state = \"normal\"\n        header.state = 'down'\n        self._current_tab = header\n        # set the carousel to load  the appropriate slide\n        # saved in the screen attribute of the tab head\n        slide = carousel.slides[header.slide]\n        if carousel.current_slide != slide:\n            carousel.current_slide.dispatch('on_leave')\n            carousel.load_slide(slide)\n            slide.dispatch('on_enter')\n\n    def add_widget(self, widget, index=0):\n        if isinstance(widget, Factory.CScreen):\n            self.carousel.add_widget(widget)\n            return\n        super(TabbedCarousel, self).add_widget(widget, index=index)\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/about.kv",
    "content": "#:import VERSION electrum.version.ELECTRUM_VERSION\n\nPopup:\n    title: _(\"About Electrum\")\n    BoxLayout:\n        orientation: 'vertical'\n        spacing: '10dp'\n        padding: '10dp'\n        GridLayout:\n            cols: 2\n            spacing: '10dp'\n            TopLabel:\n                text: _('Version')\n                size_hint_x: 0.4\n            TopLabel:\n                text: VERSION\n                size_hint_x: 0.6\n            TopLabel:\n                text: _('Licence')\n                size_hint_x: 0.4\n            TopLabel:\n                text: \"MIT Licence\"\n                size_hint_x: 0.6\n            TopLabel:\n                text: _('Homepage')\n                size_hint_x: 0.4\n            TopLabel:\n                markup: True\n                text: '[color=6666ff][ref=x]https://electrum.org[/ref][/color]'\n                on_ref_press:\n                    import webbrowser\n                    webbrowser.open(\"https://electrum.org\")\n                size_hint_x: 0.6\n            TopLabel:\n                text: _('Developers')\n                size_hint_x: 0.4\n            TopLabel:\n                text: '\\n'.join(['Thomas Voegtlin', 'Neil Booth', 'Akshay Arora'])\n                size_hint_x: 0.6\n        TopLabel:\n            text: _('Distributed by Electrum Technologies GmbH')\n            padding: '0dp', '20dp'\n        Widget:\n            size_hint: None, 0.5\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Widget:\n                size_hint: 0.5, None\n                height: '48dp'\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('Close')\n                on_release: root.dismiss()\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/address.kv",
    "content": "#:import _ electrum_gui.kivy.i18n._\n#:import Decimal decimal.Decimal\n#:set btc_symbol chr(171)\n#:set mbtc_symbol chr(187)\n#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf'\n\n<AddressLabel@Label>\n    text_size: self.width, None\n    halign: 'left'\n    valign: 'top'\n\n<AddressItem@CardItem>\n    address: ''\n    memo: ''\n    amount: ''\n    status: ''\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        AddressLabel:\n            text: root.address\n            shorten: True\n        Widget\n        AddressLabel:\n            text: root.memo\n            color: .699, .699, .699, 1\n            font_size: '13sp'\n            shorten: True\n        Widget\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        AddressLabel:\n            text: root.amount\n            halign: 'right'\n            font_size: '15sp'\n        Widget\n        AddressLabel:\n            text: root.status\n            halign: 'right'\n            font_size: '13sp'\n            color: .699, .699, .699, 1 \n\nAddressScreen:\n    id: addr_screen\n    name: 'address'\n    message: ''\n    pr_status: 'Pending'\n    show_change: False\n    show_used: 0\n    on_message:\n        self.parent.update()\n    BoxLayout\n        padding: '12dp', '70dp', '12dp', '12dp'\n        spacing: '12dp'\n        orientation: 'vertical'\n        size_hint: 1, 1.1\n        BoxLayout:\n            spacing: '6dp'\n            size_hint: 1, None\n            orientation: 'horizontal'\n            AddressFilter:\n                opacity: 1\n                size_hint: 1, None\n                height: self.minimum_height\n                spacing: '5dp'\n                AddressButton:\n                    id: search\n                    text: _('Change') if root.show_change else _('Receiving')\n                    on_release:\n                        root.show_change = not root.show_change\n                        Clock.schedule_once(lambda dt: app.address_screen.update())\n            AddressFilter:\n                opacity: 1\n                size_hint: 1, None\n                height: self.minimum_height\n                spacing: '5dp'\n                AddressButton:\n                    id: search\n                    text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used')}[root.show_used]\n                    on_release:\n                        root.show_used = (root.show_used + 1) % 4\n                        Clock.schedule_once(lambda dt: app.address_screen.update())\n            AddressFilter:\n                opacity: 1\n                size_hint: 1, None\n                height: self.minimum_height\n                spacing: '5dp'\n                canvas.before:\n                    Color:\n                        rgba: 0.9, 0.9, 0.9, 1\n                AddressButton:\n                    id: change\n                    text: root.message if root.message else _('Search')\n                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(addr_screen))\n        ScrollView:\n            GridLayout:\n                cols: 1\n                id: search_container\n                size_hint_y: None\n                height: self.minimum_height\n                spacing: '2dp'\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/history.kv",
    "content": "#:import _ electrum_gui.kivy.i18n._\n#:import Factory kivy.factory.Factory\n#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf'\n#:set btc_symbol chr(171)\n#:set mbtc_symbol chr(187)\n\n\n\n<CardLabel@Label>\n    color: 0.95, 0.95, 0.95, 1\n    size_hint: 1, None\n    text: ''\n    text_size: self.width, None\n    height: self.texture_size[1]\n    halign: 'left'\n    valign: 'top'\n\n\n<HistoryItem@CardItem>\n    icon: 'atlas://gui/kivy/theming/light/important'\n    message: ''\n    value: 0\n    amount: '--'\n    amount_color: '#FF6657' if self.value < 0 else '#2EA442'\n    confirmations: 0\n    date: ''\n    quote_text: ''\n    spacing: '9dp'\n    Image:\n        id: icon\n        source: root.icon\n        size_hint: None, 1\n        width: self.height *.54\n        mipmap: True\n    BoxLayout:\n        orientation: 'vertical'\n        Widget\n        CardLabel:\n            text: root.date\n            font_size: '14sp'\n        CardLabel:\n            color: .699, .699, .699, 1\n            font_size: '13sp'\n            shorten: True\n            text: root.message\n        Widget\n    CardLabel:\n        halign: 'right'\n        font_size: '15sp'\n        size_hint: None, 1\n        width: '110sp'\n        markup: True\n        font_name: font_light\n        text:\n            u'[color={amount_color}]{sign}{amount} {unit}[/color]\\n'\\\n            u'[color=#B2B3B3][size=13sp]{qt}[/size]'\\\n            u'[/color]'.format(amount_color=root.amount_color,\\\n            amount=root.amount[1:], qt=root.quote_text, sign=root.amount[0],\\\n            unit=app.base_unit)\n\n\nHistoryScreen:\n    name: 'history'\n    content: content\n    ScrollView:\n        id: content\n        do_scroll_x: False\n        GridLayout\n            id: history_container\n            cols: 1\n            size_hint: 1, None\n            height: self.minimum_height\n            padding: '12dp'\n            spacing: '2dp'\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/invoice.kv",
    "content": "#:import Decimal decimal.Decimal\n\n\n\nPopup:\n    id: popup\n    is_invoice: True\n    amount: 0\n    requestor: ''\n    exp: ''\n    description: ''\n    status: ''\n    signature: ''\n    isaddr: ''\n    fund: 0\n    pk: ''\n    title: _('Invoice') if popup.is_invoice else _('Request')\n    tx_hash: ''\n    BoxLayout:\n        orientation: 'vertical'\n        ScrollView:\n            GridLayout:\n                cols: 1\n                height: self.minimum_height\n                size_hint_y: None\n                padding: '10dp'\n                spacing: '10dp'\n                GridLayout:\n                    cols: 1\n                    size_hint_y: None\n                    height: self.minimum_height\n                    spacing: '10dp'\n                    BoxLabel:\n                        text: (_('Status') if popup.amount or popup.is_invoice or popup.isaddr == 'y' else _('Amount received')) if root.status else ''\n                        value: root.status\n                    BoxLabel:\n                        text: _('Request Amount') if root.amount else ''\n                        value: app.format_amount_and_units(root.amount) if root.amount else ''\n                    BoxLabel:\n                        text: _('Requested By') if popup.is_invoice else _('Address')\n                        value: root.requestor\n                    BoxLabel:\n                        text: _('Signature') if root.signature else ''\n                        value: root.signature\n                    BoxLabel:\n                        text: _('Expiration') if root.exp else ''\n                        value: root.exp\n                    BoxLabel:\n                        text: _('Description') if root.description else ''\n                        value: root.description\n                    BoxLabel:\n                        text: _('Balance') if popup.fund else ''\n                        value: app.format_amount_and_units(root.fund) if root.fund else ''\n                    TopLabel:\n                        text: _('Private Key') \n                    RefLabel:\n                        id: pk_label\n                        touched: True if not self.touched else True\n                        data: root.pk\n\n                TopLabel:\n                    text: _('Outputs') if popup.is_invoice else ''\n                OutputList:\n                    id: output_list\n                TopLabel:\n                    text: _('Transaction ID') if popup.tx_hash else ''\n                TxHashLabel:\n                    data: popup.tx_hash\n                    name: _('Transaction ID')\n        Widget:\n            size_hint: 1, 0.1\n\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Widget:\n                size_hint: 0.5, None\n                height: '48dp'\n            Button:\n                size_hint: 2, None\n                height: '48dp'\n                text: _('Close')\n                on_release: popup.dismiss()\n            Button:\n                size_hint: 2, None\n                height: '48dp'\n                text: _('Hide private key') if pk_label.data else _('Export private key')\n                on_release:\n                    setattr(pk_label, 'data', '') if pk_label.data else popup.export(pk_label, popup.requestor)\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/invoices.kv",
    "content": "<InvoicesLabel@Label>\n    #color: .305, .309, .309, 1\n    text_size: self.width, None\n    halign: 'left'\n    valign: 'top'\n\n<InvoiceItem@CardItem>\n    requestor: ''\n    memo: ''\n    amount: ''\n    status: ''\n    date: ''\n    icon: 'atlas://gui/kivy/theming/light/important'\n    Image:\n        id: icon\n        source: root.icon\n        size_hint: None, 1\n        width: self.height *.54\n        mipmap: True\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        InvoicesLabel:\n            text: root.requestor\n            shorten: True\n        Widget\n        InvoicesLabel:\n            text: root.memo\n            color: .699, .699, .699, 1\n            font_size: '13sp'\n            shorten: True\n        Widget\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        InvoicesLabel:\n            text: root.amount\n            font_size: '15sp'\n            halign: 'right'\n            width: '110sp'\n        Widget\n        InvoicesLabel:\n            text: root.status\n            font_size: '13sp'\n            halign: 'right'\n            color: .699, .699, .699, 1\n        Widget\n\n\nInvoicesScreen:\n    name: 'invoices'\n    BoxLayout:\n        orientation: 'vertical'\n        spacing: '1dp'\n        ScrollView:\n            GridLayout:\n                cols: 1\n                id: invoices_container\n                size_hint: 1, None\n                height: self.minimum_height\n                spacing: '2dp'\n                padding: '12dp'\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/network.kv",
    "content": "Popup:\n    id: nd\n    title: _('Network')\n    BoxLayout:\n        orientation: 'vertical'\n        ScrollView:\n            GridLayout:\n                id: scrollviewlayout\n                cols:1\n                size_hint: 1, None\n                height: self.minimum_height\n                padding: '10dp'\n                SettingsItem:\n                    value: _(\"%d connections.\")% app.num_nodes if app.num_nodes else _(\"Not connected\")\n                    title: _(\"Status\") + ': ' + self.value\n                    description: _(\"Connections with Electrum servers\")\n                    action: lambda x: None\n\n                CardSeparator\n                SettingsItem:\n                    title: _(\"Server\") + ': ' + app.server_host\n                    description: _(\"Server used to query your history.\")\n                    action: lambda x: app.popup_dialog('server')\n\n                CardSeparator\n                SettingsItem:\n                    proxy: app.proxy_config.get('mode')\n                    host: app.proxy_config.get('host')\n                    port: app.proxy_config.get('port')\n                    title: _(\"Proxy\") + ': ' + ((self.host +':' + self.port) if self.proxy else _('None'))\n                    description: _('Proxy configuration')\n                    action: lambda x: app.popup_dialog('proxy')\n\n                CardSeparator\n                SettingsItem:\n                    title: _(\"Auto-connect\") + ': ' + ('ON' if app.auto_connect else 'OFF')\n                    description: _(\"Select your server automatically\")\n                    action: app.toggle_auto_connect\n\n                CardSeparator\n                SettingsItem:\n                    value: \"%d blocks\" % app.num_blocks\n                    title: _(\"Blockchain\") + ': ' + self.value\n                    description: _('Verified block headers')\n                    action: lambda x: x\n\n                CardSeparator\n                SettingsItem:\n                    title: _('Fork detected at block %d')%app.blockchain_checkpoint if app.num_chains>1 else _('No fork detected')\n                    fork_description: (_('You are following branch') if app.auto_connect else _(\"Your server is on branch\")) + ' ' + app.blockchain_name\n                    description: self.fork_description if app.num_chains>1 else _('Connected nodes are on the same chain')\n                    action: app.choose_blockchain_dialog\n                    disabled: app.num_chains == 1\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/proxy.kv",
    "content": "Popup:\n    id: nd\n    title: _('Proxy')\n    BoxLayout:\n        orientation: 'vertical'\n        padding: '10dp'\n        spacing: '10dp'\n        GridLayout:\n            cols: 2\n            Label:\n                text: _('Proxy mode')\n            Spinner:\n                id: mode\n                height: '48dp'\n                size_hint_y: None\n                text: app.proxy_config.get('mode', 'none')\n                values: ['none', 'socks4', 'socks5', 'http']\n            Label:\n                text: _('Host')\n            TextInput:\n                id: host\n                multiline: False\n                height: '48dp'\n                size_hint_y: None\n                text: app.proxy_config.get('host', '')\n                disabled: mode.text == 'none'\n            Label:\n                text: _('Port')\n            TextInput:\n                id: port\n                multiline: False\n                input_type: 'number'\n                height: '48dp'\n                size_hint_y: None\n                text: app.proxy_config.get('port', '')\n                disabled: mode.text == 'none'\n            Label:\n                text: _('Username')\n            TextInput:\n                id: user\n                multiline: False\n                height: '48dp'\n                size_hint_y: None\n                text: app.proxy_config.get('user', '')\n                disabled: mode.text == 'none'\n            Label:\n                text: _('Password')\n            TextInput:\n                id: password\n                multiline: False\n                password: True\n                height: '48dp'\n                size_hint_y: None\n                text: app.proxy_config.get('password', '')\n                disabled: mode.text == 'none'\n        Widget:\n            size_hint: 1, 0.1\n        BoxLayout:\n            Widget:\n                size_hint: 0.5, None\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('OK')\n                on_release:\n                    host, port, protocol, proxy, auto_connect = app.network.get_parameters()\n                    proxy = {}\n                    proxy['mode']=str(root.ids.mode.text).lower()\n                    proxy['host']=str(root.ids.host.text)\n                    proxy['port']=str(root.ids.port.text)\n                    proxy['user']=str(root.ids.user.text)\n                    proxy['password']=str(root.ids.password.text)\n                    if proxy['mode']=='none': proxy = None\n                    app.network.set_parameters(host, port, protocol, proxy, auto_connect)\n                    app.proxy_config = proxy if proxy else {}\n                    nd.dismiss()\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/receive.kv",
    "content": "#:import _ electrum_gui.kivy.i18n._\n#:import Decimal decimal.Decimal\n#:set btc_symbol chr(171)\n#:set mbtc_symbol chr(187)\n#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf'\n\n\n\nReceiveScreen:\n    id: s\n    name: 'receive'\n\n    address: ''\n    amount: ''\n    message: ''\n    status: ''\n\n    on_address:\n        self.parent.on_address(self.address)\n    on_amount:\n        self.parent.on_amount_or_message()\n    on_message:\n        self.parent.on_amount_or_message()\n\n    BoxLayout\n        padding: '12dp', '12dp', '12dp', '12dp'\n        spacing: '12dp'\n        orientation: 'vertical'\n        size_hint: 1, 1\n        FloatLayout:\n            id: bl\n            QRCodeWidget:\n                id: qr\n                size_hint: None, 1\n                width: min(self.height, bl.width)\n                pos_hint: {'center': (.5, .5)}\n                shaded: False\n                foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)\n                on_touch_down:\n                    touch = args[1]\n                    if self.collide_point(*touch.pos): self.shaded = not self.shaded\n            Label:\n                text: root.status\n                opacity: 1 if root.status else 0\n                pos_hint: {'center': (.5, .5)}\n                size_hint: None, 1\n                width: min(self.height, bl.width)\n                bcolor: 0.3, 0.3, 0.3, 0.9\n                canvas.before:\n                    Color:\n                        rgba: self.bcolor\n                    Rectangle:\n                        pos: self.pos\n                        size: self.size\n\n        SendReceiveBlueBottom:\n            id: blue_bottom\n            size_hint: 1, None\n            height: self.minimum_height\n            BoxLayout:\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/globe'\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: address_label\n                    text: s.address if s.address else _('BTCP Address')\n                    shorten: True\n                    disabled: True\n            CardSeparator:\n                opacity: message_selection.opacity\n                color: blue_bottom.foreground_color\n            BoxLayout:\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/calculator'\n                    opacity: 0.7\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: amount_label\n                    default_text: _('Amount')\n                    text: s.amount if s.amount else _('Amount')\n                    on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, False))\n            CardSeparator:\n                opacity: message_selection.opacity\n                color: blue_bottom.foreground_color\n            BoxLayout:\n                id: message_selection\n                opacity: 1\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/pen'\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: description\n                    text: s.message if s.message else _('Description')\n                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Button:\n                text: _('Copy')\n                size_hint: 1, None\n                height: '48dp'\n                on_release: s.parent.do_copy()\n            Button:\n                text: _('Share')\n                size_hint: 1, None\n                height: '48dp'\n                on_release: s.parent.do_share()\n            Button:\n                text: _('New')\n                size_hint: 1, None\n                height: '48dp'\n                on_release: Clock.schedule_once(lambda dt: s.parent.do_new())\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/requests.kv",
    "content": "<RequestLabel@Label>\n    #color: .305, .309, .309, 1\n    text_size: self.width, None\n    halign: 'left'\n    valign: 'top'\n\n<RequestItem@CardItem>\n    address: ''\n    memo: ''\n    amount: ''\n    status: ''\n    date: ''\n    icon: 'atlas://gui/kivy/theming/light/important'\n    Image:\n        id: icon\n        source: root.icon\n        size_hint: None, 1\n        width: self.height *.54\n        mipmap: True\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        RequestLabel:\n            text: root.address\n            shorten: True\n        Widget\n        RequestLabel:\n            text: root.memo\n            color: .699, .699, .699, 1\n            font_size: '13sp'\n            shorten: True\n        Widget\n    BoxLayout:\n        spacing: '8dp'\n        height: '32dp'\n        orientation: 'vertical'\n        Widget\n        RequestLabel:\n            text: root.amount\n            halign: 'right'\n            font_size: '15sp'\n        Widget\n        RequestLabel:\n            text: root.status\n            halign: 'right'\n            font_size: '13sp'\n            color: .699, .699, .699, 1\n        Widget\n\n\n\nRequestsScreen:\n    name: 'requests'\n    BoxLayout:\n        orientation: 'vertical'\n        spacing: '1dp'\n        ScrollView:\n            GridLayout:\n                cols: 1\n                id: requests_container\n                size_hint_y: None\n                height: self.minimum_height\n                spacing: '2dp'\n                padding: '12dp'\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/send.kv",
    "content": "#:import _ electrum_gui.kivy.i18n._\n#:import Decimal decimal.Decimal\n#:set btc_symbol chr(171)\n#:set mbtc_symbol chr(187)\n#:set font_light 'gui/kivy/data/fonts/Roboto-Condensed.ttf'\n\n\nSendScreen:\n    id: s\n    name: 'send'\n    address: ''\n    amount: ''\n    message: ''\n    is_pr: False\n    BoxLayout\n        padding: '12dp', '12dp', '12dp', '12dp'\n        spacing: '12dp'\n        orientation: 'vertical'\n        SendReceiveBlueBottom:\n            id: blue_bottom\n            size_hint: 1, None\n            height: self.minimum_height\n            BoxLayout:\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/globe'\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: payto_e\n                    text: s.address if s.address else _('Recipient')\n                    shorten: True\n                    on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the recipient address using the Paste button, or use the camera to scan a QR code.')))\n            CardSeparator:\n                opacity: int(not root.is_pr)\n                color: blue_bottom.foreground_color\n            BoxLayout:\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/calculator'\n                    opacity: 0.7\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: amount_e\n                    default_text: _('Amount')\n                    text: s.amount if s.amount else _('Amount')\n                    disabled: root.is_pr\n                    on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, True))\n            CardSeparator:\n                opacity: int(not root.is_pr)\n                color: blue_bottom.foreground_color\n            BoxLayout:\n                id: message_selection\n                size_hint: 1, None\n                height: blue_bottom.item_height\n                spacing: '5dp'\n                Image:\n                    source: 'atlas://gui/kivy/theming/light/pen'\n                    size_hint: None, None\n                    size: '22dp', '22dp'\n                    pos_hint: {'center_y': .5}\n                BlueButton:\n                    id: description\n                    text: s.message if s.message else (_('No Description') if root.is_pr else _('Description'))\n                    disabled: root.is_pr\n                    on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            IconButton:\n                id: qr\n                size_hint: 0.6, 1\n                on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))\n                icon: 'atlas://gui/kivy/theming/light/camera'\n            Button:\n                text: _('Paste')\n                on_release: s.parent.do_paste()\n            Button:\n                text: _('Clear')\n                on_release: s.parent.do_clear()\n            IconButton:\n                size_hint: 0.6, 1\n                on_release: s.parent.do_save()\n                icon: 'atlas://gui/kivy/theming/light/save'\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Widget:\n                size_hint: 2, 1\n            Button:\n                text: _('Pay')\n                size_hint: 1, 1\n                on_release: s.parent.do_send()\n        Widget:\n            size_hint: 1, 1\n\n\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/server.kv",
    "content": "Popup:\n    id: nd\n    title: _('Server')\n    BoxLayout:\n        orientation: 'vertical'\n        padding: '10dp'\n        spacing: '10dp'\n        TopLabel:\n            text: _(\"Electrum requests your transaction history from a single server. The returned history is checked against blockchain headers sent by other nodes, using Simple Payment Verification (SPV).\")\n            font_size: '6pt'\n        Widget:\n            size_hint: 1, 0.8\n        GridLayout:\n            cols: 2\n            Label:\n                height: '36dp'\n                size_hint_x: 1\n                size_hint_y: None\n                text: _('Host') + ':'\n            TextInput:\n                id: host\n                multiline: False\n                height: '36dp'\n                size_hint_x: 3\n                size_hint_y: None\n                text: app.server_host\n            Label:\n                height: '36dp'\n                size_hint_x: 1\n                size_hint_y: None\n                text: _('Port') + ':'\n            TextInput:\n                id: port\n                multiline: False\n                input_type: 'number'\n                height: '36dp'\n                size_hint_x: 3\n                size_hint_y: None\n                text: app.server_port\n            Widget\n            Button:\n                id: chooser\n                text: _('Choose from peers')\n                height: '36dp'\n                size_hint_x: 0.5\n                size_hint_y: None\n                on_release:\n                    app.choose_server_dialog(root)\n        Widget:\n            size_hint: 1, 0.1\n        BoxLayout:\n            Widget:\n                size_hint: 0.5, None\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('OK')\n                on_release:\n                    host, port, protocol, proxy, auto_connect = app.network.get_parameters()\n                    host = str(root.ids.host.text)\n                    port = str(root.ids.port.text)\n                    app.network.set_parameters(host, port, protocol, proxy, auto_connect)\n                    nd.dismiss()\n"
  },
  {
    "path": "gui/kivy/uix/ui_screens/status.kv",
    "content": "Popup:\n    title: \"Electrum\"\n    confirmed: 0\n    unconfirmed: 0\n    unmatured: 0\n    watching_only: app.wallet.is_watching_only()\n    on_parent:\n        self.confirmed, self.unconfirmed, self.unmatured = app.wallet.get_balance()\n    BoxLayout:\n        orientation: 'vertical'\n        ScrollView:\n            GridLayout:\n                cols: 1\n                height: self.minimum_height\n                size_hint_y: None\n                padding: '10dp'\n                spacing: '10dp'\n                padding: '10dp'\n                spacing: '10dp'\n                GridLayout:\n                    cols: 1\n                    size_hint_y: None\n                    height: self.minimum_height\n                    spacing: '10dp'\n                    BoxLabel:\n                        text: _('Wallet Name')\n                        value: app.wallet_name()\n                    BoxLabel:\n                        text: _(\"Wallet type:\")\n                        value: app.wallet.wallet_type\n                    BoxLabel:\n                        text: _(\"Balance\") + ':'\n                        value: app.format_amount_and_units(root.confirmed + root.unconfirmed + root.unmatured)\n                    BoxLabel:\n                        text: _(\"Confirmed\") + ':'\n                        opacity: 1 if root.confirmed else 0\n                        value: app.format_amount_and_units(root.confirmed)\n                        opacity: 1 if root.confirmed else 0\n                    BoxLabel:\n                        text: _(\"Unconfirmed\") + ':'\n                        opacity: 1 if root.unconfirmed else 0\n                        value: app.format_amount_and_units(root.unconfirmed)\n                    BoxLabel:\n                        text: _(\"Unmatured\") + ':'\n                        opacity: 1 if root.unmatured else 0\n                        value: app.format_amount_and_units(root.unmatured)\n                        opacity: 1 if root.unmatured else 0\n\n                TopLabel:\n                    text: _('Master Public Key')\n                RefLabel:\n                    data: app.wallet.get_master_public_key() or 'None'\n                    name: _('Master Public Key')\n                TopLabel:\n                    id: seed_label\n                    text: _('This wallet is watching-only') if root.watching_only else ''\n\n        BoxLayout:\n            size_hint: 1, None\n            height: '48dp'\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: '' if root.watching_only else (_('Hide seed') if seed_label.text else _('Show seed'))\n                disabled: root.watching_only\n                on_release:\n                    setattr(seed_label, 'text', '') if seed_label.text else app.show_seed(seed_label)\n            Button:\n                size_hint: 0.5, None\n                height: '48dp'\n                text: _('Delete')\n                on_release:\n                    root.dismiss()\n                    app.delete_wallet()\n"
  },
  {
    "path": "gui/qt/__init__.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport signal\nimport sys\n\n\ntry:\n    import PyQt5\nexcept Exception:\n    sys.exit(\"Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'\")\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom PyQt5.QtCore import *\nimport PyQt5.QtCore as QtCore\n\nfrom electrum.i18n import _, set_language\nfrom electrum.plugins import run_hook\nfrom electrum import WalletStorage\n# from electrum.synchronizer import Synchronizer\n# from electrum.verifier import SPV\n# from electrum.util import DebugMem\nfrom electrum.util import UserCancelled, print_error\n# from electrum.wallet import Abstract_Wallet\n\nfrom .installwizard import InstallWizard, GoBack\n\n\ntry:\n    from . import icons_rc\nexcept Exception as e:\n    print(e)\n    print(\"Error: Could not find icons file.\")\n    print(\"Please run 'pyrcc5 icons.qrc -o gui/qt/icons_rc.py', and reinstall Electrum\")\n    sys.exit(1)\n\nfrom .util import *   # * needed for plugins\nfrom .main_window import ElectrumWindow\nfrom .network_dialog import NetworkDialog\n\n\nclass OpenFileEventFilter(QObject):\n    def __init__(self, windows):\n        self.windows = windows\n        super(OpenFileEventFilter, self).__init__()\n\n    def eventFilter(self, obj, event):\n        if event.type() == QtCore.QEvent.FileOpen:\n            if len(self.windows) >= 1:\n                self.windows[0].pay_to_URI(event.url().toEncoded())\n                return True\n        return False\n\n\nclass QElectrumApplication(QApplication):\n    new_window_signal = pyqtSignal(str, object)\n\n\nclass QNetworkUpdatedSignalObject(QObject):\n    network_updated_signal = pyqtSignal(str, object)\n\n\nclass ElectrumGui:\n\n    def __init__(self, config, daemon, plugins):\n        set_language(config.get('language'))\n        # Uncomment this call to verify objects are being properly\n        # GC-ed when windows are closed\n        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,\n        #                            ElectrumWindow], interval=5)])\n        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)\n        if hasattr(QtCore.Qt, \"AA_ShareOpenGLContexts\"):\n            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)\n        self.config = config\n        self.daemon = daemon\n        self.plugins = plugins\n        self.windows = []\n        self.efilter = OpenFileEventFilter(self.windows)\n        self.app = QElectrumApplication(sys.argv)\n        self.app.installEventFilter(self.efilter)\n        self.timer = Timer()\n        self.nd = None\n        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()\n        # init tray\n        self.dark_icon = self.config.get(\"dark_icon\", False)\n        self.tray = QSystemTrayIcon(self.tray_icon(), None)\n        self.tray.setToolTip('Electrum')\n        self.tray.activated.connect(self.tray_activated)\n        self.build_tray_menu()\n        self.tray.show()\n        self.app.new_window_signal.connect(self.start_new_window)\n        run_hook('init_qt', self)\n        ColorScheme.update_from_widget(QWidget())\n\n    def build_tray_menu(self):\n        # Avoid immediate GC of old menu when window closed via its action\n        if self.tray.contextMenu() is None:\n            m = QMenu()\n            self.tray.setContextMenu(m)\n        else:\n            m = self.tray.contextMenu()\n            m.clear()\n        for window in self.windows:\n            submenu = m.addMenu(window.wallet.basename())\n            submenu.addAction(_(\"Show/Hide\"), window.show_or_hide)\n            submenu.addAction(_(\"Close\"), window.close)\n        m.addAction(_(\"Dark/Light\"), self.toggle_tray_icon)\n        m.addSeparator()\n        m.addAction(_(\"Exit Electrum\"), self.close)\n\n    def tray_icon(self):\n        if self.dark_icon:\n            return QIcon(':icons/electrum_dark_icon.png')\n        else:\n            return QIcon(':icons/electrum_light_icon.png')\n\n    def toggle_tray_icon(self):\n        self.dark_icon = not self.dark_icon\n        self.config.set_key(\"dark_icon\", self.dark_icon, True)\n        self.tray.setIcon(self.tray_icon())\n\n    def tray_activated(self, reason):\n        if reason == QSystemTrayIcon.DoubleClick:\n            if all([w.is_hidden() for w in self.windows]):\n                for w in self.windows:\n                    w.bring_to_top()\n            else:\n                for w in self.windows:\n                    w.hide()\n\n    def close(self):\n        for window in self.windows:\n            window.close()\n\n    def new_window(self, path, uri=None):\n        # Use a signal as can be called from daemon thread\n        self.app.new_window_signal.emit(path, uri)\n\n    def show_network_dialog(self, parent):\n        if not self.daemon.network:\n            parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))\n            return\n        if self.nd:\n            self.nd.on_update()\n            self.nd.show()\n            self.nd.raise_()\n            return\n        self.nd = NetworkDialog(self.daemon.network, self.config,\n                                self.network_updated_signal_obj)\n        self.nd.show()\n\n    def create_window_for_wallet(self, wallet):\n        w = ElectrumWindow(self, wallet)\n        self.windows.append(w)\n        self.build_tray_menu()\n        # FIXME: Remove in favour of the load_wallet hook\n        run_hook('on_new_window', w)\n        return w\n\n    def start_new_window(self, path, uri):\n        '''Raises the window for the wallet if it is open.  Otherwise\n        opens the wallet and creates a new window for it.'''\n        for w in self.windows:\n            if w.wallet.storage.path == path:\n                w.bring_to_top()\n                break\n        else:\n            try:\n                wallet = self.daemon.load_wallet(path, None)\n            except  BaseException as e:\n                d = QMessageBox(QMessageBox.Warning, _('Error'), 'Cannot load wallet:\\n' + str(e))\n                d.exec_()\n                return\n            if not wallet:\n                storage = WalletStorage(path, manual_upgrades=True)\n                wizard = InstallWizard(self.config, self.app, self.plugins, storage)\n                try:\n                    wallet = wizard.run_and_get_wallet()\n                except UserCancelled:\n                    pass\n                except GoBack as e:\n                    print_error('[start_new_window] Exception caught (GoBack)', e)\n                wizard.terminate()\n                if not wallet:\n                    return\n                wallet.start_threads(self.daemon.network)\n                self.daemon.add_wallet(wallet)\n            w = self.create_window_for_wallet(wallet)\n        if uri:\n            w.pay_to_URI(uri)\n        w.bring_to_top()\n        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)\n\n        # this will activate the window\n        w.activateWindow()\n        return w\n\n    def close_window(self, window):\n        self.windows.remove(window)\n        self.build_tray_menu()\n        # save wallet path of last open window\n        if not self.windows:\n            self.config.save_last_wallet(window.wallet)\n        run_hook('on_close_window', window)\n\n    def init_network(self):\n        # Show network dialog if config does not exist\n        if self.daemon.network:\n            if self.config.get('auto_connect') is None:\n                wizard = InstallWizard(self.config, self.app, self.plugins, None)\n                wizard.init_network(self.daemon.network)\n                wizard.terminate()\n\n    def main(self):\n        try:\n            self.init_network()\n        except UserCancelled:\n            return\n        except GoBack:\n            return\n        except:\n            import traceback\n            traceback.print_exc(file=sys.stdout)\n            return\n        self.timer.start()\n        self.config.open_last_wallet()\n        path = self.config.get_wallet_path()\n        if not self.start_new_window(path, self.config.get('url')):\n            return\n        signal.signal(signal.SIGINT, lambda *args: self.app.quit())\n\n        def quit_after_last_window():\n            # on some platforms, not only does exec_ not return but not even\n            # aboutToQuit is emitted (but following this, it should be emitted)\n            if self.app.quitOnLastWindowClosed():\n                self.app.quit()\n        self.app.lastWindowClosed.connect(quit_after_last_window)\n\n        def clean_up():\n            # Shut down the timer cleanly\n            self.timer.stop()\n            # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html\n            event = QtCore.QEvent(QtCore.QEvent.Clipboard)\n            self.app.sendEvent(self.app.clipboard(), event)\n            self.tray.hide()\n        self.app.aboutToQuit.connect(clean_up)\n\n        # main loop\n        self.app.exec_()\n        # on some platforms the exec_ call may not return, so use clean_up()\n"
  },
  {
    "path": "gui/qt/address_dialog.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom electrum.i18n import _\n\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\n\nfrom .util import *\nfrom .history_list import HistoryList\nfrom .qrtextedit import ShowQRTextEdit\n\n\nclass AddressDialog(WindowModalDialog):\n\n    def __init__(self, parent, address):\n        WindowModalDialog.__init__(self, parent, _(\"Address\"))\n        self.address = address\n        self.parent = parent\n        self.config = parent.config\n        self.wallet = parent.wallet\n        self.app = parent.app\n        self.saved = True\n\n        self.setMinimumWidth(700)\n        vbox = QVBoxLayout()\n        self.setLayout(vbox)\n\n        vbox.addWidget(QLabel(_(\"Address:\")))\n        self.addr_e = ButtonsLineEdit(self.address)\n        self.addr_e.addCopyButton(self.app)\n        icon = \":icons/qrcode_white.png\" if ColorScheme.dark_scheme else \":icons/qrcode.png\"\n        self.addr_e.addButton(icon, self.show_qr, _(\"Show QR Code\"))\n        self.addr_e.setReadOnly(True)\n        vbox.addWidget(self.addr_e)\n\n        try:\n            pubkeys = self.wallet.get_public_keys(address)\n        except BaseException as e:\n            pubkeys = None\n        if pubkeys:\n            vbox.addWidget(QLabel(_(\"Public keys\") + ':'))\n            for pubkey in pubkeys:\n                pubkey_e = ButtonsLineEdit(pubkey)\n                pubkey_e.addCopyButton(self.app)\n                vbox.addWidget(pubkey_e)\n\n        try:\n            redeem_script = self.wallet.pubkeys_to_redeem_script(pubkeys)\n        except BaseException as e:\n            redeem_script = None\n        if redeem_script:\n            vbox.addWidget(QLabel(_(\"Redeem Script\") + ':'))\n            redeem_e = ShowQRTextEdit(text=redeem_script)\n            redeem_e.addCopyButton(self.app)\n            vbox.addWidget(redeem_e)\n\n        vbox.addWidget(QLabel(_(\"History\")))\n        self.hw = HistoryList(self.parent)\n        self.hw.get_domain = self.get_domain\n        vbox.addWidget(self.hw)\n\n        vbox.addLayout(Buttons(CloseButton(self)))\n        self.format_amount = self.parent.format_amount\n        self.hw.update()\n\n    def get_domain(self):\n        return [self.address]\n\n    def show_qr(self):\n        text = self.address\n        try:\n            self.parent.show_qrcode(text, 'Address', parent=self)\n        except Exception as e:\n            self.show_message(str(e))\n"
  },
  {
    "path": "gui/qt/address_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport webbrowser\n\nfrom .util import *\nfrom electrum.i18n import _\nfrom electrum.util import block_explorer_URL\nfrom electrum.plugins import run_hook\nfrom electrum.bitcoin import is_address\n\n\nclass AddressList(MyTreeWidget):\n    filter_columns = [0, 1, 2]  # Address, Label, Balance\n\n    def __init__(self, parent=None):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [], 1)\n        self.refresh_headers()\n        self.setSelectionMode(QAbstractItemView.ExtendedSelection)\n        self.show_change = False\n        self.show_used = 0\n        self.change_button = QComboBox(self)\n        self.change_button.currentIndexChanged.connect(self.toggle_change)\n        for t in [_('Receiving'), _('Change')]:\n            self.change_button.addItem(t)\n        self.used_button = QComboBox(self)\n        self.used_button.currentIndexChanged.connect(self.toggle_used)\n        for t in [_('All'), _('Unused'), _('Funded'), _('Used')]:\n            self.used_button.addItem(t)\n\n    def get_list_header(self):\n        return QLabel(_(\"Filter \")), self.change_button, self.used_button\n\n    def refresh_headers(self):\n        headers = [ _('Address'), _('Label'), _('Balance')]\n        fx = self.parent.fx\n        if fx and fx.get_fiat_address_config():\n            headers.extend([_(fx.get_currency()+' Balance')])\n        headers.extend([_('Tx')])\n        self.update_headers(headers)\n\n    def toggle_change(self, show):\n        show = bool(show)\n        if show == self.show_change:\n            return\n        self.show_change = show\n        self.update()\n\n    def toggle_used(self, state):\n        if state == self.show_used:\n            return\n        self.show_used = state\n        self.update()\n\n    def on_update(self):\n        self.wallet = self.parent.wallet\n        item = self.currentItem()\n        current_address = item.data(0, Qt.UserRole) if item else None\n        addr_list = self.wallet.get_change_addresses() if self.show_change else self.wallet.get_receiving_addresses()\n        self.clear()\n        for address in addr_list:\n            num = len(self.wallet.history.get(address,[]))\n            is_used = self.wallet.is_used(address)\n            label = self.wallet.labels.get(address, '')\n            c, u, x = self.wallet.get_addr_balance(address)\n            balance = c + u + x\n            if self.show_used == 1 and (balance or is_used):\n                continue\n            if self.show_used == 2 and balance == 0:\n                continue\n            if self.show_used == 3 and not is_used:\n                continue\n            balance_text = self.parent.format_amount(balance)\n            fx = self.parent.fx\n            if fx and fx.get_fiat_address_config():\n                rate = fx.exchange_rate()\n                fiat_balance = fx.value_str(balance, rate)\n                address_item = QTreeWidgetItem([address, label, balance_text, fiat_balance, \"%d\"%num])\n                address_item.setTextAlignment(3, Qt.AlignRight)\n            else:\n                address_item = QTreeWidgetItem([address, label, balance_text, \"%d\"%num])\n                address_item.setTextAlignment(2, Qt.AlignRight)\n            address_item.setFont(0, QFont(MONOSPACE_FONT))\n            address_item.setData(0, Qt.UserRole, address)\n            address_item.setData(0, Qt.UserRole+1, True) # label can be edited\n            if self.wallet.is_frozen(address):\n                address_item.setBackground(0, ColorScheme.BLUE.as_color(True))\n            if self.wallet.is_beyond_limit(address, self.show_change):\n                address_item.setBackground(0, ColorScheme.RED.as_color(True))\n            self.addChild(address_item)\n            if address == current_address:\n                self.setCurrentItem(address_item)\n\n    def create_menu(self, position):\n        from electrum.wallet import Multisig_Wallet\n        is_multisig = isinstance(self.wallet, Multisig_Wallet)\n        can_delete = self.wallet.can_delete_address()\n        selected = self.selectedItems()\n        multi_select = len(selected) > 1\n        addrs = [item.text(0) for item in selected]\n        if not addrs:\n            return\n        if not multi_select:\n            item = self.itemAt(position)\n            col = self.currentColumn()\n            if not item:\n                return\n            addr = addrs[0]\n            if not is_address(addr):\n                item.setExpanded(not item.isExpanded())\n                return\n\n        menu = QMenu()\n        if not multi_select:\n            column_title = self.headerItem().text(col)\n            copy_text = item.text(col)\n            menu.addAction(_(\"Copy %s\")%column_title, lambda: self.parent.app.clipboard().setText(copy_text))\n            menu.addAction(_('Details'), lambda: self.parent.show_address(addr))\n            if col in self.editable_columns:\n                menu.addAction(_(\"Edit %s\")%column_title, lambda: self.editItem(item, col))\n            menu.addAction(_(\"Request payment\"), lambda: self.parent.receive_at(addr))\n            if self.wallet.can_export():\n                menu.addAction(_(\"Private key\"), lambda: self.parent.show_private_key(addr))\n            if not is_multisig and not self.wallet.is_watching_only():\n                menu.addAction(_(\"Sign/verify message\"), lambda: self.parent.sign_verify_message(addr))\n                menu.addAction(_(\"Encrypt/decrypt message\"), lambda: self.parent.encrypt_message(addr))\n            if can_delete:\n                menu.addAction(_(\"Remove from wallet\"), lambda: self.parent.remove_address(addr))\n            addr_URL = block_explorer_URL(self.config, 'addr', addr)\n            if addr_URL:\n                menu.addAction(_(\"View on block explorer\"), lambda: webbrowser.open(addr_URL))\n\n            if not self.wallet.is_frozen(addr):\n                menu.addAction(_(\"Freeze\"), lambda: self.parent.set_frozen_state([addr], True))\n            else:\n                menu.addAction(_(\"Unfreeze\"), lambda: self.parent.set_frozen_state([addr], False))\n\n        coins = self.wallet.get_utxos(addrs)\n        if coins:\n            menu.addAction(_(\"Spend from\"), lambda: self.parent.spend_coins(coins))\n\n        run_hook('receive_menu', menu, addrs, self.wallet)\n        menu.exec_(self.viewport().mapToGlobal(position))\n\n    def on_permit_edit(self, item, column):\n        # labels for headings, e.g. \"receiving\" or \"used\" should not be editable\n        return item.childCount() == 0\n"
  },
  {
    "path": "gui/qt/amountedit.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame)\n\nfrom decimal import Decimal\nfrom electrum.util import format_satoshis_plain\n\n\nclass MyLineEdit(QLineEdit):\n    frozen = pyqtSignal()\n\n    def setFrozen(self, b):\n        self.setReadOnly(b)\n        self.setFrame(not b)\n        self.frozen.emit()\n\nclass AmountEdit(MyLineEdit):\n    shortcut = pyqtSignal()\n\n    def __init__(self, base_unit, is_int = False, parent=None):\n        QLineEdit.__init__(self, parent)\n        # This seems sufficient for hundred-BTC amounts with 8 decimals\n        self.setFixedWidth(140)\n        self.base_unit = base_unit\n        self.textChanged.connect(self.numbify)\n        self.is_int = is_int\n        self.is_shortcut = False\n        self.help_palette = QPalette()\n\n    def decimal_point(self):\n        return 8\n\n    def numbify(self):\n        text = self.text().strip()\n        if text == '!':\n            self.shortcut.emit()\n            return\n        pos = self.cursorPosition()\n        chars = '0123456789'\n        if not self.is_int: chars +='.'\n        s = ''.join([i for i in text if i in chars])\n        if not self.is_int:\n            if '.' in s:\n                p = s.find('.')\n                s = s.replace('.','')\n                s = s[:p] + '.' + s[p:p+self.decimal_point()]\n        self.setText(s)\n        # setText sets Modified to False.  Instead we want to remember\n        # if updates were because of user modification.\n        self.setModified(self.hasFocus())\n        self.setCursorPosition(pos)\n\n    def paintEvent(self, event):\n        QLineEdit.paintEvent(self, event)\n        if self.base_unit:\n            panel = QStyleOptionFrame()\n            self.initStyleOption(panel)\n            textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)\n            textRect.adjust(2, 0, -10, 0)\n            painter = QPainter(self)\n            painter.setPen(self.help_palette.brush(QPalette.Disabled, QPalette.Text).color())\n            painter.drawText(textRect, Qt.AlignRight | Qt.AlignVCenter, self.base_unit())\n\n    def get_amount(self):\n        try:\n            return (int if self.is_int else Decimal)(str(self.text()))\n        except:\n            return None\n\n    def setAmount(self, x):\n        self.setText(\"%d\"%x)\n\n\nclass BTCAmountEdit(AmountEdit):\n\n    def __init__(self, decimal_point, is_int = False, parent=None):\n        AmountEdit.__init__(self, self._base_unit, is_int, parent)\n        self.decimal_point = decimal_point\n\n    def _base_unit(self):\n        p = self.decimal_point()\n        if p == 8:\n            return 'BTCP'\n        if p == 5:\n            return 'mBTCP'\n        if p == 2:\n            return 'bits'\n        raise Exception('Unknown base unit')\n\n    def get_amount(self):\n        try:\n            x = Decimal(str(self.text()))\n        except:\n            return None\n        p = pow(10, self.decimal_point())\n        return int( p * x )\n\n    def setAmount(self, amount):\n        if amount is None:\n            self.setText(\" \") # Space forces repaint in case units changed\n        else:\n            self.setText(format_satoshis_plain(amount, self.decimal_point()))\n\n\nclass FeerateEdit(BTCAmountEdit):\n    def _base_unit(self):\n        p = self.decimal_point()\n        if p == 2:\n            return 'mBTCP/kB'\n        if p == 0:\n            return 'sat/byte'\n        raise Exception('Unknown base unit')\n\n    def get_amount(self):\n        sat_per_byte_amount = BTCAmountEdit.get_amount(self)\n        if sat_per_byte_amount is None:\n            return None\n        return 1000 * sat_per_byte_amount\n"
  },
  {
    "path": "gui/qt/console.py",
    "content": "\n# source: http://stackoverflow.com/questions/2758159/how-to-embed-a-python-interpreter-in-a-pyqt-widget\n\nimport sys, os, re\nimport traceback, platform\nfrom PyQt5 import QtCore\nfrom PyQt5 import QtGui\nfrom PyQt5 import QtWidgets\nfrom electrum import util\n\n\nif platform.system() == 'Windows':\n    MONOSPACE_FONT = 'Lucida Console'\nelif platform.system() == 'Darwin':\n    MONOSPACE_FONT = 'Monaco'\nelse:\n    MONOSPACE_FONT = 'monospace'\n\n\nclass Console(QtWidgets.QPlainTextEdit):\n    def __init__(self, prompt='>> ', startup_message='', parent=None):\n        QtWidgets.QPlainTextEdit.__init__(self, parent)\n\n        self.prompt = prompt\n        self.history = []\n        self.namespace = {}\n        self.construct = []\n\n        self.setGeometry(50, 75, 600, 400)\n        self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere)\n        self.setUndoRedoEnabled(False)\n        self.document().setDefaultFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal))\n        self.showMessage(startup_message)\n\n        self.updateNamespace({'run':self.run_script})\n        self.set_json(False)\n\n    def set_json(self, b):\n        self.is_json = b\n\n    def run_script(self, filename):\n        with open(filename) as f:\n            script = f.read()\n\n        # eval is generally considered bad practice. use it wisely!\n        result = eval(script, self.namespace, self.namespace)\n\n\n\n    def updateNamespace(self, namespace):\n        self.namespace.update(namespace)\n\n    def showMessage(self, message):\n        self.appendPlainText(message)\n        self.newPrompt()\n\n    def clear(self):\n        self.setPlainText('')\n        self.newPrompt()\n\n    def newPrompt(self):\n        if self.construct:\n            prompt = '.' * len(self.prompt)\n        else:\n            prompt = self.prompt\n\n        self.completions_pos = self.textCursor().position()\n        self.completions_visible = False\n\n        self.appendPlainText(prompt)\n        self.moveCursor(QtGui.QTextCursor.End)\n\n    def getCommand(self):\n        doc = self.document()\n        curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()\n        curr_line = curr_line.rstrip()\n        curr_line = curr_line[len(self.prompt):]\n        return curr_line\n\n    def setCommand(self, command):\n        if self.getCommand() == command:\n            return\n\n        doc = self.document()\n        curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text()\n        self.moveCursor(QtGui.QTextCursor.End)\n        for i in range(len(curr_line) - len(self.prompt)):\n            self.moveCursor(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor)\n\n        self.textCursor().removeSelectedText()\n        self.textCursor().insertText(command)\n        self.moveCursor(QtGui.QTextCursor.End)\n\n    def show_completions(self, completions):\n        if self.completions_visible:\n            self.hide_completions()\n\n        c = self.textCursor()\n        c.setPosition(self.completions_pos)\n\n        completions = map(lambda x: x.split('.')[-1], completions)\n        t = '\\n' + ' '.join(completions)\n        if len(t) > 500:\n            t = t[:500] + '...'\n        c.insertText(t)\n        self.completions_end = c.position()\n\n        self.moveCursor(QtGui.QTextCursor.End)\n        self.completions_visible = True\n\n    def hide_completions(self):\n        if not self.completions_visible:\n            return\n        c = self.textCursor()\n        c.setPosition(self.completions_pos)\n        l = self.completions_end - self.completions_pos\n        for x in range(l): c.deleteChar()\n\n        self.moveCursor(QtGui.QTextCursor.End)\n        self.completions_visible = False\n\n    def getConstruct(self, command):\n        if self.construct:\n            prev_command = self.construct[-1]\n            self.construct.append(command)\n            if not prev_command and not command:\n                ret_val = '\\n'.join(self.construct)\n                self.construct = []\n                return ret_val\n            else:\n                return ''\n        else:\n            if command and command[-1] == (':'):\n                self.construct.append(command)\n                return ''\n            else:\n                return command\n\n    def getHistory(self):\n        return self.history\n\n    def setHisory(self, history):\n        self.history = history\n\n    def addToHistory(self, command):\n        if command[0:1] == ' ':\n            return\n\n        if command and (not self.history or self.history[-1] != command):\n            self.history.append(command)\n        self.history_index = len(self.history)\n\n    def getPrevHistoryEntry(self):\n        if self.history:\n            self.history_index = max(0, self.history_index - 1)\n            return self.history[self.history_index]\n        return ''\n\n    def getNextHistoryEntry(self):\n        if self.history:\n            hist_len = len(self.history)\n            self.history_index = min(hist_len, self.history_index + 1)\n            if self.history_index < hist_len:\n                return self.history[self.history_index]\n        return ''\n\n    def getCursorPosition(self):\n        c = self.textCursor()\n        return c.position() - c.block().position() - len(self.prompt)\n\n    def setCursorPosition(self, position):\n        self.moveCursor(QtGui.QTextCursor.StartOfLine)\n        for i in range(len(self.prompt) + position):\n            self.moveCursor(QtGui.QTextCursor.Right)\n\n    def register_command(self, c, func):\n        methods = { c: func}\n        self.updateNamespace(methods)\n\n\n    def runCommand(self):\n        command = self.getCommand()\n        self.addToHistory(command)\n\n        command = self.getConstruct(command)\n\n        if command:\n            tmp_stdout = sys.stdout\n\n            class stdoutProxy():\n                def __init__(self, write_func):\n                    self.write_func = write_func\n                    self.skip = False\n\n                def flush(self):\n                    pass\n\n                def write(self, text):\n                    if not self.skip:\n                        stripped_text = text.rstrip('\\n')\n                        self.write_func(stripped_text)\n                        QtCore.QCoreApplication.processEvents()\n                    self.skip = not self.skip\n\n            if type(self.namespace.get(command)) == type(lambda:None):\n                self.appendPlainText(\"'%s' is a function. Type '%s()' to use it in the Python console.\"%(command, command))\n                self.newPrompt()\n                return\n\n            sys.stdout = stdoutProxy(self.appendPlainText)\n            try:\n                try:\n                    # eval is generally considered bad practice. use it wisely!\n                    result = eval(command, self.namespace, self.namespace)\n                    if result != None:\n                        if self.is_json:\n                            util.print_msg(util.json_encode(result))\n                        else:\n                            self.appendPlainText(repr(result))\n                except SyntaxError:\n                    # exec is generally considered bad practice. use it wisely!\n                    exec(command, self.namespace, self.namespace)\n            except SystemExit:\n                self.close()\n            except Exception:\n                traceback_lines = traceback.format_exc().split('\\n')\n                # Remove traceback mentioning this file, and a linebreak\n                for i in (3,2,1,-1):\n                    traceback_lines.pop(i)\n                self.appendPlainText('\\n'.join(traceback_lines))\n            sys.stdout = tmp_stdout\n        self.newPrompt()\n        self.set_json(False)\n\n\n    def keyPressEvent(self, event):\n        if event.key() == QtCore.Qt.Key_Tab:\n            self.completions()\n            return\n\n        self.hide_completions()\n\n        if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):\n            self.runCommand()\n            return\n        if event.key() == QtCore.Qt.Key_Home:\n            self.setCursorPosition(0)\n            return\n        if event.key() == QtCore.Qt.Key_PageUp:\n            return\n        elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace):\n            if self.getCursorPosition() == 0:\n                return\n        elif event.key() == QtCore.Qt.Key_Up:\n            self.setCommand(self.getPrevHistoryEntry())\n            return\n        elif event.key() == QtCore.Qt.Key_Down:\n            self.setCommand(self.getNextHistoryEntry())\n            return\n        elif event.key() == QtCore.Qt.Key_L and event.modifiers() == QtCore.Qt.ControlModifier:\n            self.clear()\n\n        super(Console, self).keyPressEvent(event)\n\n\n\n    def completions(self):\n        cmd = self.getCommand()\n        lastword = re.split(' |\\(|\\)',cmd)[-1]\n        beginning = cmd[0:-len(lastword)]\n\n        path = lastword.split('.')\n        ns = self.namespace.keys()\n\n        if len(path) == 1:\n            ns = ns\n            prefix = ''\n        else:\n            obj = self.namespace.get(path[0])\n            prefix = path[0] + '.'\n            ns = dir(obj)\n\n\n        completions = []\n        for x in ns:\n            if x[0] == '_':continue\n            xx = prefix + x\n            if xx.startswith(lastword):\n                completions.append(xx)\n        completions.sort()\n\n        if not completions:\n            self.hide_completions()\n        elif len(completions) == 1:\n            self.hide_completions()\n            self.setCommand(beginning + completions[0])\n        else:\n            # find common prefix\n            p = os.path.commonprefix(completions)\n            if len(p)>len(lastword):\n                self.hide_completions()\n                self.setCommand(beginning + p)\n            else:\n                self.show_completions(completions)\n\n\nwelcome_message = '''\n   ---------------------------------------------------------------\n     Welcome to a primitive Python interpreter.\n   ---------------------------------------------------------------\n'''\n\nif __name__ == '__main__':\n    app = QtWidgets.QApplication(sys.argv)\n    console = Console(startup_message=welcome_message)\n    console.updateNamespace({'myVar1' : app, 'myVar2' : 1234})\n    console.show()\n    sys.exit(app.exec_())\n"
  },
  {
    "path": "gui/qt/contact_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport webbrowser\n\nfrom electrum.i18n import _\nfrom electrum.bitcoin import is_address\nfrom electrum.util import block_explorer_URL\nfrom electrum.plugins import run_hook\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import (\n    QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)\nfrom .util import MyTreeWidget\n\n\nclass ContactList(MyTreeWidget):\n    filter_columns = [0, 1]  # Key, Value\n\n    def __init__(self, parent):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Name'), _('Address')], 0, [0])\n        self.setSelectionMode(QAbstractItemView.ExtendedSelection)\n        self.setSortingEnabled(True)\n\n    def on_permit_edit(self, item, column):\n        # openalias items shouldn't be editable\n        return item.text(1) != \"openalias\"\n\n    def on_edited(self, item, column, prior):\n        if column == 0:  # Remove old contact if renamed\n            self.parent.contacts.pop(prior)\n        self.parent.set_contact(item.text(0), item.text(1))\n\n    def import_contacts(self):\n        wallet_folder = self.parent.get_wallet_folder()\n        filename, __ = QFileDialog.getOpenFileName(self.parent, \"Select your wallet file\", wallet_folder)\n        if not filename:\n            return\n        self.parent.contacts.import_file(filename)\n        self.on_update()\n\n    def create_menu(self, position):\n        menu = QMenu()\n        selected = self.selectedItems()\n        if not selected:\n            menu.addAction(_(\"New contact\"), lambda: self.parent.new_contact_dialog())\n            menu.addAction(_(\"Import file\"), lambda: self.import_contacts())\n        else:\n            names = [item.text(0) for item in selected]\n            keys = [item.text(1) for item in selected]\n            column = self.currentColumn()\n            column_title = self.headerItem().text(column)\n            column_data = '\\n'.join([item.text(column) for item in selected])\n            menu.addAction(_(\"Copy %s\")%column_title, lambda: self.parent.app.clipboard().setText(column_data))\n            if column in self.editable_columns:\n                item = self.currentItem()\n                menu.addAction(_(\"Edit %s\")%column_title, lambda: self.editItem(item, column))\n            menu.addAction(_(\"Pay to\"), lambda: self.parent.payto_contacts(keys))\n            menu.addAction(_(\"Delete\"), lambda: self.parent.delete_contacts(keys))\n            URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]\n            if URLs:\n                menu.addAction(_(\"View on block explorer\"), lambda: map(webbrowser.open, URLs))\n\n        run_hook('create_contact_menu', menu, selected)\n        menu.exec_(self.viewport().mapToGlobal(position))\n\n    def on_update(self):\n        item = self.currentItem()\n        current_key = item.data(0, Qt.UserRole) if item else None\n        self.clear()\n        for key in sorted(self.parent.contacts.keys()):\n            _type, name = self.parent.contacts[key]\n            item = QTreeWidgetItem([name, key])\n            item.setData(0, Qt.UserRole, key)\n            self.addTopLevelItem(item)\n            if key == current_key:\n                self.setCurrentItem(item)\n        run_hook('update_contacts_tab', self)\n"
  },
  {
    "path": "gui/qt/fee_slider.py",
    "content": "\nfrom electrum.i18n import _\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import QSlider, QToolTip\n\nimport threading\n\nclass FeeSlider(QSlider):\n\n    def __init__(self, window, config, callback):\n        QSlider.__init__(self, Qt.Horizontal)\n        self.config = config\n        self.window = window\n        self.callback = callback\n        self.dyn = False\n        self.lock = threading.RLock()\n        self.update()\n        self.valueChanged.connect(self.moved)\n\n    def moved(self, pos):\n        with self.lock:\n            fee_rate = self.config.dynfee(pos) if self.dyn else self.config.static_fee(pos)\n            tooltip = self.get_tooltip(pos, fee_rate)\n            QToolTip.showText(QCursor.pos(), tooltip, self)\n            self.setToolTip(tooltip)\n            self.callback(self.dyn, pos, fee_rate)\n\n    def get_tooltip(self, pos, fee_rate):\n        from electrum.util import fee_levels\n        rate_str = self.window.format_fee_rate(fee_rate) if fee_rate else _('unknown')\n        if self.dyn:\n            tooltip = fee_levels[pos] + '\\n' + rate_str\n        else:\n            tooltip = 'Fixed rate: ' + rate_str\n            if self.config.has_fee_estimates():\n                i = self.config.reverse_dynfee(fee_rate)\n                tooltip += '\\n' + (_('Low fee') if i < 0 else 'Within %d blocks'%i)\n        return tooltip\n\n    def update(self):\n        with self.lock:\n            self.dyn = self.config.is_dynfee()\n            if self.dyn:\n                pos = self.config.get('fee_level', 2)\n                fee_rate = self.config.dynfee(pos)\n                self.setRange(0, 4)\n                self.setValue(pos)\n            else:\n                fee_rate = self.config.fee_per_kb()\n                pos = self.config.static_fee_index(fee_rate)\n                self.setRange(0, 9)\n                self.setValue(pos)\n            tooltip = self.get_tooltip(pos, fee_rate)\n            self.setToolTip(tooltip)\n\n    def activate(self):\n        self.setStyleSheet('')\n\n    def deactivate(self):\n        # TODO it would be nice to find a platform-independent solution\n        # that makes the slider look as if it was disabled\n        self.setStyleSheet(\n            \"\"\"\n            QSlider::groove:horizontal {\n                border: 1px solid #999999;\n                height: 8px;\n                background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #B1B1B1);\n                margin: 2px 0;\n            }\n\n            QSlider::handle:horizontal {\n                background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);\n                border: 1px solid #5c5c5c;\n                width: 12px;\n                margin: -2px 0;\n                border-radius: 3px;\n            }\n            \"\"\"\n        )\n"
  },
  {
    "path": "gui/qt/history_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport webbrowser\n\nfrom .util import *\nfrom electrum.i18n import _\nfrom electrum.util import block_explorer_URL\nfrom electrum.util import timestamp_to_datetime, profiler\n\n\nTX_ICONS = [\n    \"warning.png\",\n    \"warning.png\",\n    \"warning.png\",\n    \"unconfirmed.png\",\n    \"unconfirmed.png\",\n    \"clock1.png\",\n    \"clock2.png\",\n    \"clock3.png\",\n    \"clock4.png\",\n    \"clock5.png\",\n    \"confirmed.png\",\n]\n\n\nclass HistoryList(MyTreeWidget):\n    filter_columns = [2, 3, 4]  # Date, Description, Amount\n\n    def __init__(self, parent=None):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [], 3)\n        self.refresh_headers()\n        self.setColumnHidden(1, True)\n\n    def refresh_headers(self):\n        headers = ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')]\n        fx = self.parent.fx\n        if fx and fx.show_history():\n            headers.extend(['%s '%fx.ccy + _('Amount'), '%s '%fx.ccy + _('Balance')])\n        self.update_headers(headers)\n\n    def get_domain(self):\n        '''Replaced in address_dialog.py'''\n        return self.wallet.get_addresses()\n\n    @profiler\n    def on_update(self):\n        self.wallet = self.parent.wallet\n        h = self.wallet.get_history(self.get_domain())\n        item = self.currentItem()\n        current_tx = item.data(0, Qt.UserRole) if item else None\n        self.clear()\n        fx = self.parent.fx\n        if fx: fx.history_used_spot = False\n        for h_item in h:\n            tx_hash, height, conf, timestamp, value, balance = h_item\n            status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)\n            has_invoice = self.wallet.invoices.paid.get(tx_hash)\n            icon = QIcon(\":icons/\" + TX_ICONS[status])\n            v_str = self.parent.format_amount(value, True, whitespaces=True)\n            balance_str = self.parent.format_amount(balance, whitespaces=True)\n            label = self.wallet.get_label(tx_hash)\n            entry = ['', tx_hash, status_str, label, v_str, balance_str]\n            if fx and fx.show_history():\n                date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)\n                for amount in [value, balance]:\n                    text = fx.historical_value_str(amount, date)\n                    entry.append(text)\n            item = QTreeWidgetItem(entry)\n            item.setIcon(0, icon)\n            item.setToolTip(0, str(conf) + \" confirmation\" + (\"s\" if conf != 1 else \"\"))\n            if has_invoice:\n                item.setIcon(3, QIcon(\":icons/seal\"))\n            for i in range(len(entry)):\n                if i>3:\n                    item.setTextAlignment(i, Qt.AlignRight)\n                if i!=2:\n                    item.setFont(i, QFont(MONOSPACE_FONT))\n            if value and value < 0:\n                item.setForeground(3, QBrush(QColor(\"#BC1E1E\")))\n                item.setForeground(4, QBrush(QColor(\"#BC1E1E\")))\n            if tx_hash:\n                item.setData(0, Qt.UserRole, tx_hash)\n            self.insertTopLevelItem(0, item)\n            if current_tx == tx_hash:\n                self.setCurrentItem(item)\n\n    def on_doubleclick(self, item, column):\n        if self.permit_edit(item, column):\n            super(HistoryList, self).on_doubleclick(item, column)\n        else:\n            tx_hash = item.data(0, Qt.UserRole)\n            tx = self.wallet.transactions.get(tx_hash)\n            self.parent.show_transaction(tx)\n\n    def update_labels(self):\n        root = self.invisibleRootItem()\n        child_count = root.childCount()\n        for i in range(child_count):\n            item = root.child(i)\n            txid = item.data(0, Qt.UserRole)\n            label = self.wallet.get_label(txid)\n            item.setText(3, label)\n\n    def update_item(self, tx_hash, height, conf, timestamp):\n        status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)\n        icon = QIcon(\":icons/\" +  TX_ICONS[status])\n        items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)\n        if items:\n            item = items[0]\n            item.setIcon(0, icon)\n            item.setText(2, status_str)\n\n    def create_menu(self, position):\n        self.selectedIndexes()\n        item = self.currentItem()\n        if not item:\n            return\n        column = self.currentColumn()\n        tx_hash = item.data(0, Qt.UserRole)\n        if not tx_hash:\n            return\n        if column is 0:\n            column_title = \"ID\"\n            column_data = tx_hash\n        else:\n            column_title = self.headerItem().text(column)\n            column_data = item.text(column)\n\n        tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)\n        height, conf, timestamp = self.wallet.get_tx_height(tx_hash)\n        tx = self.wallet.transactions.get(tx_hash)\n        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)\n        is_unconfirmed = height <= 0\n        pr_key = self.wallet.invoices.paid.get(tx_hash)\n\n        menu = QMenu()\n\n        menu.addAction(_(\"Copy %s\")%column_title, lambda: self.parent.app.clipboard().setText(column_data))\n        if column in self.editable_columns:\n            menu.addAction(_(\"Edit %s\")%column_title, lambda: self.editItem(item, column))\n\n        menu.addAction(_(\"Details\"), lambda: self.parent.show_transaction(tx))\n        if is_unconfirmed and tx:\n            rbf = is_mine and not tx.is_final()\n            if rbf:\n                menu.addAction(_(\"Increase fee\"), lambda: self.parent.bump_fee_dialog(tx))\n            else:\n                child_tx = self.wallet.cpfp(tx, 0)\n                if child_tx:\n                    menu.addAction(_(\"Child pays for parent\"), lambda: self.parent.cpfp(tx, child_tx))\n        if pr_key:\n            menu.addAction(QIcon(\":icons/seal\"), _(\"View invoice\"), lambda: self.parent.show_invoice(pr_key))\n        if tx_URL:\n            menu.addAction(_(\"View on block explorer\"), lambda: webbrowser.open(tx_URL))\n        menu.exec_(self.viewport().mapToGlobal(position))\n"
  },
  {
    "path": "gui/qt/installwizard.py",
    "content": "\nimport os\nimport sys\nimport threading\nimport traceback\n\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\n\nfrom electrum import Wallet, WalletStorage\nfrom electrum.util import UserCancelled, InvalidPassword\nfrom electrum.base_wizard import BaseWizard\nfrom electrum.i18n import _\n\nfrom .seed_dialog import SeedLayout, KeysLayout\nfrom .network_dialog import NetworkChoiceLayout\nfrom .util import *\nfrom .password_dialog import PasswordLayout, PW_NEW\n\n\nclass GoBack(Exception):\n    pass\n\nMSG_GENERATING_WAIT = _(\"Generating your addresses, please wait...\")\nMSG_ENTER_ANYTHING = _(\"Please enter a seed phrase, a master key, a list of \"\n                       \"BTCP addresses, or a list of private keys\")\nMSG_ENTER_SEED_OR_MPK = _(\"Please enter a seed phrase or a master key (xpub or xprv):\")\nMSG_COSIGNER = _(\"Please enter the master public key of cosigner #%d:\")\nMSG_ENTER_PASSWORD = _(\"Choose a password to encrypt your wallet keys.\") + '\\n'\\\n                     + _(\"Leave this field empty if you want to disable encryption.\")\nMSG_RESTORE_PASSPHRASE = \\\n    _(\"Please enter your seed derivation passphrase. \"\n      \"Note: this is NOT your encryption password. \"\n      \"Leave this field empty if you did not use one or are unsure.\")\n\n\nclass CosignWidget(QWidget):\n    size = 120\n\n    def __init__(self, m, n):\n        QWidget.__init__(self)\n        self.R = QRect(0, 0, self.size, self.size)\n        self.setGeometry(self.R)\n        self.setMinimumHeight(self.size)\n        self.setMaximumHeight(self.size)\n        self.m = m\n        self.n = n\n\n    def set_n(self, n):\n        self.n = n\n        self.update()\n\n    def set_m(self, m):\n        self.m = m\n        self.update()\n\n    def paintEvent(self, event):\n        bgcolor = self.palette().color(QPalette.Background)\n        pen = QPen(bgcolor, 7, Qt.SolidLine)\n        qp = QPainter()\n        qp.begin(self)\n        qp.setPen(pen)\n        qp.setRenderHint(QPainter.Antialiasing)\n        qp.setBrush(Qt.gray)\n        for i in range(self.n):\n            alpha = int(16* 360 * i/self.n)\n            alpha2 = int(16* 360 * 1/self.n)\n            qp.setBrush(Qt.green if i<self.m else Qt.gray)\n            qp.drawPie(self.R, alpha, alpha2)\n        qp.end()\n\n\n\ndef wizard_dialog(func):\n    def func_wrapper(*args, **kwargs):\n        run_next = kwargs['run_next']\n        wizard = args[0]\n        wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))\n        try:\n            out = func(*args, **kwargs)\n        except GoBack:\n            wizard.go_back() if wizard.can_go_back() else wizard.close()\n            return\n        except UserCancelled:\n            return\n        #if out is None:\n        #    out = ()\n        if type(out) is not tuple:\n            out = (out,)\n        run_next(*out)\n    return func_wrapper\n\n\n\n# WindowModalDialog must come first as it overrides show_error\nclass InstallWizard(QDialog, MessageBoxMixin, BaseWizard):\n\n    accept_signal = pyqtSignal()\n    synchronized_signal = pyqtSignal(str)\n\n    def __init__(self, config, app, plugins, storage):\n        BaseWizard.__init__(self, config, storage)\n        QDialog.__init__(self, None)\n        self.setWindowTitle('Bitcoin Private Electrum  -  ' + _('Install Wizard'))\n        self.app = app\n        self.config = config\n        # Set for base base class\n        self.plugins = plugins\n        self.language_for_seed = config.get('language')\n        self.setMinimumSize(600, 400)\n        self.accept_signal.connect(self.accept)\n        self.title = QLabel()\n        self.main_widget = QWidget()\n        self.back_button = QPushButton(_(\"Back\"), self)\n        self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))\n        self.next_button = QPushButton(_(\"Next\"), self)\n        self.next_button.setDefault(True)\n        self.logo = QLabel()\n        self.please_wait = QLabel(_(\"Please wait...\"))\n        self.please_wait.setAlignment(Qt.AlignCenter)\n        self.icon_filename = None\n        self.loop = QEventLoop()\n        self.rejected.connect(lambda: self.loop.exit(0))\n        self.back_button.clicked.connect(lambda: self.loop.exit(1))\n        self.next_button.clicked.connect(lambda: self.loop.exit(2))\n        outer_vbox = QVBoxLayout(self)\n        inner_vbox = QVBoxLayout()\n        inner_vbox.addWidget(self.title)\n        inner_vbox.addWidget(self.main_widget)\n        inner_vbox.addStretch(1)\n        inner_vbox.addWidget(self.please_wait)\n        inner_vbox.addStretch(1)\n        scroll_widget = QWidget()\n        scroll_widget.setLayout(inner_vbox)\n        scroll = QScrollArea()\n        scroll.setWidget(scroll_widget)\n        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)\n        scroll.setWidgetResizable(True)\n        icon_vbox = QVBoxLayout()\n        icon_vbox.addWidget(self.logo)\n        icon_vbox.addStretch(1)\n        hbox = QHBoxLayout()\n        hbox.addLayout(icon_vbox)\n        hbox.addSpacing(5)\n        hbox.addWidget(scroll)\n        hbox.setStretchFactor(scroll, 1)\n        outer_vbox.addLayout(hbox)\n        outer_vbox.addLayout(Buttons(self.back_button, self.next_button))\n        self.set_icon(':icons/electrum.png')\n        self.show()\n        self.raise_()\n        self.refresh_gui()  # Need for QT on MacOSX.  Lame.\n\n    def run_and_get_wallet(self):\n\n        vbox = QVBoxLayout()\n        hbox = QHBoxLayout()\n        hbox.addWidget(QLabel(_('Wallet') + ':'))\n        self.name_e = QLineEdit()\n        hbox.addWidget(self.name_e)\n        button = QPushButton(_('Choose...'))\n        hbox.addWidget(button)\n        vbox.addLayout(hbox)\n\n        self.msg_label = QLabel('')\n        vbox.addWidget(self.msg_label)\n        hbox2 = QHBoxLayout()\n        self.pw_e = QLineEdit('', self)\n        self.pw_e.setFixedWidth(150)\n        self.pw_e.setEchoMode(2)\n        self.pw_label = QLabel(_('Password') + ':')\n        hbox2.addWidget(self.pw_label)\n        hbox2.addWidget(self.pw_e)\n        hbox2.addStretch()\n        vbox.addLayout(hbox2)\n        self.set_layout(vbox, title=_('Bitcoin Private Electrum Wallet'))\n\n        wallet_folder = os.path.dirname(self.storage.path)\n\n        def on_choose():\n            path, __ = QFileDialog.getOpenFileName(self, \"Select your wallet file\", wallet_folder)\n            if path:\n                self.name_e.setText(path)\n\n        def on_filename(filename):\n            path = os.path.join(wallet_folder, filename)\n            try:\n                self.storage = WalletStorage(path, manual_upgrades=True)\n                self.next_button.setEnabled(True)\n            except IOError:\n                self.storage = None\n                self.next_button.setEnabled(False)\n            if self.storage:\n                if not self.storage.file_exists():\n                    msg =_(\"This file does not exist.\") + '\\n' \\\n                          + _(\"Press 'Next' to create this wallet, or choose another file.\")\n                    pw = False\n                elif self.storage.file_exists() and self.storage.is_encrypted():\n                    msg = _(\"This file is encrypted.\") + '\\n' + _('Enter your password or choose another file.')\n                    pw = True\n                else:\n                    msg = _(\"Press 'Next' to open this wallet.\")\n                    pw = False\n            else:\n                msg = _('Cannot read file')\n                pw = False\n            self.msg_label.setText(msg)\n            if pw:\n                self.pw_label.show()\n                self.pw_e.show()\n                self.pw_e.setFocus()\n            else:\n                self.pw_label.hide()\n                self.pw_e.hide()\n\n        button.clicked.connect(on_choose)\n        self.name_e.textChanged.connect(on_filename)\n        n = os.path.basename(self.storage.path)\n        self.name_e.setText(n)\n\n        while True:\n            if self.storage.file_exists() and not self.storage.is_encrypted():\n                break\n            if self.loop.exec_() != 2:  # 2 = next\n                return\n            if not self.storage.file_exists():\n                break\n            if self.storage.file_exists() and self.storage.is_encrypted():\n                password = self.pw_e.text()\n                try:\n                    self.storage.decrypt(password)\n                    break\n                except InvalidPassword as e:\n                    QMessageBox.information(None, _('Error'), str(e))\n                    continue\n                except BaseException as e:\n                    traceback.print_exc(file=sys.stdout)\n                    QMessageBox.information(None, _('Error'), str(e))\n                    return\n\n        path = self.storage.path\n        if self.storage.requires_split():\n            self.hide()\n            msg = _(\"The wallet '%s' contains multiple accounts, which are no longer supported since Electrum 2.7.\\n\\n\"\n                    \"Do you want to split your wallet into multiple files?\"%path)\n            if not self.question(msg):\n                return\n            file_list = '\\n'.join(self.storage.split_accounts())\n            msg = _('Your accounts have been moved to') + ':\\n' + file_list + '\\n\\n'+ _('Do you want to delete the old file') + ':\\n' + path\n            if self.question(msg):\n                os.remove(path)\n                self.show_warning(_('The file was removed'))\n            return\n\n        if self.storage.requires_upgrade():\n            self.storage.upgrade()\n            self.wallet = Wallet(self.storage)\n            return self.wallet\n\n        action = self.storage.get_action()\n        if action and action != 'new':\n            self.hide()\n            msg = _(\"The file '%s' contains an incompletely created wallet.\\n\"\n                    \"Do you want to complete its creation now?\") % path\n            if not self.question(msg):\n                if self.question(_(\"Do you want to delete '%s'?\") % path):\n                    os.remove(path)\n                    self.show_warning(_('The file was removed'))\n                return\n            self.show()\n        if action:\n            # self.wallet is set in run\n            self.run(action)\n            return self.wallet\n\n        self.wallet = Wallet(self.storage)\n        return self.wallet\n\n\n\n    def finished(self):\n        \"\"\"Called in hardware client wrapper, in order to close popups.\"\"\"\n        return\n\n    def on_error(self, exc_info):\n        if not isinstance(exc_info[1], UserCancelled):\n            traceback.print_exception(*exc_info)\n            self.show_error(str(exc_info[1]))\n\n    def set_icon(self, filename):\n        prior_filename, self.icon_filename = self.icon_filename, filename\n        self.logo.setPixmap(QPixmap(filename).scaledToWidth(60))\n        return prior_filename\n\n    def set_layout(self, layout, title=None, next_enabled=True):\n        self.title.setText(\"<b>%s</b>\"%title if title else \"\")\n        self.title.setVisible(bool(title))\n        # Get rid of any prior layout by assigning it to a temporary widget\n        prior_layout = self.main_widget.layout()\n        if prior_layout:\n            QWidget().setLayout(prior_layout)\n        self.main_widget.setLayout(layout)\n        self.back_button.setEnabled(True)\n        self.next_button.setEnabled(next_enabled)\n        if next_enabled:\n            self.next_button.setFocus()\n        self.main_widget.setVisible(True)\n        self.please_wait.setVisible(False)\n\n    def exec_layout(self, layout, title=None, raise_on_cancel=True,\n                        next_enabled=True):\n        self.set_layout(layout, title, next_enabled)\n        result = self.loop.exec_()\n        if not result and raise_on_cancel:\n            raise UserCancelled\n        if result == 1:\n            raise GoBack\n        self.title.setVisible(False)\n        self.back_button.setEnabled(False)\n        self.next_button.setEnabled(False)\n        self.main_widget.setVisible(False)\n        self.please_wait.setVisible(True)\n        self.refresh_gui()\n        return result\n\n    def refresh_gui(self):\n        # For some reason, to refresh the GUI this needs to be called twice\n        self.app.processEvents()\n        self.app.processEvents()\n\n    def remove_from_recently_open(self, filename):\n        self.config.remove_from_recently_open(filename)\n\n    def text_input(self, title, message, is_valid, allow_multi=False):\n        slayout = KeysLayout(parent=self, title=message, is_valid=is_valid,\n                             allow_multi=allow_multi)\n        self.exec_layout(slayout, title, next_enabled=False)\n        return slayout.get_text()\n\n    def seed_input(self, title, message, is_seed, options):\n        slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self)\n        self.exec_layout(slayout, title, next_enabled=False)\n        return slayout.get_seed(), slayout.is_bip39, slayout.is_ext\n\n    @wizard_dialog\n    def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False):\n        return self.text_input(title, message, is_valid, allow_multi)\n\n    @wizard_dialog\n    def add_cosigner_dialog(self, run_next, index, is_valid):\n        title = _(\"Add Cosigner\") + \" %d\"%index\n        message = ' '.join([\n            _('Please enter the master public key (xpub) of your cosigner.'),\n            _('Enter their master private key (xprv) if you want to be able to sign for them.')\n        ])\n        return self.text_input(title, message, is_valid)\n\n    @wizard_dialog\n    def restore_seed_dialog(self, run_next, test):\n        options = []\n        if self.opt_ext:\n            options.append('ext')\n        if self.opt_bip39:\n            options.append('bip39')\n        title = _('Enter Seed')\n        message = _('Please enter your seed phrase in order to restore your wallet.')\n        return self.seed_input(title, message, test, options)\n\n    @wizard_dialog\n    def confirm_seed_dialog(self, run_next, test):\n        self.app.clipboard().clear()\n        title = _('Confirm Seed')\n        message = ' '.join([\n            _('Your seed is important!'),\n            _('If you lose your seed, your money will be permanently lost.'),\n            _('To make sure that you have properly saved your seed, please retype it here.')\n        ])\n        seed, is_bip39, is_ext = self.seed_input(title, message, test, None)\n        return seed\n\n    @wizard_dialog\n    def show_seed_dialog(self, run_next, seed_text):\n        title =  _(\"Your wallet generation seed is:\")\n        slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext'])\n        self.exec_layout(slayout)\n        return slayout.is_ext\n\n    def pw_layout(self, msg, kind):\n        playout = PasswordLayout(None, msg, kind, self.next_button)\n        playout.encrypt_cb.setChecked(True)\n        self.exec_layout(playout.layout())\n        return playout.new_password(), playout.encrypt_cb.isChecked()\n\n    @wizard_dialog\n    def request_password(self, run_next):\n        \"\"\"Request the user enter a new password and confirm it.  Return\n        the password or None for no password.\"\"\"\n        return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW)\n\n    def show_restore(self, wallet, network):\n        # FIXME: these messages are shown after the install wizard is\n        # finished and the window closed.  On MacOSX they appear parented\n        # with a re-appeared ghost install wizard window...\n        if network:\n            def task():\n                wallet.wait_until_synchronized()\n                if wallet.is_found():\n                    msg = _(\"Recovery successful\")\n                else:\n                    msg = _(\"No transactions found for this seed\")\n                self.synchronized_signal.emit(msg)\n            self.synchronized_signal.connect(self.show_message)\n            t = threading.Thread(target = task)\n            t.daemon = True\n            t.start()\n        else:\n            msg = _(\"This wallet was restored offline. It may \"\n                    \"contain more addresses than displayed.\")\n            self.show_message(msg)\n\n    @wizard_dialog\n    def confirm_dialog(self, title, message, run_next):\n        self.confirm(message, title)\n\n    def confirm(self, message, title):\n        label = WWLabel(message)\n        vbox = QVBoxLayout()\n        vbox.addWidget(label)\n        self.exec_layout(vbox, title)\n\n    @wizard_dialog\n    def action_dialog(self, action, run_next):\n        self.run(action)\n\n    def terminate(self):\n        self.accept_signal.emit()\n\n    def waiting_dialog(self, task, msg):\n        self.please_wait.setText(MSG_GENERATING_WAIT)\n        self.refresh_gui()\n        t = threading.Thread(target = task)\n        t.start()\n        t.join()\n\n    @wizard_dialog\n    def choice_dialog(self, title, message, choices, run_next):\n        c_values = [x[0] for x in choices]\n        c_titles = [x[1] for x in choices]\n        clayout = ChoicesLayout(message, c_titles)\n        vbox = QVBoxLayout()\n        vbox.addLayout(clayout.layout())\n        self.exec_layout(vbox, title)\n        action = c_values[clayout.selected_index()]\n        return action\n\n    def query_choice(self, msg, choices):\n        \"\"\"called by hardware wallets\"\"\"\n        clayout = ChoicesLayout(msg, choices)\n        vbox = QVBoxLayout()\n        vbox.addLayout(clayout.layout())\n        self.exec_layout(vbox, '')\n        return clayout.selected_index()\n\n    @wizard_dialog\n    def line_dialog(self, run_next, title, message, default, test, warning='',\n                    presets=()):\n        vbox = QVBoxLayout()\n        vbox.addWidget(WWLabel(message))\n        line = QLineEdit()\n        line.setText(default)\n        def f(text):\n            self.next_button.setEnabled(test(text))\n        line.textEdited.connect(f)\n        vbox.addWidget(line)\n        vbox.addWidget(WWLabel(warning))\n\n        for preset in presets:\n            button = QPushButton(preset[0])\n            button.clicked.connect(lambda __, text=preset[1]: line.setText(text))\n            button.setMaximumWidth(150)\n            hbox = QHBoxLayout()\n            hbox.addWidget(button, Qt.AlignCenter)\n            vbox.addLayout(hbox)\n\n        self.exec_layout(vbox, title, next_enabled=test(default))\n        return ' '.join(line.text().split())\n\n    @wizard_dialog\n    def show_xpub_dialog(self, xpub, run_next):\n        msg = ' '.join([\n            _(\"Here is your master public key.\"),\n            _(\"Please share it with your cosigners.\")\n        ])\n        vbox = QVBoxLayout()\n        layout = SeedLayout(xpub, title=msg, icon=False)\n        vbox.addLayout(layout.layout())\n        self.exec_layout(vbox, _('Master Public Key'))\n        return None\n\n    def init_network(self, network):\n        message = _(\"Electrum communicates with remote servers to get \"\n                  \"information about your transactions and addresses. The \"\n                  \"servers all fulfill the same purpose only differing in \"\n                  \"hardware. In most cases you simply want to let Electrum \"\n                  \"pick one at random.  However if you prefer feel free to \"\n                  \"select a server manually.\")\n        choices = [_(\"Auto connect\"), _(\"Select server manually\")]\n        title = _(\"How do you want to connect to a server? \")\n        clayout = ChoicesLayout(message, choices)\n        self.back_button.setText(_('Cancel'))\n        self.exec_layout(clayout.layout(), title)\n        r = clayout.selected_index()\n        if r == 1:\n            nlayout = NetworkChoiceLayout(network, self.config, wizard=True)\n            if self.exec_layout(nlayout.layout()):\n                nlayout.accept()\n        else:\n            network.auto_connect = True\n            self.config.set_key('auto_connect', True, True)\n\n    @wizard_dialog\n    def multisig_dialog(self, run_next):\n        cw = CosignWidget(2, 2)\n        m_edit = QSlider(Qt.Horizontal, self)\n        n_edit = QSlider(Qt.Horizontal, self)\n        n_edit.setMinimum(2)\n        n_edit.setMaximum(15)\n        m_edit.setMinimum(1)\n        m_edit.setMaximum(2)\n        n_edit.setValue(2)\n        m_edit.setValue(2)\n        n_label = QLabel()\n        m_label = QLabel()\n        grid = QGridLayout()\n        grid.addWidget(n_label, 0, 0)\n        grid.addWidget(n_edit, 0, 1)\n        grid.addWidget(m_label, 1, 0)\n        grid.addWidget(m_edit, 1, 1)\n        def on_m(m):\n            m_label.setText(_('Require {0} signatures').format(m))\n            cw.set_m(m)\n        def on_n(n):\n            n_label.setText(_('From {0} cosigners').format(n))\n            cw.set_n(n)\n            m_edit.setMaximum(n)\n        n_edit.valueChanged.connect(on_n)\n        m_edit.valueChanged.connect(on_m)\n        on_n(2)\n        on_m(2)\n        vbox = QVBoxLayout()\n        vbox.addWidget(cw)\n        vbox.addWidget(WWLabel(_(\"Choose the number of signatures needed to unlock funds in your wallet:\")))\n        vbox.addLayout(grid)\n        self.exec_layout(vbox, _(\"Multi-Signature Wallet\"))\n        m = int(m_edit.value())\n        n = int(n_edit.value())\n        return (m, n)\n"
  },
  {
    "path": "gui/qt/invoice_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom .util import *\nfrom electrum.i18n import _\nfrom electrum.util import format_time\n\n\nclass InvoiceList(MyTreeWidget):\n    filter_columns = [0, 1, 2, 3]  # Date, Requested By, Description, Amount\n\n    def __init__(self, parent):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Expires'), _('Requested By'), _('Description'), _('Amount'), _('Status')], 2)\n        self.setSortingEnabled(True)\n        self.header().setSectionResizeMode(1, QHeaderView.Interactive)\n        self.setColumnWidth(1, 200)\n\n    def on_update(self):\n        inv_list = self.parent.invoices.unpaid_invoices()\n        self.clear()\n        for pr in inv_list:\n            key = pr.get_id()\n            status = self.parent.invoices.get_status(key)\n            requestor = pr.get_requestor()\n            exp = pr.get_expiration_date()\n            date_str = format_time(exp) if exp else _('Never')\n            item = QTreeWidgetItem([date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')])\n            item.setIcon(4, QIcon(pr_icons.get(status)))\n            item.setData(0, Qt.UserRole, key)\n            item.setFont(1, QFont(MONOSPACE_FONT))\n            item.setFont(3, QFont(MONOSPACE_FONT))\n            self.addTopLevelItem(item)\n        self.setCurrentItem(self.topLevelItem(0))\n        self.setVisible(len(inv_list))\n        self.parent.invoices_label.setVisible(len(inv_list))\n\n    def import_invoices(self):\n        wallet_folder = self.parent.get_wallet_folder()\n        filename, __ = QFileDialog.getOpenFileName(self.parent, \"Select your wallet file\", wallet_folder)\n        if not filename:\n            return\n        self.parent.invoices.import_file(filename)\n        self.on_update()\n\n    def create_menu(self, position):\n        menu = QMenu()\n        item = self.itemAt(position)\n        if not item:\n            return\n        key = item.data(0, Qt.UserRole)\n        column = self.currentColumn()\n        column_title = self.headerItem().text(column)\n        column_data = item.text(column)\n        pr = self.parent.invoices.get(key)\n        status = self.parent.invoices.get_status(key)\n        if column_data:\n            menu.addAction(_(\"Copy %s\")%column_title, lambda: self.parent.app.clipboard().setText(column_data))\n        menu.addAction(_(\"Details\"), lambda: self.parent.show_invoice(key))\n        if status == PR_UNPAID:\n            menu.addAction(_(\"Pay Now\"), lambda: self.parent.do_pay_invoice(key))\n        menu.addAction(_(\"Delete\"), lambda: self.parent.delete_invoice(key))\n        menu.exec_(self.viewport().mapToGlobal(position))\n"
  },
  {
    "path": "gui/qt/main_window.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport sys, time, threading\nimport os, json, traceback\nimport shutil\nimport weakref\nimport webbrowser\nimport csv\nfrom decimal import Decimal\nimport base64\nfrom functools import partial\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\n\nfrom electrum.util import bh2u, bfh\n\nfrom electrum import keystore, simple_config\nfrom electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants\nfrom electrum.plugins import run_hook\nfrom electrum.i18n import _\nfrom electrum.util import (format_time, format_satoshis, PrintError,\n                           format_satoshis_plain, NotEnoughFunds,\n                           UserCancelled, NoDynamicFeeEstimates)\nfrom electrum import Transaction\nfrom electrum import util, bitcoin, commands, coinchooser\nfrom electrum import paymentrequest\nfrom electrum.wallet import Multisig_Wallet\ntry:\n    from electrum.plot import plot_history\nexcept:\n    plot_history = None\n\nfrom .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit\nfrom .qrcodewidget import QRCodeWidget, QRDialog\nfrom .qrtextedit import ShowQRTextEdit, ScanQRTextEdit\nfrom .transaction_dialog import show_transaction\nfrom .fee_slider import FeeSlider\n\nfrom .util import *\n\n\nclass StatusBarButton(QPushButton):\n    def __init__(self, icon, tooltip, func):\n        QPushButton.__init__(self, icon, '')\n        self.setToolTip(tooltip)\n        self.setFlat(True)\n        self.setMaximumWidth(25)\n        self.clicked.connect(self.onPress)\n        self.func = func\n        self.setIconSize(QSize(25,25))\n\n    def onPress(self, checked=False):\n        '''Drops the unwanted PyQt5 \"checked\" argument'''\n        self.func()\n\n    def keyPressEvent(self, e):\n        if e.key() == Qt.Key_Return:\n            self.func()\n\n\nfrom electrum.paymentrequest import PR_PAID\n\n\nclass ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):\n\n    payment_request_ok_signal = pyqtSignal()\n    payment_request_error_signal = pyqtSignal()\n    notify_transactions_signal = pyqtSignal()\n    new_fx_quotes_signal = pyqtSignal()\n    new_fx_history_signal = pyqtSignal()\n    network_signal = pyqtSignal(str, object)\n    alias_received_signal = pyqtSignal()\n    computing_privkeys_signal = pyqtSignal()\n    show_privkeys_signal = pyqtSignal()\n\n    def __init__(self, gui_object, wallet):\n        QMainWindow.__init__(self)\n\n        self.gui_object = gui_object\n        self.config = config = gui_object.config\n        self.network = gui_object.daemon.network\n        self.fx = gui_object.daemon.fx\n        self.invoices = wallet.invoices\n        self.contacts = wallet.contacts\n        self.tray = gui_object.tray\n        self.app = gui_object.app\n        self.cleaned_up = False\n        self.is_max = False\n        self.payment_request = None\n        self.checking_accounts = False\n        self.qr_window = None\n        self.not_enough_funds = False\n        self.pluginsdialog = None\n        self.require_fee_update = False\n        self.tx_notifications = []\n        self.tl_windows = []\n        self.tx_external_keypairs = {}\n\n        self.create_status_bar()\n        self.need_update = threading.Event()\n\n        self.decimal_point = config.get('decimal_point', 8)\n        self.fee_unit = config.get('fee_unit', 0)\n        self.num_zeros = int(config.get('num_zeros', 0))\n\n        self.completions = QStringListModel()\n\n        self.tabs = tabs = QTabWidget(self)\n        self.send_tab = self.create_send_tab()\n        self.receive_tab = self.create_receive_tab()\n        self.addresses_tab = self.create_addresses_tab()\n        self.utxo_tab = self.create_utxo_tab()\n        self.console_tab = self.create_console_tab()\n        self.contacts_tab = self.create_contacts_tab()\n        tabs.addTab(self.create_history_tab(), QIcon(\":icons/tab_history.png\"), _('History'))\n        tabs.addTab(self.send_tab, QIcon(\":icons/tab_send.png\"), _('Send'))\n        tabs.addTab(self.receive_tab, QIcon(\":icons/tab_receive.png\"), _('Receive'))\n\n        def add_optional_tab(tabs, tab, icon, description, name):\n            tab.tab_icon = icon\n            tab.tab_description = description\n            tab.tab_pos = len(tabs)\n            tab.tab_name = name\n            if self.config.get('show_{}_tab'.format(name), False):\n                tabs.addTab(tab, icon, description.replace(\"&\", \"\"))\n\n        add_optional_tab(tabs, self.addresses_tab, QIcon(\":icons/tab_addresses.png\"), _(\"&Addresses\"), \"addresses\")\n        add_optional_tab(tabs, self.utxo_tab, QIcon(\":icons/tab_coins.png\"), _(\"Co&ins\"), \"utxo\")\n        add_optional_tab(tabs, self.contacts_tab, QIcon(\":icons/tab_contacts.png\"), _(\"Con&tacts\"), \"contacts\")\n        add_optional_tab(tabs, self.console_tab, QIcon(\":icons/tab_console.png\"), _(\"Con&sole\"), \"console\")\n\n        tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)\n        self.setCentralWidget(tabs)\n\n        if self.config.get(\"is_maximized\"):\n            self.showMaximized()\n\n        self.setWindowIcon(QIcon(\":icons/electrum.png\"))\n        self.init_menubar()\n\n        wrtabs = weakref.proxy(tabs)\n        QShortcut(QKeySequence(\"Ctrl+W\"), self, self.close)\n        QShortcut(QKeySequence(\"Ctrl+Q\"), self, self.close)\n        QShortcut(QKeySequence(\"Ctrl+R\"), self, self.update_wallet)\n        QShortcut(QKeySequence(\"Ctrl+PgUp\"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() - 1)%wrtabs.count()))\n        QShortcut(QKeySequence(\"Ctrl+PgDown\"), self, lambda: wrtabs.setCurrentIndex((wrtabs.currentIndex() + 1)%wrtabs.count()))\n\n        for i in range(wrtabs.count()):\n            QShortcut(QKeySequence(\"Alt+\" + str(i + 1)), self, lambda i=i: wrtabs.setCurrentIndex(i))\n\n        self.payment_request_ok_signal.connect(self.payment_request_ok)\n        self.payment_request_error_signal.connect(self.payment_request_error)\n        self.notify_transactions_signal.connect(self.notify_transactions)\n        self.history_list.setFocus(True)\n\n        # network callbacks\n        if self.network:\n            self.network_signal.connect(self.on_network_qt)\n            interests = ['updated', 'new_transaction', 'status',\n                         'banner', 'verified', 'fee']\n            # To avoid leaking references to \"self\" that prevent the\n            # window from being GC-ed when closed, callbacks should be\n            # methods of this class only, and specifically not be\n            # partials, lambdas or methods of subobjects.  Hence...\n            self.network.register_callback(self.on_network, interests)\n            # set initial message\n            self.console.showMessage(self.network.banner)\n            self.network.register_callback(self.on_quotes, ['on_quotes'])\n            self.network.register_callback(self.on_history, ['on_history'])\n            self.new_fx_quotes_signal.connect(self.on_fx_quotes)\n            self.new_fx_history_signal.connect(self.on_fx_history)\n\n        # update fee slider in case we missed the callback\n        self.fee_slider.update()\n        self.load_wallet(wallet)\n        self.connect_slots(gui_object.timer)\n        self.fetch_alias()\n\n    def on_history(self, b):\n        self.new_fx_history_signal.emit()\n\n    def on_fx_history(self):\n        self.history_list.refresh_headers()\n        self.history_list.update()\n        self.address_list.update()\n\n    def on_quotes(self, b):\n        self.new_fx_quotes_signal.emit()\n\n    def on_fx_quotes(self):\n        self.update_status()\n        # Refresh edits with the new rate\n        edit = self.fiat_send_e if self.fiat_send_e.is_last_edited else self.amount_e\n        edit.textEdited.emit(edit.text())\n        edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e\n        edit.textEdited.emit(edit.text())\n        # History tab needs updating if it used spot\n        if self.fx.history_used_spot:\n            self.history_list.update()\n\n    def toggle_tab(self, tab):\n        show = not self.config.get('show_{}_tab'.format(tab.tab_name), False)\n        self.config.set_key('show_{}_tab'.format(tab.tab_name), show)\n        item_text = (_(\"Hide\") if show else _(\"Show\")) + \" \" + tab.tab_description\n        tab.menu_action.setText(item_text)\n        if show:\n            # Find out where to place the tab\n            index = len(self.tabs)\n            for i in range(len(self.tabs)):\n                try:\n                    if tab.tab_pos < self.tabs.widget(i).tab_pos:\n                        index = i\n                        break\n                except AttributeError:\n                    pass\n            self.tabs.insertTab(index, tab, tab.tab_icon, tab.tab_description.replace(\"&\", \"\"))\n        else:\n            i = self.tabs.indexOf(tab)\n            self.tabs.removeTab(i)\n\n    def push_top_level_window(self, window):\n        '''Used for e.g. tx dialog box to ensure new dialogs are appropriately\n        parented.  This used to be done by explicitly providing the parent\n        window, but that isn't something hardware wallet prompts know.'''\n        self.tl_windows.append(window)\n\n    def pop_top_level_window(self, window):\n        self.tl_windows.remove(window)\n\n    def top_level_window(self):\n        '''Do the right thing in the presence of tx dialog windows'''\n        override = self.tl_windows[-1] if self.tl_windows else None\n        return self.top_level_window_recurse(override)\n\n    def diagnostic_name(self):\n        return \"%s/%s\" % (PrintError.diagnostic_name(self),\n                          self.wallet.basename() if self.wallet else \"None\")\n\n    def is_hidden(self):\n        return self.isMinimized() or self.isHidden()\n\n    def show_or_hide(self):\n        if self.is_hidden():\n            self.bring_to_top()\n        else:\n            self.hide()\n\n    def bring_to_top(self):\n        self.show()\n        self.raise_()\n\n    def on_error(self, exc_info):\n        if not isinstance(exc_info[1], UserCancelled):\n            traceback.print_exception(*exc_info)\n            self.show_error(str(exc_info[1]))\n\n    def on_network(self, event, *args):\n        if event == 'updated':\n            self.need_update.set()\n            self.gui_object.network_updated_signal_obj.network_updated_signal \\\n                .emit(event, args)\n\n        elif event == 'new_transaction':\n            self.tx_notifications.append(args[0])\n            self.notify_transactions_signal.emit()\n        elif event in ['status', 'banner', 'verified', 'fee']:\n            # Handle in GUI thread\n            self.network_signal.emit(event, args)\n        else:\n            self.print_error(\"unexpected network message:\", event, args)\n\n    def on_network_qt(self, event, args=None):\n        # Handle a network message in the GUI thread\n        if event == 'status':\n            self.update_status()\n        elif event == 'banner':\n            self.console.showMessage(args[0])\n        elif event == 'verified':\n            self.history_list.update_item(*args)\n        elif event == 'fee':\n            if self.config.is_dynfee():\n                self.fee_slider.update()\n                self.do_update_fee()\n        else:\n            self.print_error(\"unexpected network_qt signal:\", event, args)\n\n    def fetch_alias(self):\n        self.alias_info = None\n        alias = self.config.get('alias')\n        if alias:\n            alias = str(alias)\n            def f():\n                self.alias_info = self.contacts.resolve_openalias(alias)\n                self.alias_received_signal.emit()\n            t = threading.Thread(target=f)\n            t.setDaemon(True)\n            t.start()\n\n    def close_wallet(self):\n        if self.wallet:\n            self.print_error('close_wallet', self.wallet.storage.path)\n        run_hook('close_wallet', self.wallet)\n\n    def load_wallet(self, wallet):\n        wallet.thread = TaskThread(self, self.on_error)\n        self.wallet = wallet\n        self.update_recently_visited(wallet.storage.path)\n        # address used to create a dummy transaction and estimate transaction fee\n        self.history_list.update()\n        self.address_list.update()\n        self.utxo_list.update()\n        self.need_update.set()\n        # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized\n        self.notify_transactions()\n        # update menus\n        self.seed_menu.setEnabled(self.wallet.has_seed())\n        self.update_lock_icon()\n        self.update_buttons_on_seed()\n        self.update_console()\n        self.clear_receive_tab()\n        self.request_list.update()\n        self.tabs.show()\n        self.init_geometry()\n        if self.config.get('hide_gui') and self.gui_object.tray.isVisible():\n            self.hide()\n        else:\n            self.show()\n        self.watching_only_changed()\n        run_hook('load_wallet', wallet, self)\n\n    def init_geometry(self):\n        winpos = self.wallet.storage.get(\"winpos-qt\")\n        try:\n            screen = self.app.desktop().screenGeometry()\n            assert screen.contains(QRect(*winpos))\n            self.setGeometry(*winpos)\n        except:\n            self.print_error(\"using default geometry\")\n            self.setGeometry(100, 100, 840, 400)\n\n    def watching_only_changed(self):\n        name = \"[TESTNET] Bitcoin Private Electrum\" if NetworkConstants.TESTNET else \"Bitcoin Private Electrum\"\n        title = '%s %s  -  %s' % (name, self.wallet.electrum_version,\n                                        self.wallet.basename())\n        extra = [self.wallet.storage.get('wallet_type', '?')]\n        if self.wallet.is_watching_only():\n            self.warn_if_watching_only()\n            extra.append(_('watching only'))\n        title += '  [%s]'% ', '.join(extra)\n        self.setWindowTitle(title)\n        self.password_menu.setEnabled(self.wallet.can_change_password())\n        self.import_privkey_menu.setVisible(self.wallet.can_import_privkey())\n        self.import_address_menu.setVisible(self.wallet.can_import_address())\n        self.export_menu.setEnabled(self.wallet.can_export())\n\n    def warn_if_watching_only(self):\n        if self.wallet.is_watching_only():\n            msg = ' '.join([\n                _(\"This wallet is watching-only.\"),\n                _(\"This means you will not be able to spend BTCP with it.\"),\n                _(\"Make sure you own the seed phrase or the private keys, before you request BTCP to be sent to this wallet.\")\n            ])\n            self.show_warning(msg, title=_('Information'))\n\n    def open_wallet(self):\n        wallet_folder = self.get_wallet_folder()\n        filename, __ = QFileDialog.getOpenFileName(self, \"Select your wallet file\", wallet_folder)\n        if not filename:\n            return\n        self.gui_object.new_window(filename)\n\n\n    def backup_wallet(self):\n        path = self.wallet.storage.path\n        wallet_folder = os.path.dirname(path)\n        filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder)\n        if not filename:\n            return\n\n        new_path = os.path.join(wallet_folder, filename)\n        if new_path != path:\n            try:\n                shutil.copy2(path, new_path)\n                self.show_message(_(\"A copy of your wallet file was created in\")+\" '%s'\" % str(new_path), title=_(\"Wallet backup created\"))\n            except (IOError, os.error) as reason:\n                self.show_critical(_(\"Electrum was unable to copy your wallet file to the specified location.\") + \"\\n\" + str(reason), title=_(\"Unable to create backup\"))\n\n    def update_recently_visited(self, filename):\n        recent = self.config.get('recently_open', [])\n        try:\n            sorted(recent)\n        except:\n            recent = []\n        if filename in recent:\n            recent.remove(filename)\n        recent.insert(0, filename)\n        recent = recent[:5]\n        self.config.set_key('recently_open', recent)\n        self.recently_visited_menu.clear()\n        for i, k in enumerate(sorted(recent)):\n            b = os.path.basename(k)\n            def loader(k):\n                return lambda: self.gui_object.new_window(k)\n            self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence(\"Ctrl+%d\"%(i+1)))\n        self.recently_visited_menu.setEnabled(len(recent))\n\n    def get_wallet_folder(self):\n        return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))\n\n    def new_wallet(self):\n        wallet_folder = self.get_wallet_folder()\n        i = 1\n        while True:\n            filename = \"wallet_%d\" % i\n            if filename in os.listdir(wallet_folder):\n                i += 1\n            else:\n                break\n        full_path = os.path.join(wallet_folder, filename)\n        self.gui_object.start_new_window(full_path, None)\n\n    def init_menubar(self):\n        menubar = QMenuBar()\n\n        file_menu = menubar.addMenu(_(\"&File\"))\n        self.recently_visited_menu = file_menu.addMenu(_(\"&Recently Opened\"))\n        file_menu.addAction(_(\"&Open\"), self.open_wallet).setShortcut(QKeySequence.Open)\n        file_menu.addAction(_(\"&New/Restore\"), self.new_wallet).setShortcut(QKeySequence.New)\n        file_menu.addAction(_(\"&Backup Wallet\"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)\n        file_menu.addAction(_(\"Delete\"), self.remove_wallet)\n        file_menu.addSeparator()\n        file_menu.addAction(_(\"&Quit\"), self.close)\n\n        wallet_menu = menubar.addMenu(_(\"&Wallet\"))\n        wallet_menu.addAction(_(\"&Information\"), self.show_master_public_keys)\n        wallet_menu.addSeparator()\n        self.password_menu = wallet_menu.addAction(_(\"&Password\"), self.change_password_dialog)\n        self.seed_menu = wallet_menu.addAction(_(\"&Seed\"), self.show_seed_dialog)\n        self.private_keys_menu = wallet_menu.addMenu(_(\"&Private Keys\"))\n        self.private_keys_menu.addAction(_(\"&Sweep\"), self.sweep_key_dialog)\n        self.import_privkey_menu = self.private_keys_menu.addAction(_(\"&Import\"), self.do_import_privkey)\n        self.export_menu = self.private_keys_menu.addAction(_(\"&Export\"), self.export_privkeys_dialog)\n        self.import_address_menu = wallet_menu.addAction(_(\"Import Addresses\"), self.import_addresses)\n        wallet_menu.addSeparator()\n\n        labels_menu = wallet_menu.addMenu(_(\"&Labels\"))\n        labels_menu.addAction(_(\"&Import\"), self.do_import_labels)\n        labels_menu.addAction(_(\"&Export\"), self.do_export_labels)\n        contacts_menu = wallet_menu.addMenu(_(\"Contacts\"))\n        contacts_menu.addAction(_(\"&New\"), self.new_contact_dialog)\n        contacts_menu.addAction(_(\"Import\"), lambda: self.contact_list.import_contacts())\n        invoices_menu = wallet_menu.addMenu(_(\"Invoices\"))\n        invoices_menu.addAction(_(\"Import\"), lambda: self.invoice_list.import_invoices())\n        hist_menu = wallet_menu.addMenu(_(\"&History\"))\n        hist_menu.addAction(\"Plot\", self.plot_history_dialog).setEnabled(plot_history is not None)\n        hist_menu.addAction(\"Export\", self.export_history_dialog)\n\n        wallet_menu.addSeparator()\n        wallet_menu.addAction(_(\"Find\"), self.toggle_search).setShortcut(QKeySequence(\"Ctrl+F\"))\n\n        def add_toggle_action(view_menu, tab):\n            is_shown = self.config.get('show_{}_tab'.format(tab.tab_name), False)\n            item_name = (_(\"Hide\") if is_shown else _(\"Show\")) + \" \" + tab.tab_description\n            tab.menu_action = view_menu.addAction(item_name, lambda: self.toggle_tab(tab))\n\n        view_menu = menubar.addMenu(_(\"&View\"))\n        add_toggle_action(view_menu, self.addresses_tab)\n        add_toggle_action(view_menu, self.utxo_tab)\n        add_toggle_action(view_menu, self.contacts_tab)\n        add_toggle_action(view_menu, self.console_tab)\n\n        tools_menu = menubar.addMenu(_(\"&Tools\"))\n\n        # Settings / Preferences are all reserved keywords in OSX using this as work around\n        tools_menu.addAction(_(\"Electrum Preferences\") if sys.platform == 'darwin' else _(\"Preferences\"), self.settings_dialog)\n        tools_menu.addAction(_(\"&Network\"), lambda: self.gui_object.show_network_dialog(self))\n        tools_menu.addAction(_(\"&Plugins\"), self.plugins_dialog)\n        tools_menu.addSeparator()\n        tools_menu.addAction(_(\"&Sign/Verify Message\"), self.sign_verify_message)\n        tools_menu.addAction(_(\"&Encrypt/Decrypt Message\"), self.encrypt_message)\n        tools_menu.addSeparator()\n\n        paytomany_menu = tools_menu.addAction(_(\"&Pay Multiple Addresses\"), self.paytomany)\n\n        raw_transaction_menu = tools_menu.addMenu(_(\"&Load Transaction\"))\n        raw_transaction_menu.addAction(_(\"&From File\"), self.do_process_from_file)\n        raw_transaction_menu.addAction(_(\"&From Text\"), self.do_process_from_text)\n        raw_transaction_menu.addAction(_(\"&From the Blockchain\"), self.do_process_from_txid)\n        raw_transaction_menu.addAction(_(\"&From QR Code\"), self.read_tx_from_qrcode)\n        self.raw_transaction_menu = raw_transaction_menu\n        run_hook('init_menubar_tools', self, tools_menu)\n\n        help_menu = menubar.addMenu(_(\"&Help\"))\n        help_menu.addAction(_(\"&About\"), self.show_about)\n        help_menu.addAction(_(\"&Official website\"), lambda: webbrowser.open(\"https://btcprivate.org\"))\n        help_menu.addSeparator()\n        help_menu.addAction(_(\"&Documentation\"), lambda: webbrowser.open(\"https://github.com/z-classic/zclassic/wiki\")).setShortcut(QKeySequence.HelpContents)\n        help_menu.addAction(_(\"&Report Bug\"), self.show_report_bug)\n        help_menu.addSeparator()\n        help_menu.addAction(_(\"&Donate to server\"), self.donate_to_server)\n\n        self.setMenuBar(menubar)\n\n    def donate_to_server(self):\n        d = self.network.get_donation_address()\n        if d:\n            host = self.network.get_parameters()[0]\n            self.pay_to_URI('bitcoin:%s?message=donation for %s'%(d, host))\n        else:\n            self.show_error(_('No donation address for this server'))\n\n    def show_about(self):\n        QMessageBox.about(self, \"Electrum BTCP\",\n            _(\"Version\")+\" %s\" % (self.wallet.electrum_version) + \"\\n\\n\" +\n                _(\"Electrum BTCP's focus is speed, with low resource usage and simplifying Bitcoin Private. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin Private system.\"  + \"\\n\\n\" +\n                _(\"Uses icons from the Icons8 icon pack (icons8.com).\")))\n\n    def show_report_bug(self):\n        msg = ' '.join([\n            _(\"Please report any bugs as issues on github:<br/>\"),\n            \"<a href=\\\"https://github.com/BTCPrivate/electrum-btcp/issues\\\">https://github.com/BTCPrivate/electrum-btcp/issues</a><br/><br/>\",\n            _(\"Before reporting a bug, upgrade to the most recent version of Electrum-ZCL (latest release or git HEAD), and include the version number in your report.\"),\n            _(\"Try to explain not only what the bug is, but how it occurs.\")\n         ])\n        self.show_message(msg, title=\"Electrum BTCP - \" + _(\"Reporting Bugs\"))\n\n    def notify_transactions(self):\n        if not self.network or not self.network.is_connected():\n            return\n        self.print_error(\"Notifying GUI\")\n        if len(self.tx_notifications) > 0:\n            # Combine the transactions if there are more then three\n            tx_amount = len(self.tx_notifications)\n            if(tx_amount >= 3):\n                total_amount = 0\n                for tx in self.tx_notifications:\n                    is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)\n                    if(v > 0):\n                        total_amount += v\n                self.notify(_(\"%(txs)s new transactions received: Total amount received in the new transactions %(amount)s\") \\\n                            % { 'txs' : tx_amount, 'amount' : self.format_amount_and_units(total_amount)})\n                self.tx_notifications = []\n            else:\n              for tx in self.tx_notifications:\n                  if tx:\n                      self.tx_notifications.remove(tx)\n                      is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)\n                      if(v > 0):\n                          self.notify(_(\"Inbound Transaction - %(amount)s\") % { 'amount' : self.format_amount_and_units(v)})\n\n    def notify(self, message):\n        if self.tray:\n            try:\n                # this requires Qt 5.9\n                self.tray.showMessage(\"Electrum BTCP\", message, QIcon(\":icons/electrum_dark_icon\"), 20000)\n            except TypeError:\n                self.tray.showMessage(\"Electrum BTCP\", message, QSystemTrayIcon.Information, 20000)\n\n\n\n    # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user\n    def getOpenFileName(self, title, filter = \"\"):\n        directory = self.config.get('io_dir', os.path.expanduser('~'))\n        fileName, __ = QFileDialog.getOpenFileName(self, title, directory, filter)\n        if fileName and directory != os.path.dirname(fileName):\n            self.config.set_key('io_dir', os.path.dirname(fileName), True)\n        return fileName\n\n    def getSaveFileName(self, title, filename, filter = \"\"):\n        directory = self.config.get('io_dir', os.path.expanduser('~'))\n        path = os.path.join( directory, filename )\n        fileName, __ = QFileDialog.getSaveFileName(self, title, path, filter)\n        if fileName and directory != os.path.dirname(fileName):\n            self.config.set_key('io_dir', os.path.dirname(fileName), True)\n        return fileName\n\n    def connect_slots(self, sender):\n        sender.timer_signal.connect(self.timer_actions)\n\n    def timer_actions(self):\n        # Note this runs in the GUI thread\n        if self.need_update.is_set():\n            self.need_update.clear()\n            self.update_wallet()\n        # resolve aliases\n        # FIXME this is a blocking network call that has a timeout of 5 sec\n        self.payto_e.resolve()\n        # update fee\n        if self.require_fee_update:\n            self.do_update_fee()\n            self.require_fee_update = False\n\n    def format_amount(self, x, is_diff=False, whitespaces=False):\n        return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)\n\n    def format_amount_and_units(self, amount):\n        text = self.format_amount(amount) + ' '+ self.base_unit()\n        x = self.fx.format_amount_and_units(amount)\n        if text and x:\n            text += ' (%s)'%x\n        return text\n\n    def format_fee_rate(self, fee_rate):\n        if self.fee_unit == 0:\n            return format_satoshis(fee_rate/1000, False, self.num_zeros, 0, False)  + ' sat/byte'\n        else:\n            return self.format_amount(fee_rate) + ' ' + self.base_unit() + '/kB'\n\n    def get_decimal_point(self):\n        return self.decimal_point\n\n    def base_unit(self):\n        assert self.decimal_point in [2, 5, 8]\n        if self.decimal_point == 2:\n            return 'bits'\n        if self.decimal_point == 5:\n            return 'mBTCP'\n        if self.decimal_point == 8:\n            return 'BTCP'\n        raise Exception('Unknown base unit')\n\n    def connect_fields(self, window, btc_e, fiat_e, fee_e):\n\n        def edit_changed(edit):\n            if edit.follows:\n                return\n            edit.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())\n            fiat_e.is_last_edited = (edit == fiat_e)\n            amount = edit.get_amount()\n            rate = self.fx.exchange_rate() if self.fx else None\n            if rate is None or amount is None:\n                if edit is fiat_e:\n                    btc_e.setText(\"\")\n                    if fee_e:\n                        fee_e.setText(\"\")\n                else:\n                    fiat_e.setText(\"\")\n            else:\n                if edit is fiat_e:\n                    btc_e.follows = True\n                    btc_e.setAmount(int(amount / Decimal(rate) * COIN))\n                    btc_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())\n                    btc_e.follows = False\n                    if fee_e:\n                        window.update_fee()\n                else:\n                    fiat_e.follows = True\n                    fiat_e.setText(self.fx.ccy_amount_str(\n                        amount * Decimal(rate) / COIN, False))\n                    fiat_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())\n                    fiat_e.follows = False\n\n        btc_e.follows = False\n        fiat_e.follows = False\n        fiat_e.textChanged.connect(partial(edit_changed, fiat_e))\n        btc_e.textChanged.connect(partial(edit_changed, btc_e))\n        fiat_e.is_last_edited = False\n\n    def update_status(self):\n        if not self.wallet:\n            return\n\n        if self.network is None or not self.network.is_running():\n            text = _(\"Offline\")\n            icon = QIcon(\":icons/status_disconnected.png\")\n\n        elif self.network.is_connected():\n            server_height = self.network.get_server_height()\n            server_lag = self.network.get_local_height() - server_height\n            # Server height can be 0 after switching to a new server\n            # until we get a headers subscription request response.\n            # Display the synchronizing message in that case.\n            if not self.wallet.up_to_date or server_height == 0:\n                text = _(\"Synchronizing...\")\n                icon = QIcon(\":icons/status_waiting.png\")\n            elif server_lag > 1:\n                text = _(\"Server is lagging (%d blocks)\"%server_lag)\n                icon = QIcon(\":icons/status_lagging.png\")\n            else:\n                c, u, x = self.wallet.get_balance()\n                text =  _(\"Balance\" ) + \": %s \"%(self.format_amount_and_units(c))\n                if u:\n                    text +=  \" [%s unconfirmed]\"%(self.format_amount(u, True).strip())\n                if x:\n                    text +=  \" [%s unmatured]\"%(self.format_amount(x, True).strip())\n\n                # append fiat balance and price\n                if self.fx.is_enabled():\n                    text += self.fx.get_fiat_status_text(c + u + x,\n                        self.base_unit(), self.get_decimal_point()) or ''\n                if not self.network.proxy:\n                    icon = QIcon(\":icons/status_connected.png\")\n                else:\n                    icon = QIcon(\":icons/status_connected_proxy.png\")\n        else:\n            text = _(\"Not connected\")\n            icon = QIcon(\":icons/status_disconnected.png\")\n\n        self.tray.setToolTip(\"%s (%s)\" % (text, self.wallet.basename()))\n        self.balance_label.setText(text)\n        self.status_button.setIcon( icon )\n\n\n    def update_wallet(self):\n        self.update_status()\n        if self.wallet.up_to_date or not self.network or not self.network.is_connected():\n            self.update_tabs()\n\n    def update_tabs(self):\n        self.history_list.update()\n        self.request_list.update()\n        self.address_list.update()\n        self.utxo_list.update()\n        self.contact_list.update()\n        self.invoice_list.update()\n        self.update_completions()\n\n    def create_history_tab(self):\n        from .history_list import HistoryList\n        self.history_list = l = HistoryList(self)\n        l.searchable_list = l\n        return l\n\n    def show_address(self, addr):\n        from . import address_dialog\n        d = address_dialog.AddressDialog(self, addr)\n        d.exec_()\n\n    def show_transaction(self, tx, tx_desc = None):\n        '''tx_desc is set only for txs created in the Send tab'''\n        show_transaction(tx, self, tx_desc)\n\n    def create_receive_tab(self):\n        # A 4-column grid layout.  All the stretch is in the last column.\n        # The exchange rate plugin adds a fiat widget in column 2\n        self.receive_grid = grid = QGridLayout()\n        grid.setSpacing(8)\n        grid.setColumnStretch(3, 1)\n\n        self.receive_address_e = ButtonsLineEdit()\n        self.receive_address_e.addCopyButton(self.app)\n        self.receive_address_e.setReadOnly(True)\n        msg = _('BTCP address where the payment should be received. Note that each payment request uses a different BTCP address.')\n        self.receive_address_label = HelpLabel(_('Receiving address'), msg)\n        self.receive_address_e.textChanged.connect(self.update_receive_qr)\n        self.receive_address_e.setFocusPolicy(Qt.NoFocus)\n        grid.addWidget(self.receive_address_label, 0, 0)\n        grid.addWidget(self.receive_address_e, 0, 1, 1, -1)\n\n        self.receive_message_e = QLineEdit()\n        grid.addWidget(QLabel(_('Description')), 1, 0)\n        grid.addWidget(self.receive_message_e, 1, 1, 1, -1)\n        self.receive_message_e.textChanged.connect(self.update_receive_qr)\n\n        self.receive_amount_e = BTCAmountEdit(self.get_decimal_point)\n        grid.addWidget(QLabel(_('Requested amount')), 2, 0)\n        grid.addWidget(self.receive_amount_e, 2, 1)\n        self.receive_amount_e.textChanged.connect(self.update_receive_qr)\n\n        self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')\n        if not self.fx or not self.fx.is_enabled():\n            self.fiat_receive_e.setVisible(False)\n        grid.addWidget(self.fiat_receive_e, 2, 2, Qt.AlignLeft)\n        self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)\n\n        self.expires_combo = QComboBox()\n        self.expires_combo.addItems([i[0] for i in expiration_values])\n        self.expires_combo.setCurrentIndex(3)\n        self.expires_combo.setFixedWidth(self.receive_amount_e.width())\n        msg = ' '.join([\n            _('Expiration date of your request.'),\n            _('This information is seen by the recipient if you send them a signed payment request.'),\n            _('Expired requests have to be deleted manually from your list, in order to free the corresponding BTCP addresses.'),\n            _('The bitcoin address never expires and will always be part of this electrum wallet.'),\n        ])\n        grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)\n        grid.addWidget(self.expires_combo, 3, 1)\n        self.expires_label = QLineEdit('')\n        self.expires_label.setReadOnly(1)\n        self.expires_label.setFocusPolicy(Qt.NoFocus)\n        self.expires_label.hide()\n        grid.addWidget(self.expires_label, 3, 1)\n\n        self.save_request_button = QPushButton(_('Save'))\n        self.save_request_button.clicked.connect(self.save_payment_request)\n\n        self.new_request_button = QPushButton(_('New'))\n        self.new_request_button.clicked.connect(self.new_payment_request)\n\n        self.receive_qr = QRCodeWidget(fixedSize=200)\n        self.receive_qr.mouseReleaseEvent = lambda x: self.toggle_qr_window()\n        self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))\n        self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))\n\n        self.receive_buttons = buttons = QHBoxLayout()\n        buttons.addStretch(1)\n        buttons.addWidget(self.save_request_button)\n        buttons.addWidget(self.new_request_button)\n        grid.addLayout(buttons, 4, 1, 1, 2)\n\n        self.receive_requests_label = QLabel(_('Requests'))\n\n        from .request_list import RequestList\n        self.request_list = RequestList(self)\n\n        # layout\n        vbox_g = QVBoxLayout()\n        vbox_g.addLayout(grid)\n        vbox_g.addStretch()\n\n        hbox = QHBoxLayout()\n        hbox.addLayout(vbox_g)\n        hbox.addWidget(self.receive_qr)\n\n        w = QWidget()\n        w.searchable_list = self.request_list\n        vbox = QVBoxLayout(w)\n        vbox.addLayout(hbox)\n        vbox.addStretch(1)\n        vbox.addWidget(self.receive_requests_label)\n        vbox.addWidget(self.request_list)\n        vbox.setStretchFactor(self.request_list, 1000)\n\n        return w\n\n\n    def delete_payment_request(self, addr):\n        self.wallet.remove_payment_request(addr, self.config)\n        self.request_list.update()\n        self.clear_receive_tab()\n\n    def get_request_URI(self, addr):\n        req = self.wallet.receive_requests[addr]\n        message = self.wallet.labels.get(addr, '')\n        amount = req['amount']\n        URI = util.create_URI(addr, amount, message)\n        if req.get('time'):\n            URI += \"&time=%d\"%req.get('time')\n        if req.get('exp'):\n            URI += \"&exp=%d\"%req.get('exp')\n        if req.get('name') and req.get('sig'):\n            sig = bfh(req.get('sig'))\n            sig = bitcoin.base_encode(sig, base=58)\n            URI += \"&name=\" + req['name'] + \"&sig=\"+sig\n        return str(URI)\n\n\n    def sign_payment_request(self, addr):\n        alias = self.config.get('alias')\n        alias_privkey = None\n        if alias and self.alias_info:\n            alias_addr, alias_name, validated = self.alias_info\n            if alias_addr:\n                if self.wallet.is_mine(alias_addr):\n                    msg = _('This payment request will be signed.') + '\\n' + _('Please enter your password')\n                    password = self.password_dialog(msg)\n                    if password:\n                        try:\n                            self.wallet.sign_payment_request(addr, alias, alias_addr, password)\n                        except Exception as e:\n                            self.show_error(str(e))\n                            return\n                    else:\n                        return\n                else:\n                    return\n\n    def save_payment_request(self):\n        addr = str(self.receive_address_e.text())\n        amount = self.receive_amount_e.get_amount()\n        message = self.receive_message_e.text()\n        if not message and not amount:\n            self.show_error(_('No message or amount'))\n            return False\n        i = self.expires_combo.currentIndex()\n        expiration = list(map(lambda x: x[1], expiration_values))[i]\n        req = self.wallet.make_payment_request(addr, amount, message, expiration)\n        self.wallet.add_payment_request(req, self.config)\n        self.sign_payment_request(addr)\n        self.request_list.update()\n        self.address_list.update()\n        self.save_request_button.setEnabled(False)\n\n    def view_and_paste(self, title, msg, data):\n        dialog = WindowModalDialog(self, title)\n        vbox = QVBoxLayout()\n        label = QLabel(msg)\n        label.setWordWrap(True)\n        vbox.addWidget(label)\n        pr_e = ShowQRTextEdit(text=data)\n        vbox.addWidget(pr_e)\n        vbox.addLayout(Buttons(CopyCloseButton(pr_e.text, self.app, dialog)))\n        dialog.setLayout(vbox)\n        dialog.exec_()\n\n    def export_payment_request(self, addr):\n        r = self.wallet.receive_requests.get(addr)\n        pr = paymentrequest.serialize_request(r).SerializeToString()\n        name = r['id'] + '.bip70'\n        fileName = self.getSaveFileName(_(\"Select where to save your payment request\"), name, \"*.bip70\")\n        if fileName:\n            with open(fileName, \"wb+\") as f:\n                f.write(util.to_bytes(pr))\n            self.show_message(_(\"Request saved successfully\"))\n            self.saved = True\n\n    def new_payment_request(self):\n        addr = self.wallet.get_unused_address()\n        if addr is None:\n            if not self.wallet.is_deterministic():\n                msg = [\n                    _('No more addresses in your wallet.'),\n                    _('You are using a non-deterministic wallet, which cannot create new addresses.'),\n                    _('If you want to create new addresses, use a deterministic wallet instead.')\n                   ]\n                self.show_message(' '.join(msg))\n                return\n            if not self.question(_(\"Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\\n\\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\\n\\nCreate anyway?\")):\n                return\n            addr = self.wallet.create_new_address(False)\n        self.set_receive_address(addr)\n        self.expires_label.hide()\n        self.expires_combo.show()\n        self.new_request_button.setEnabled(False)\n        self.receive_message_e.setFocus(1)\n\n    def set_receive_address(self, addr):\n        self.receive_address_e.setText(addr)\n        self.receive_message_e.setText('')\n        self.receive_amount_e.setAmount(None)\n\n    def clear_receive_tab(self):\n        addr = self.wallet.get_receiving_address() or ''\n        self.receive_address_e.setText(addr)\n        self.receive_message_e.setText('')\n        self.receive_amount_e.setAmount(None)\n        self.expires_label.hide()\n        self.expires_combo.show()\n\n    def toggle_qr_window(self):\n        from . import qrwindow\n        if not self.qr_window:\n            self.qr_window = qrwindow.QR_Window(self)\n            self.qr_window.setVisible(True)\n            self.qr_window_geometry = self.qr_window.geometry()\n        else:\n            if not self.qr_window.isVisible():\n                self.qr_window.setVisible(True)\n                self.qr_window.setGeometry(self.qr_window_geometry)\n            else:\n                self.qr_window_geometry = self.qr_window.geometry()\n                self.qr_window.setVisible(False)\n        self.update_receive_qr()\n\n    def show_send_tab(self):\n        self.tabs.setCurrentIndex(self.tabs.indexOf(self.send_tab))\n\n    def show_receive_tab(self):\n        self.tabs.setCurrentIndex(self.tabs.indexOf(self.receive_tab))\n\n    def receive_at(self, addr):\n        if not bitcoin.is_address(addr):\n            return\n        self.show_receive_tab()\n        self.receive_address_e.setText(addr)\n        self.new_request_button.setEnabled(True)\n\n    def update_receive_qr(self):\n        addr = str(self.receive_address_e.text())\n        amount = self.receive_amount_e.get_amount()\n        message = self.receive_message_e.text()\n        self.save_request_button.setEnabled((amount is not None) or (message != \"\"))\n        uri = util.create_URI(addr, amount, message)\n        self.receive_qr.setData(uri)\n        if self.qr_window and self.qr_window.isVisible():\n            self.qr_window.set_content(addr, amount, message, uri)\n\n    def create_send_tab(self):\n        # A 4-column grid layout.  All the stretch is in the last column.\n        # The exchange rate plugin adds a fiat widget in column 2\n        self.send_grid = grid = QGridLayout()\n        grid.setSpacing(8)\n        grid.setColumnStretch(3, 1)\n\n        from .paytoedit import PayToEdit\n        self.amount_e = BTCAmountEdit(self.get_decimal_point)\n        self.payto_e = PayToEdit(self)\n        msg = _('Recipient of the funds.') + '\\n\\n'\\\n              + _('You may enter a BTCP address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a BTCP address)')\n        payto_label = HelpLabel(_('Pay to'), msg)\n        grid.addWidget(payto_label, 1, 0)\n        grid.addWidget(self.payto_e, 1, 1, 1, -1)\n\n        completer = QCompleter()\n        completer.setCaseSensitivity(False)\n        self.payto_e.setCompleter(completer)\n        completer.setModel(self.completions)\n\n        msg = _('Description of the transaction (not mandatory).') + '\\n\\n'\\\n              + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \\'History\\' tab.')\n        description_label = HelpLabel(_('Description'), msg)\n        grid.addWidget(description_label, 2, 0)\n        self.message_e = MyLineEdit()\n        grid.addWidget(self.message_e, 2, 1, 1, -1)\n\n        self.from_label = QLabel(_('From'))\n        grid.addWidget(self.from_label, 3, 0)\n        self.from_list = MyTreeWidget(self, self.from_list_menu, ['',''])\n        self.from_list.setHeaderHidden(True)\n        self.from_list.setMaximumHeight(80)\n        grid.addWidget(self.from_list, 3, 1, 1, -1)\n        self.set_pay_from([])\n\n        msg = _('Amount to be sent.') + '\\n\\n' \\\n              + _('The amount will be displayed in red if you do not have enough funds in your wallet.') + ' ' \\\n              + _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\\n\\n' \\\n              + _('Keyboard shortcut: type \"!\" to send all your coins.')\n        amount_label = HelpLabel(_('Amount'), msg)\n        grid.addWidget(amount_label, 4, 0)\n        grid.addWidget(self.amount_e, 4, 1)\n\n        self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')\n        if not self.fx or not self.fx.is_enabled():\n            self.fiat_send_e.setVisible(False)\n        grid.addWidget(self.fiat_send_e, 4, 2)\n        self.amount_e.frozen.connect(\n            lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))\n\n        self.max_button = EnterButton(_(\"Max\"), self.spend_max)\n        self.max_button.setFixedWidth(140)\n        grid.addWidget(self.max_button, 4, 3)\n        hbox = QHBoxLayout()\n        hbox.addStretch(1)\n        grid.addLayout(hbox, 4, 4)\n\n        msg = _('BTCP transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\\n\\n'\\\n              + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\\n\\n'\\\n              + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')\n        self.fee_e_label = HelpLabel(_('Fee'), msg)\n\n        def fee_cb(dyn, pos, fee_rate):\n            if dyn:\n                self.config.set_key('fee_level', pos, False)\n            else:\n                self.config.set_key('fee_per_kb', fee_rate, False)\n\n            if fee_rate:\n                self.feerate_e.setAmount(fee_rate // 1000)\n            self.fee_e.setModified(False)\n\n            self.fee_slider.activate()\n            self.spend_max() if self.is_max else self.update_fee()\n\n        self.fee_slider = FeeSlider(self, self.config, fee_cb)\n        self.fee_slider.setFixedWidth(140)\n\n        def on_fee_or_feerate(edit_changed, editing_finished):\n            edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e\n            if editing_finished:\n                if not edit_changed.get_amount():\n                    # This is so that when the user blanks the fee and moves on,\n                    # we go back to auto-calculate mode and put a fee back.\n                    edit_changed.setModified(False)\n            else:\n                # edit_changed was edited just now, so make sure we will\n                # freeze the correct fee setting (this)\n                edit_other.setModified(False)\n            self.fee_slider.deactivate()\n            self.update_fee()\n\n        class TxSizeLabel(QLabel):\n            def setAmount(self, byte_size):\n                self.setText(('x   %s bytes   =' % byte_size) if byte_size else '')\n\n        self.size_e = TxSizeLabel()\n        self.size_e.setAlignment(Qt.AlignCenter)\n        self.size_e.setAmount(0)\n        self.size_e.setFixedWidth(140)\n        self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())\n\n        self.feerate_e = FeerateEdit(lambda: 2 if self.fee_unit else 0)\n        self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False))\n        self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True))\n\n        self.fee_e = BTCAmountEdit(self.get_decimal_point)\n        self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False))\n        self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True))\n\n        self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)\n\n        vbox_feelabel = QVBoxLayout()\n        vbox_feelabel.addWidget(self.fee_e_label)\n        vbox_feelabel.addStretch(1)\n        grid.addLayout(vbox_feelabel, 5, 0)\n\n        self.fee_adv_controls = QWidget()\n        hbox = QHBoxLayout(self.fee_adv_controls)\n        hbox.setContentsMargins(0, 0, 0, 0)\n        hbox.addWidget(self.feerate_e)\n        hbox.addWidget(self.size_e)\n        hbox.addWidget(self.fee_e)\n\n        vbox_feecontrol = QVBoxLayout()\n        vbox_feecontrol.addWidget(self.fee_adv_controls)\n        vbox_feecontrol.addWidget(self.fee_slider)\n\n        grid.addLayout(vbox_feecontrol, 5, 1, 1, 3)\n\n        if not self.config.get('show_fee', True):\n            self.fee_adv_controls.setVisible(False)\n\n        self.preview_button = EnterButton(_(\"Preview\"), self.do_preview)\n        self.preview_button.setToolTip(_('Display the details of your transactions before signing it.'))\n        self.send_button = EnterButton(_(\"Send\"), self.do_send)\n        self.clear_button = EnterButton(_(\"Clear\"), self.do_clear)\n        buttons = QHBoxLayout()\n        buttons.addStretch(1)\n        buttons.addWidget(self.clear_button)\n        buttons.addWidget(self.preview_button)\n        buttons.addWidget(self.send_button)\n        grid.addLayout(buttons, 6, 1, 1, 3)\n\n        self.amount_e.shortcut.connect(self.spend_max)\n        self.payto_e.textChanged.connect(self.update_fee)\n        self.amount_e.textEdited.connect(self.update_fee)\n\n        def reset_max(t):\n            self.is_max = False\n            self.max_button.setEnabled(not bool(t))\n        self.amount_e.textEdited.connect(reset_max)\n        self.fiat_send_e.textEdited.connect(reset_max)\n\n        def entry_changed():\n            text = \"\"\n\n            amt_color = ColorScheme.DEFAULT\n            fee_color = ColorScheme.DEFAULT\n            feerate_color = ColorScheme.DEFAULT\n\n            if self.not_enough_funds:\n                amt_color, fee_color = ColorScheme.RED, ColorScheme.RED\n                feerate_color = ColorScheme.RED\n                text = _( \"Not enough funds\" )\n                c, u, x = self.wallet.get_frozen_balance()\n                if c+u+x:\n                    text += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_(\"are frozen\") + ')'\n\n            # blue color denotes auto-filled values\n            elif self.fee_e.isModified():\n                feerate_color = ColorScheme.BLUE\n            elif self.feerate_e.isModified():\n                fee_color = ColorScheme.BLUE\n            elif self.amount_e.isModified():\n                fee_color = ColorScheme.BLUE\n                feerate_color = ColorScheme.BLUE\n            else:\n                amt_color = ColorScheme.BLUE\n                fee_color = ColorScheme.BLUE\n                feerate_color = ColorScheme.BLUE\n\n            self.statusBar().showMessage(text)\n            self.amount_e.setStyleSheet(amt_color.as_stylesheet())\n            self.fee_e.setStyleSheet(fee_color.as_stylesheet())\n            self.feerate_e.setStyleSheet(feerate_color.as_stylesheet())\n\n        self.amount_e.textChanged.connect(entry_changed)\n        self.fee_e.textChanged.connect(entry_changed)\n        self.feerate_e.textChanged.connect(entry_changed)\n\n        self.invoices_label = QLabel(_('Invoices'))\n        from .invoice_list import InvoiceList\n        self.invoice_list = InvoiceList(self)\n\n        vbox0 = QVBoxLayout()\n        vbox0.addLayout(grid)\n        hbox = QHBoxLayout()\n        hbox.addLayout(vbox0)\n        w = QWidget()\n        vbox = QVBoxLayout(w)\n        vbox.addLayout(hbox)\n        vbox.addStretch(1)\n        vbox.addWidget(self.invoices_label)\n        vbox.addWidget(self.invoice_list)\n        vbox.setStretchFactor(self.invoice_list, 1000)\n        w.searchable_list = self.invoice_list\n        run_hook('create_send_tab', grid)\n        return w\n\n    def spend_max(self):\n        self.is_max = True\n        self.do_update_fee()\n\n    def update_fee(self):\n        self.require_fee_update = True\n\n    def get_payto_or_dummy(self):\n        r = self.payto_e.get_recipient()\n        if r:\n            return r\n        return (TYPE_ADDRESS, self.wallet.dummy_address())\n\n    def do_update_fee(self):\n        '''Recalculate the fee.  If the fee was manually input, retain it, but\n        still build the TX to see if there are enough funds.\n        '''\n        if not self.config.get('offline') and self.config.is_dynfee() and not self.config.has_fee_estimates():\n            self.statusBar().showMessage(_('Waiting for fee estimates...'))\n            return False\n        freeze_fee = self.is_send_fee_frozen()\n        freeze_feerate = self.is_send_feerate_frozen()\n        amount = '!' if self.is_max else self.amount_e.get_amount()\n        if amount is None:\n            if not freeze_fee:\n                self.fee_e.setAmount(None)\n            self.not_enough_funds = False\n            self.statusBar().showMessage('')\n        else:\n            fee_estimator = self.get_send_fee_estimator()\n            outputs = self.payto_e.get_outputs(self.is_max)\n            if not outputs:\n                _type, addr = self.get_payto_or_dummy()\n                outputs = [(_type, addr, amount)]\n            is_sweep = bool(self.tx_external_keypairs)\n            make_tx = lambda fee_est: \\\n                self.wallet.make_unsigned_transaction(\n                    self.get_coins(), outputs, self.config,\n                    fixed_fee=fee_est, is_sweep=is_sweep)\n            try:\n                tx = make_tx(fee_estimator)\n                self.not_enough_funds = False\n            except NotEnoughFunds:\n                self.not_enough_funds = True\n                if not freeze_fee:\n                    self.fee_e.setAmount(None)\n                return\n            except NoDynamicFeeEstimates:\n                tx = make_tx(0)\n                size = tx.estimated_size()\n                self.size_e.setAmount(size)\n                return\n            except BaseException:\n                traceback.print_exc(file=sys.stderr)\n                return\n\n            size = tx.estimated_size()\n            self.size_e.setAmount(size)\n\n            fee = tx.get_fee()\n            if not freeze_fee:\n                fee = None if self.not_enough_funds else fee\n                self.fee_e.setAmount(fee)\n            if not freeze_feerate:\n                fee_rate = fee // size if fee is not None else None\n                self.feerate_e.setAmount(fee_rate)\n\n            if self.is_max:\n                amount = tx.output_value()\n                self.amount_e.setAmount(amount)\n\n    def from_list_delete(self, item):\n        i = self.from_list.indexOfTopLevelItem(item)\n        self.pay_from.pop(i)\n        self.redraw_from_list()\n        self.update_fee()\n\n    def from_list_menu(self, position):\n        item = self.from_list.itemAt(position)\n        menu = QMenu()\n        menu.addAction(_(\"Remove\"), lambda: self.from_list_delete(item))\n        menu.exec_(self.from_list.viewport().mapToGlobal(position))\n\n    def set_pay_from(self, coins):\n        self.pay_from = list(coins)\n        self.redraw_from_list()\n\n    def redraw_from_list(self):\n        self.from_list.clear()\n        self.from_label.setHidden(len(self.pay_from) == 0)\n        self.from_list.setHidden(len(self.pay_from) == 0)\n\n        def format(x):\n            h = x.get('prevout_hash')\n            return h[0:10] + '...' + h[-10:] + \":%d\"%x.get('prevout_n') + u'\\t' + \"%s\"%x.get('address')\n\n        for item in self.pay_from:\n            self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))\n\n    def get_contact_payto(self, key):\n        _type, label = self.contacts.get(key)\n        return label + '  <' + key + '>' if _type == 'address' else key\n\n    def update_completions(self):\n        l = [self.get_contact_payto(key) for key in self.contacts.keys()]\n        self.completions.setStringList(l)\n\n    def protected(func):\n        '''Password request wrapper.  The password is passed to the function\n        as the 'password' named argument.  \"None\" indicates either an\n        unencrypted wallet, or the user cancelled the password request.\n        An empty input is passed as the empty string.'''\n        def request_password(self, *args, **kwargs):\n            parent = self.top_level_window()\n            password = None\n            while self.wallet.has_password():\n                password = self.password_dialog(parent=parent)\n                if password is None:\n                    # User cancelled password input\n                    return\n                try:\n                    self.wallet.check_password(password)\n                    break\n                except Exception as e:\n                    self.show_error(str(e), parent=parent)\n                    continue\n\n            kwargs['password'] = password\n            return func(self, *args, **kwargs)\n        return request_password\n\n    def is_send_fee_frozen(self):\n        return self.fee_e.isVisible() and self.fee_e.isModified() \\\n               and (self.fee_e.text() or self.fee_e.hasFocus())\n\n    def is_send_feerate_frozen(self):\n        return self.feerate_e.isVisible() and self.feerate_e.isModified() \\\n               and (self.feerate_e.text() or self.feerate_e.hasFocus())\n\n    def get_send_fee_estimator(self):\n        if self.is_send_fee_frozen():\n            fee_estimator = self.fee_e.get_amount()\n        elif self.is_send_feerate_frozen():\n            amount = self.feerate_e.get_amount()\n            amount = 0 if amount is None else float(amount)\n            fee_estimator = partial(\n                simple_config.SimpleConfig.estimate_fee_for_feerate, amount)\n        else:\n            fee_estimator = None\n        return fee_estimator\n\n    def read_send_tab(self):\n        if self.payment_request and self.payment_request.has_expired():\n            self.show_error(_('Payment request has expired.'))\n            return\n        label = self.message_e.text()\n\n        if self.payment_request:\n            outputs = self.payment_request.get_outputs()\n        else:\n            errors = self.payto_e.get_errors()\n            if errors:\n                self.show_warning(_(\"Invalid lines found:\") + \"\\n\\n\" + '\\n'.join([ _(\"Line #\") + str(x[0]+1) + \": \" + x[1] for x in errors]))\n                return\n            outputs = self.payto_e.get_outputs(self.is_max)\n\n            if self.payto_e.is_alias and self.payto_e.validated is False:\n                alias = self.payto_e.toPlainText()\n                msg = _('WARNING: the alias \"%s\" could not be validated via an additional security check, DNSSEC, and thus may not be correct.'%alias) + '\\n'\n                msg += _('Do you wish to continue?')\n                if not self.question(msg):\n                    return\n\n        if not outputs:\n            self.show_error(_('No Recipients'))\n            return\n\n        for _type, addr, amount in outputs:\n            if addr is None:\n                self.show_error(_('No BTCP Address'))\n                return\n            if _type == TYPE_ADDRESS and not bitcoin.is_address(addr):\n                self.show_error(_('Invalid BTCP Address'))\n                return\n            if amount is None:\n                self.show_error(_('Invalid Amount'))\n                return\n\n        fee_estimator = self.get_send_fee_estimator()\n        coins = self.get_coins()\n        return outputs, fee_estimator, label, coins\n\n    def do_preview(self):\n        self.do_send(preview = True)\n\n    def do_send(self, preview = False):\n        if run_hook('abort_send', self):\n            return\n        r = self.read_send_tab()\n        if not r:\n            return\n        outputs, fee_estimator, tx_desc, coins = r\n        try:\n            is_sweep = bool(self.tx_external_keypairs)\n            tx = self.wallet.make_unsigned_transaction(\n                coins, outputs, self.config, fixed_fee=fee_estimator,\n                is_sweep=is_sweep)\n        except NotEnoughFunds:\n            self.show_message(_(\"Insufficient funds\"))\n            return\n        except BaseException as e:\n            traceback.print_exc(file=sys.stdout)\n            self.show_message(str(e))\n            return\n\n        amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))\n        fee = tx.get_fee()\n\n        use_rbf = self.config.get('use_rbf', True)\n        if use_rbf:\n            tx.set_rbf(True)\n\n        if fee < self.wallet.relayfee() * tx.estimated_size() / 1000:\n            self.show_error(_(\"This transaction requires a higher fee, or it will not be propagated by the network\"))\n            return\n\n        if preview:\n            self.show_transaction(tx, tx_desc)\n            return\n\n        # confirmation dialog\n        msg = [\n            _(\"Amount to be sent\") + \": \" + self.format_amount_and_units(amount),\n            _(\"Mining fee\") + \": \" + self.format_amount_and_units(fee),\n        ]\n\n        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)\n        if x_fee:\n            x_fee_address, x_fee_amount = x_fee\n            msg.append( _(\"Additional fees\") + \": \" + self.format_amount_and_units(x_fee_amount) )\n\n        confirm_rate = 2 * self.config.max_fee_rate()\n        if fee > confirm_rate * tx.estimated_size() / 1000:\n            msg.append(_('Warning') + ': ' + _(\"The fee for this transaction seems unusually high.\"))\n\n        if self.wallet.has_password():\n            msg.append(\"\")\n            msg.append(_(\"Enter your password to proceed\"))\n            password = self.password_dialog('\\n'.join(msg))\n            if not password:\n                return\n        else:\n            msg.append(_('Proceed?'))\n            password = None\n            if not self.question('\\n'.join(msg)):\n                return\n\n        def sign_done(success):\n            if success:\n                if not tx.is_complete():\n                    self.show_transaction(tx)\n                    self.do_clear()\n                else:\n                    self.broadcast_transaction(tx, tx_desc)\n        self.sign_tx_with_password(tx, sign_done, password)\n\n    @protected\n    def sign_tx(self, tx, callback, password):\n        self.sign_tx_with_password(tx, callback, password)\n\n    def sign_tx_with_password(self, tx, callback, password):\n        '''Sign the transaction in a separate thread.  When done, calls\n        the callback with a success code of True or False.\n        '''\n\n        def on_signed(result):\n            callback(True)\n        def on_failed(exc_info):\n            self.on_error(exc_info)\n            callback(False)\n\n        if self.tx_external_keypairs:\n            # can sign directly\n            task = partial(Transaction.sign, tx, self.tx_external_keypairs)\n        else:\n            # call hook to see if plugin needs gui interaction\n            run_hook('sign_tx', self, tx)\n            task = partial(self.wallet.sign_transaction, tx, password)\n        WaitingDialog(self, _('Signing transaction...'), task,\n                      on_signed, on_failed)\n\n    def broadcast_transaction(self, tx, tx_desc):\n\n        def broadcast_thread():\n            # non-GUI thread\n            pr = self.payment_request\n            if pr and pr.has_expired():\n                self.payment_request = None\n                return False, _(\"Payment request has expired.\")\n            status, msg =  self.network.broadcast(tx)\n            if pr and status is True:\n                self.invoices.set_paid(pr, tx.txid())\n                self.invoices.save()\n                self.payment_request = None\n                refund_address = self.wallet.get_receiving_addresses()[0]\n                ack_status, ack_msg = pr.send_ack(str(tx), refund_address)\n                if ack_status:\n                    msg = ack_msg\n            return status, msg\n\n        # Capture current TL window; override might be removed on return\n        parent = self.top_level_window()\n\n        def broadcast_done(result):\n            # GUI thread\n            if result:\n                status, msg = result\n                if status:\n                    if tx_desc is not None and tx.is_complete():\n                        self.wallet.set_label(tx.txid(), tx_desc)\n                    parent.show_message(_('Payment sent.') + '\\n' + msg)\n                    self.invoice_list.update()\n                    self.do_clear()\n                else:\n                    parent.show_error(msg)\n\n        WaitingDialog(self, _('Broadcasting transaction...'),\n                      broadcast_thread, broadcast_done, self.on_error)\n\n    def query_choice(self, msg, choices):\n        # Needed by QtHandler for hardware wallets\n        dialog = WindowModalDialog(self.top_level_window())\n        clayout = ChoicesLayout(msg, choices)\n        vbox = QVBoxLayout(dialog)\n        vbox.addLayout(clayout.layout())\n        vbox.addLayout(Buttons(OkButton(dialog)))\n        if not dialog.exec_():\n            return None\n        return clayout.selected_index()\n\n    def lock_amount(self, b):\n        self.amount_e.setFrozen(b)\n        self.max_button.setEnabled(not b)\n\n    def prepare_for_payment_request(self):\n        self.show_send_tab()\n        self.payto_e.is_pr = True\n        for e in [self.payto_e, self.amount_e, self.message_e]:\n            e.setFrozen(True)\n        self.payto_e.setText(_(\"please wait...\"))\n        return True\n\n    def delete_invoice(self, key):\n        self.invoices.remove(key)\n        self.invoice_list.update()\n\n    def payment_request_ok(self):\n        pr = self.payment_request\n        key = self.invoices.add(pr)\n        status = self.invoices.get_status(key)\n        self.invoice_list.update()\n        if status == PR_PAID:\n            self.show_message(\"invoice already paid\")\n            self.do_clear()\n            self.payment_request = None\n            return\n        self.payto_e.is_pr = True\n        if not pr.has_expired():\n            self.payto_e.setGreen()\n        else:\n            self.payto_e.setExpired()\n        self.payto_e.setText(pr.get_requestor())\n        self.amount_e.setText(format_satoshis_plain(pr.get_amount(), self.decimal_point))\n        self.message_e.setText(pr.get_memo())\n        # signal to set fee\n        self.amount_e.textEdited.emit(\"\")\n\n    def payment_request_error(self):\n        self.show_message(self.payment_request.error)\n        self.payment_request = None\n        self.do_clear()\n\n    def on_pr(self, request):\n        self.payment_request = request\n        if self.payment_request.verify(self.contacts):\n            self.payment_request_ok_signal.emit()\n        else:\n            self.payment_request_error_signal.emit()\n\n    def pay_to_URI(self, URI):\n        if not URI or not isinstance(URI, str):\n            return\n        try:\n            out = util.parse_URI(URI, self.on_pr)\n        except BaseException as e:\n            self.show_error(_('Invalid bitcoin URI:') + '\\n' + str(e))\n            return\n        self.show_send_tab()\n        r = out.get('r')\n        sig = out.get('sig')\n        name = out.get('name')\n        if r or (name and sig):\n            self.prepare_for_payment_request()\n            return\n        address = out.get('address')\n        amount = out.get('amount')\n        label = out.get('label')\n        message = out.get('message')\n        # use label as description (not BIP21 compliant)\n        if label and not message:\n            message = label\n        if address:\n            self.payto_e.setText(address)\n        if message:\n            self.message_e.setText(message)\n        if amount:\n            self.amount_e.setAmount(amount)\n            self.amount_e.textEdited.emit(\"\")\n\n\n    def do_clear(self):\n        self.is_max = False\n        self.not_enough_funds = False\n        self.payment_request = None\n        self.payto_e.is_pr = False\n        for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,\n                  self.fee_e, self.feerate_e]:\n            e.setText('')\n            e.setFrozen(False)\n        self.fee_slider.activate()\n        self.size_e.setAmount(0)\n        self.set_pay_from([])\n        self.tx_external_keypairs = {}\n        self.update_status()\n        run_hook('do_clear', self)\n\n    def set_frozen_state(self, addrs, freeze):\n        self.wallet.set_frozen_state(addrs, freeze)\n        self.address_list.update()\n        self.utxo_list.update()\n        self.update_fee()\n\n    def create_list_tab(self, l, list_header=None):\n        w = QWidget()\n        w.searchable_list = l\n        vbox = QVBoxLayout()\n        w.setLayout(vbox)\n        vbox.setContentsMargins(0, 0, 0, 0)\n        vbox.setSpacing(0)\n        if list_header:\n            hbox = QHBoxLayout()\n            for b in list_header:\n                hbox.addWidget(b)\n            hbox.addStretch()\n            vbox.addLayout(hbox)\n        vbox.addWidget(l)\n        return w\n\n    def create_addresses_tab(self):\n        from .address_list import AddressList\n        self.address_list = l = AddressList(self)\n        return self.create_list_tab(l, l.get_list_header())\n\n    def create_utxo_tab(self):\n        from .utxo_list import UTXOList\n        self.utxo_list = l = UTXOList(self)\n        return self.create_list_tab(l)\n\n    def create_contacts_tab(self):\n        from .contact_list import ContactList\n        self.contact_list = l = ContactList(self)\n        return self.create_list_tab(l)\n\n    def remove_address(self, addr):\n        if self.question(_(\"Do you want to remove\")+\" %s \"%addr +_(\"from your wallet?\")):\n            self.wallet.delete_address(addr)\n            self.address_list.update()\n            self.history_list.update()\n            self.clear_receive_tab()\n\n    def get_coins(self):\n        if self.pay_from:\n            return self.pay_from\n        else:\n            return self.wallet.get_spendable_coins(None, self.config)\n\n    def spend_coins(self, coins):\n        self.set_pay_from(coins)\n        self.show_send_tab()\n        self.update_fee()\n\n    def paytomany(self):\n        self.show_send_tab()\n        self.payto_e.paytomany()\n        msg = '\\n'.join([\n            _('Enter a list of outputs in the \\'Pay to\\' field.'),\n            _('One output per line.'),\n            _('Format: address, amount'),\n            _('You may load a CSV file using the file icon.')\n        ])\n        self.show_message(msg, title=_('Pay to many'))\n\n    def payto_contacts(self, labels):\n        paytos = [self.get_contact_payto(label) for label in labels]\n        self.show_send_tab()\n        if len(paytos) == 1:\n            self.payto_e.setText(paytos[0])\n            self.amount_e.setFocus()\n        else:\n            text = \"\\n\".join([payto + \", 0\" for payto in paytos])\n            self.payto_e.setText(text)\n            self.payto_e.setFocus()\n\n    def set_contact(self, label, address):\n        if not is_address(address):\n            self.show_error(_('Invalid Address'))\n            self.contact_list.update()  # Displays original unchanged value\n            return False\n        self.contacts[address] = ('address', label)\n        self.contact_list.update()\n        self.history_list.update()\n        self.update_completions()\n        return True\n\n    def delete_contacts(self, labels):\n        if not self.question(_(\"Remove %s from your list of contacts?\")\n                             % \" + \".join(labels)):\n            return\n        for label in labels:\n            self.contacts.pop(label)\n        self.history_list.update()\n        self.contact_list.update()\n        self.update_completions()\n\n    def show_invoice(self, key):\n        pr = self.invoices.get(key)\n        pr.verify(self.contacts)\n        self.show_pr_details(pr)\n\n    def show_pr_details(self, pr):\n        key = pr.get_id()\n        d = WindowModalDialog(self, _(\"Invoice\"))\n        vbox = QVBoxLayout(d)\n        grid = QGridLayout()\n        grid.addWidget(QLabel(_(\"Requested By\") + ':'), 0, 0)\n        grid.addWidget(QLabel(pr.get_requestor()), 0, 1)\n        grid.addWidget(QLabel(_(\"Amount\") + ':'), 1, 0)\n        outputs_str = '\\n'.join(map(lambda x: self.format_amount(x[2])+ self.base_unit() + ' @ ' + x[1], pr.get_outputs()))\n        grid.addWidget(QLabel(outputs_str), 1, 1)\n        expires = pr.get_expiration_date()\n        grid.addWidget(QLabel(_(\"Memo\") + ':'), 2, 0)\n        grid.addWidget(QLabel(pr.get_memo()), 2, 1)\n        grid.addWidget(QLabel(_(\"Signature\") + ':'), 3, 0)\n        grid.addWidget(QLabel(pr.get_verify_status()), 3, 1)\n        if expires:\n            grid.addWidget(QLabel(_(\"Expires\") + ':'), 4, 0)\n            grid.addWidget(QLabel(format_time(expires)), 4, 1)\n        vbox.addLayout(grid)\n        def do_export():\n            fn = self.getSaveFileName(_(\"Save invoice to file\"), \"*.bip70\")\n            if not fn:\n                return\n            with open(fn, 'wb') as f:\n                data = f.write(pr.raw)\n            self.show_message(_('Invoice saved as' + ' ' + fn))\n        exportButton = EnterButton(_('Save'), do_export)\n        def do_delete():\n            if self.question(_('Delete invoice?')):\n                self.invoices.remove(key)\n                self.history_list.update()\n                self.invoice_list.update()\n                d.close()\n        deleteButton = EnterButton(_('Delete'), do_delete)\n        vbox.addLayout(Buttons(exportButton, deleteButton, CloseButton(d)))\n        d.exec_()\n\n    def do_pay_invoice(self, key):\n        pr = self.invoices.get(key)\n        self.payment_request = pr\n        self.prepare_for_payment_request()\n        pr.error = None  # this forces verify() to re-run\n        if pr.verify(self.contacts):\n            self.payment_request_ok()\n        else:\n            self.payment_request_error()\n\n    def create_console_tab(self):\n        from .console import Console\n        self.console = console = Console()\n        return console\n\n    def update_console(self):\n        console = self.console\n        console.history = self.config.get(\"console-history\",[])\n        console.history_index = len(console.history)\n\n        console.updateNamespace({'wallet' : self.wallet,\n                                 'network' : self.network,\n                                 'plugins' : self.gui_object.plugins,\n                                 'window': self})\n        console.updateNamespace({'util' : util, 'bitcoin':bitcoin})\n\n        c = commands.Commands(self.config, self.wallet, self.network, lambda: self.console.set_json(True))\n        methods = {}\n        def mkfunc(f, method):\n            return lambda *args: f(method, args, self.password_dialog)\n        for m in dir(c):\n            if m[0]=='_' or m in ['network','wallet']: continue\n            methods[m] = mkfunc(c._run, m)\n\n        console.updateNamespace(methods)\n\n    def create_status_bar(self):\n\n        sb = QStatusBar()\n        sb.setFixedHeight(35)\n        qtVersion = qVersion()\n\n        self.balance_label = QLabel(\"\")\n        self.balance_label.setTextInteractionFlags(Qt.TextSelectableByMouse)\n        self.balance_label.setStyleSheet(\"\"\"QLabel { padding: 0 }\"\"\")\n        sb.addWidget(self.balance_label)\n\n        self.search_box = QLineEdit()\n        self.search_box.textChanged.connect(self.do_search)\n        self.search_box.hide()\n        sb.addPermanentWidget(self.search_box)\n\n        self.lock_icon = QIcon()\n        self.password_button = StatusBarButton(self.lock_icon, _(\"Password\"), self.change_password_dialog )\n        sb.addPermanentWidget(self.password_button)\n\n        sb.addPermanentWidget(StatusBarButton(QIcon(\":icons/preferences.png\"), _(\"Preferences\"), self.settings_dialog ) )\n        self.seed_button = StatusBarButton(QIcon(\":icons/seed.png\"), _(\"Seed\"), self.show_seed_dialog )\n        sb.addPermanentWidget(self.seed_button)\n        self.status_button = StatusBarButton(QIcon(\":icons/status_disconnected.png\"), _(\"Network\"), lambda: self.gui_object.show_network_dialog(self))\n        sb.addPermanentWidget(self.status_button)\n        run_hook('create_status_bar', sb)\n        self.setStatusBar(sb)\n\n    def update_lock_icon(self):\n        icon = QIcon(\":icons/lock.png\") if self.wallet.has_password() else QIcon(\":icons/unlock.png\")\n        self.password_button.setIcon(icon)\n\n    def update_buttons_on_seed(self):\n        self.seed_button.setVisible(self.wallet.has_seed())\n        self.password_button.setVisible(self.wallet.can_change_password())\n        self.send_button.setVisible(not self.wallet.is_watching_only())\n\n    def change_password_dialog(self):\n        from .password_dialog import ChangePasswordDialog\n        d = ChangePasswordDialog(self, self.wallet)\n        ok, password, new_password, encrypt_file = d.run()\n        if not ok:\n            return\n        try:\n            self.wallet.update_password(password, new_password, encrypt_file)\n        except BaseException as e:\n            self.show_error(str(e))\n            return\n        except:\n            traceback.print_exc(file=sys.stdout)\n            self.show_error(_('Failed to update password'))\n            return\n        msg = _('Password was updated successfully') if new_password else _('Password is disabled, this wallet is not protected')\n        self.show_message(msg, title=_(\"Success\"))\n        self.update_lock_icon()\n\n    def toggle_search(self):\n        self.search_box.setHidden(not self.search_box.isHidden())\n        if not self.search_box.isHidden():\n            self.search_box.setFocus(1)\n        else:\n            self.do_search('')\n\n    def do_search(self, t):\n        tab = self.tabs.currentWidget()\n        if hasattr(tab, 'searchable_list'):\n            tab.searchable_list.filter(t)\n\n    def new_contact_dialog(self):\n        d = WindowModalDialog(self, _(\"New Contact\"))\n        vbox = QVBoxLayout(d)\n        vbox.addWidget(QLabel(_('New Contact') + ':'))\n        grid = QGridLayout()\n        line1 = QLineEdit()\n        line1.setFixedWidth(280)\n        line2 = QLineEdit()\n        line2.setFixedWidth(280)\n        grid.addWidget(QLabel(_(\"Address\")), 1, 0)\n        grid.addWidget(line1, 1, 1)\n        grid.addWidget(QLabel(_(\"Name\")), 2, 0)\n        grid.addWidget(line2, 2, 1)\n        vbox.addLayout(grid)\n        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))\n        if d.exec_():\n            self.set_contact(line2.text(), line1.text())\n\n    def show_master_public_keys(self):\n        dialog = WindowModalDialog(self, _(\"Wallet Information\"))\n        dialog.setMinimumSize(500, 100)\n        mpk_list = self.wallet.get_master_public_keys()\n        vbox = QVBoxLayout()\n        wallet_type = self.wallet.storage.get('wallet_type', '')\n        grid = QGridLayout()\n        basename = os.path.basename(self.wallet.storage.path)\n        grid.addWidget(QLabel(_(\"Wallet name\")+ ':'), 0, 0)\n        grid.addWidget(QLabel(basename), 0, 1)\n        grid.addWidget(QLabel(_(\"Wallet type\")+ ':'), 1, 0)\n        grid.addWidget(QLabel(wallet_type), 1, 1)\n        grid.addWidget(QLabel(_(\"Script type\")+ ':'), 2, 0)\n        grid.addWidget(QLabel(self.wallet.txin_type), 2, 1)\n        vbox.addLayout(grid)\n        if self.wallet.is_deterministic():\n            mpk_text = ShowQRTextEdit()\n            mpk_text.setMaximumHeight(150)\n            mpk_text.addCopyButton(self.app)\n            def show_mpk(index):\n                mpk_text.setText(mpk_list[index])\n            # only show the combobox in case multiple accounts are available\n            if len(mpk_list) > 1:\n                def label(key):\n                    if isinstance(self.wallet, Multisig_Wallet):\n                        return _(\"cosigner\") + ' ' + str(key+1)\n                    return ''\n                labels = [label(i) for i in range(len(mpk_list))]\n                on_click = lambda clayout: show_mpk(clayout.selected_index())\n                labels_clayout = ChoicesLayout(_(\"Master Public Keys\"), labels, on_click)\n                vbox.addLayout(labels_clayout.layout())\n            else:\n                vbox.addWidget(QLabel(_(\"Master Public Key\")))\n            show_mpk(0)\n            vbox.addWidget(mpk_text)\n        vbox.addStretch(1)\n        vbox.addLayout(Buttons(CloseButton(dialog)))\n        dialog.setLayout(vbox)\n        dialog.exec_()\n\n    def remove_wallet(self):\n        if self.question('\\n'.join([\n                _('Delete wallet file?'),\n                \"%s\"%self.wallet.storage.path,\n                _('If your wallet contains funds, make sure you have saved its seed.')])):\n            self._delete_wallet()\n\n    @protected\n    def _delete_wallet(self, password):\n        wallet_path = self.wallet.storage.path\n        basename = os.path.basename(wallet_path)\n        self.gui_object.daemon.stop_wallet(wallet_path)\n        self.close()\n        os.unlink(wallet_path)\n        self.show_error(\"Wallet removed:\" + basename)\n\n    @protected\n    def show_seed_dialog(self, password):\n        if not self.wallet.has_seed():\n            self.show_message(_('This wallet has no seed'))\n            return\n        keystore = self.wallet.get_keystore()\n        try:\n            seed = keystore.get_seed(password)\n            passphrase = keystore.get_passphrase(password)\n        except BaseException as e:\n            self.show_error(str(e))\n            return\n        from .seed_dialog import SeedDialog\n        d = SeedDialog(self, seed, passphrase)\n        d.exec_()\n\n    def show_qrcode(self, data, title = _(\"QR code\"), parent=None):\n        if not data:\n            return\n        d = QRDialog(data, parent or self, title)\n        d.exec_()\n\n    @protected\n    def show_private_key(self, address, password):\n        if not address:\n            return\n        try:\n            pk, redeem_script = self.wallet.export_private_key(address, password)\n        except Exception as e:\n            traceback.print_exc(file=sys.stdout)\n            self.show_message(str(e))\n            return\n        xtype = bitcoin.deserialize_privkey(pk)[0]\n        d = WindowModalDialog(self, _(\"Private key\"))\n        d.setMinimumSize(600, 150)\n        vbox = QVBoxLayout()\n        vbox.addWidget(QLabel(_(\"Address\") + ': ' + address))\n        vbox.addWidget(QLabel(_(\"Script type\") + ': ' + xtype))\n        vbox.addWidget(QLabel(_(\"Private key\") + ':'))\n        keys_e = ShowQRTextEdit(text=pk)\n        keys_e.addCopyButton(self.app)\n        vbox.addWidget(keys_e)\n        if redeem_script:\n            vbox.addWidget(QLabel(_(\"Redeem Script\") + ':'))\n            rds_e = ShowQRTextEdit(text=redeem_script)\n            rds_e.addCopyButton(self.app)\n            vbox.addWidget(rds_e)\n        if xtype in ['p2wpkh', 'p2wsh', 'p2wpkh-p2sh', 'p2wsh-p2sh']:\n            vbox.addWidget(WWLabel(_(\"Warning: the format of private keys associated to segwit addresses may not be compatible with other wallets\")))\n        vbox.addLayout(Buttons(CloseButton(d)))\n        d.setLayout(vbox)\n        d.exec_()\n\n    msg_sign = _(\"Signing with an address actually means signing with the corresponding \"\n                \"private key, and verifying with the corresponding public key. The \"\n                \"address you have entered does not have a unique public key, so these \"\n                \"operations cannot be performed.\") + '\\n\\n' + \\\n               _('The operation is undefined. Not just in Electrum, but in general.')\n\n    @protected\n    def do_sign(self, address, message, signature, password):\n        address  = address.text().strip()\n        message = message.toPlainText().strip()\n        if not bitcoin.is_address(address):\n            self.show_message(_('Invalid BTCP address.'))\n            return\n        if not self.wallet.is_mine(address):\n            self.show_message(_('Address not in wallet.'))\n            return\n        txin_type = self.wallet.get_txin_type(address)\n        if txin_type not in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:\n            self.show_message(_('Cannot sign messages with this type of address:') + \\\n                              ' ' + txin_type + '\\n\\n' + self.msg_sign)\n            return\n        task = partial(self.wallet.sign_message, address, message, password)\n\n        def show_signed_message(sig):\n            signature.setText(base64.b64encode(sig).decode('ascii'))\n        self.wallet.thread.add(task, on_success=show_signed_message)\n\n    def do_verify(self, address, message, signature):\n        address  = address.text().strip()\n        message = message.toPlainText().strip().encode('utf-8')\n        if not bitcoin.is_address(address):\n            self.show_message(_('Invalid BTCP address.'))\n            return\n        try:\n            # This can throw on invalid base64\n            sig = base64.b64decode(str(signature.toPlainText()))\n            verified = bitcoin.verify_message(address, sig, message)\n        except Exception as e:\n            verified = False\n        if verified:\n            self.show_message(_(\"Signature verified\"))\n        else:\n            self.show_error(_(\"Wrong signature\"))\n\n    def sign_verify_message(self, address=''):\n        d = WindowModalDialog(self, _('Sign/verify Message'))\n        d.setMinimumSize(610, 290)\n\n        layout = QGridLayout(d)\n\n        message_e = QTextEdit()\n        layout.addWidget(QLabel(_('Message')), 1, 0)\n        layout.addWidget(message_e, 1, 1)\n        layout.setRowStretch(2,3)\n\n        address_e = QLineEdit()\n        address_e.setText(address)\n        layout.addWidget(QLabel(_('Address')), 2, 0)\n        layout.addWidget(address_e, 2, 1)\n\n        signature_e = QTextEdit()\n        layout.addWidget(QLabel(_('Signature')), 3, 0)\n        layout.addWidget(signature_e, 3, 1)\n        layout.setRowStretch(3,1)\n\n        hbox = QHBoxLayout()\n\n        b = QPushButton(_(\"Sign\"))\n        b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))\n        hbox.addWidget(b)\n\n        b = QPushButton(_(\"Verify\"))\n        b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))\n        hbox.addWidget(b)\n\n        b = QPushButton(_(\"Close\"))\n        b.clicked.connect(d.accept)\n        hbox.addWidget(b)\n        layout.addLayout(hbox, 4, 1)\n        d.exec_()\n\n    @protected\n    def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):\n        cyphertext = encrypted_e.toPlainText()\n        task = partial(self.wallet.decrypt_message, pubkey_e.text(), cyphertext, password)\n        self.wallet.thread.add(task, on_success=lambda text: message_e.setText(text.decode('utf-8')))\n\n    def do_encrypt(self, message_e, pubkey_e, encrypted_e):\n        message = message_e.toPlainText()\n        message = message.encode('utf-8')\n        try:\n            encrypted = bitcoin.encrypt_message(message, pubkey_e.text())\n            encrypted_e.setText(encrypted.decode('ascii'))\n        except BaseException as e:\n            traceback.print_exc(file=sys.stdout)\n            self.show_warning(str(e))\n\n    def encrypt_message(self, address=''):\n        d = WindowModalDialog(self, _('Encrypt/decrypt Message'))\n        d.setMinimumSize(610, 490)\n\n        layout = QGridLayout(d)\n\n        message_e = QTextEdit()\n        layout.addWidget(QLabel(_('Message')), 1, 0)\n        layout.addWidget(message_e, 1, 1)\n        layout.setRowStretch(2,3)\n\n        pubkey_e = QLineEdit()\n        if address:\n            pubkey = self.wallet.get_public_key(address)\n            pubkey_e.setText(pubkey)\n        layout.addWidget(QLabel(_('Public key')), 2, 0)\n        layout.addWidget(pubkey_e, 2, 1)\n\n        encrypted_e = QTextEdit()\n        layout.addWidget(QLabel(_('Encrypted')), 3, 0)\n        layout.addWidget(encrypted_e, 3, 1)\n        layout.setRowStretch(3,1)\n\n        hbox = QHBoxLayout()\n        b = QPushButton(_(\"Encrypt\"))\n        b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))\n        hbox.addWidget(b)\n\n        b = QPushButton(_(\"Decrypt\"))\n        b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))\n        hbox.addWidget(b)\n\n        b = QPushButton(_(\"Close\"))\n        b.clicked.connect(d.accept)\n        hbox.addWidget(b)\n\n        layout.addLayout(hbox, 4, 1)\n        d.exec_()\n\n    def password_dialog(self, msg=None, parent=None):\n        from .password_dialog import PasswordDialog\n        parent = parent or self\n        d = PasswordDialog(parent, msg)\n        return d.run()\n\n    def tx_from_text(self, txt):\n        from electrum.transaction import tx_from_str\n        try:\n            tx = tx_from_str(txt)\n            return Transaction(tx)\n        except BaseException as e:\n            self.show_critical(_(\"Electrum was unable to parse your transaction\") + \":\\n\" + str(e))\n            return\n\n    def read_tx_from_qrcode(self):\n        from electrum import qrscanner\n        try:\n            data = qrscanner.scan_barcode(self.config.get_video_device())\n        except BaseException as e:\n            self.show_error(str(e))\n            return\n        if not data:\n            return\n        # if the user scanned a bitcoin URI\n        if str(data).startswith(\"bitcoin:\"):\n            self.pay_to_URI(data)\n            return\n        # else if the user scanned an offline signed tx\n        data = bh2u(bitcoin.base_decode(data, length=None, base=43))\n        tx = self.tx_from_text(data)\n        if not tx:\n            return\n        self.show_transaction(tx)\n\n    def read_tx_from_file(self):\n        fileName = self.getOpenFileName(_(\"Select your transaction file\"), \"*.txn\")\n        if not fileName:\n            return\n        try:\n            with open(fileName, \"r\") as f:\n                file_content = f.read()\n        except (ValueError, IOError, os.error) as reason:\n            self.show_critical(_(\"Electrum was unable to open your transaction file\") + \"\\n\" + str(reason), title=_(\"Unable to read file or no transaction found\"))\n            return\n        return self.tx_from_text(file_content)\n\n    def do_process_from_text(self):\n        from electrum.transaction import SerializationError\n        text = text_dialog(self, _('Input raw transaction'), _(\"Transaction:\"), _(\"Load transaction\"))\n        if not text:\n            return\n        try:\n            tx = self.tx_from_text(text)\n            if tx:\n                self.show_transaction(tx)\n        except SerializationError as e:\n            self.show_critical(_(\"Electrum was unable to deserialize the transaction:\") + \"\\n\" + str(e))\n\n    def do_process_from_file(self):\n        from electrum.transaction import SerializationError\n        try:\n            tx = self.read_tx_from_file()\n            if tx:\n                self.show_transaction(tx)\n        except SerializationError as e:\n            self.show_critical(_(\"Electrum was unable to deserialize the transaction:\") + \"\\n\" + str(e))\n\n    def do_process_from_txid(self):\n        from electrum import transaction\n        txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')\n        if ok and txid:\n            txid = str(txid).strip()\n            try:\n                r = self.network.synchronous_get(('blockchain.transaction.get',[txid]))\n            except BaseException as e:\n                self.show_message(str(e))\n                return\n            tx = transaction.Transaction(r)\n            self.show_transaction(tx)\n\n    @protected\n    def export_privkeys_dialog(self, password):\n        if self.wallet.is_watching_only():\n            self.show_message(_(\"This is a watching-only wallet\"))\n            return\n\n        if isinstance(self.wallet, Multisig_Wallet):\n            self.show_message(_('WARNING: This is a multi-signature wallet.') + '\\n' +\n                              _('It can not be \"backed up\" by simply exporting these private keys.'))\n\n        d = WindowModalDialog(self, _('Private keys'))\n        d.setMinimumSize(850, 300)\n        vbox = QVBoxLayout(d)\n\n        msg = \"%s\\n%s\\n%s\" % (_(\"WARNING: ALL your private keys are secret.\"),\n                              _(\"Exposing a single private key can compromise your entire wallet!\"),\n                              _(\"In particular, DO NOT use 'redeem private key' services proposed by third parties.\"))\n        vbox.addWidget(QLabel(msg))\n\n        e = QTextEdit()\n        e.setReadOnly(True)\n        vbox.addWidget(e)\n\n        defaultname = 'electrum-private-keys.csv'\n        select_msg = _('Select file to export your private keys to')\n        hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)\n        vbox.addLayout(hbox)\n\n        b = OkButton(d, _('Export'))\n        b.setEnabled(False)\n        vbox.addLayout(Buttons(CancelButton(d), b))\n\n        private_keys = {}\n        addresses = self.wallet.get_addresses()\n        done = False\n        cancelled = False\n        def privkeys_thread():\n            for addr in addresses:\n                time.sleep(0.1)\n                if done or cancelled:\n                    break\n                privkey = self.wallet.export_private_key(addr, password)[0]\n                private_keys[addr] = privkey\n                self.computing_privkeys_signal.emit()\n            if not cancelled:\n                self.computing_privkeys_signal.disconnect()\n                self.show_privkeys_signal.emit()\n\n        def show_privkeys():\n            s = \"\\n\".join( map( lambda x: x[0] + \"\\t\"+ x[1], private_keys.items()))\n            e.setText(s)\n            b.setEnabled(True)\n            self.show_privkeys_signal.disconnect()\n            nonlocal done\n            done = True\n\n        def on_dialog_closed(*args):\n            nonlocal done\n            nonlocal cancelled\n            if not done:\n                cancelled = True\n                self.computing_privkeys_signal.disconnect()\n                self.show_privkeys_signal.disconnect()\n\n        self.computing_privkeys_signal.connect(lambda: e.setText(\"Please wait... %d/%d\"%(len(private_keys),len(addresses))))\n        self.show_privkeys_signal.connect(show_privkeys)\n        d.finished.connect(on_dialog_closed)\n        threading.Thread(target=privkeys_thread).start()\n\n        if not d.exec_():\n            done = True\n            return\n\n        filename = filename_e.text()\n        if not filename:\n            return\n\n        try:\n            self.do_export_privkeys(filename, private_keys, csv_button.isChecked())\n        except (IOError, os.error) as reason:\n            txt = \"\\n\".join([\n                _(\"Electrum was unable to produce a private key-export.\"),\n                str(reason)\n            ])\n            self.show_critical(txt, title=_(\"Unable to create csv\"))\n\n        except Exception as e:\n            self.show_message(str(e))\n            return\n\n        self.show_message(_(\"Private keys exported.\"))\n\n    def do_export_privkeys(self, fileName, pklist, is_csv):\n        with open(fileName, \"w+\") as f:\n            if is_csv:\n                transaction = csv.writer(f)\n                transaction.writerow([\"address\", \"private_key\"])\n                for addr, pk in pklist.items():\n                    transaction.writerow([\"%34s\"%addr,pk])\n            else:\n                import json\n                f.write(json.dumps(pklist, indent = 4))\n\n    def do_import_labels(self):\n        labelsFile = self.getOpenFileName(_(\"Open Labels File\"), \"*.json\")\n        if not labelsFile: return\n        try:\n            with open(labelsFile, 'r') as f:\n                data = f.read()\n            for key, value in json.loads(data).items():\n                self.wallet.set_label(key, value)\n            self.show_message(_(\"Your labels were imported from\") + \" '%s'\" % str(labelsFile))\n        except (IOError, os.error) as reason:\n            self.show_critical(_(\"Electrum was unable to import your labels.\") + \"\\n\" + str(reason))\n        self.address_list.update()\n        self.history_list.update()\n\n    def do_export_labels(self):\n        labels = self.wallet.labels\n        try:\n            fileName = self.getSaveFileName(_(\"Select destination file for your labels:\"), 'electrum_labels.json', \"*.json\")\n            if fileName:\n                with open(fileName, 'w+') as f:\n                    json.dump(labels, f, indent=4, sort_keys=True)\n                self.show_message(_(\"Your labels were exported to\") + \" '%s'\" % str(fileName))\n        except (IOError, os.error) as reason:\n            self.show_critical(_(\"Electrum was unable to export your labels.\") + \"\\n\" + str(reason))\n\n    def export_history_dialog(self):\n        d = WindowModalDialog(self, _('Export History'))\n        d.setMinimumSize(400, 200)\n        vbox = QVBoxLayout(d)\n        defaultname = os.path.expanduser('~/electrum-history.csv')\n        select_msg = _('Select destination file for your wallet transaction history:')\n        hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)\n        vbox.addLayout(hbox)\n        vbox.addStretch(1)\n        hbox = Buttons(CancelButton(d), OkButton(d, _('Export')))\n        vbox.addLayout(hbox)\n        run_hook('export_history_dialog', self, hbox)\n        self.update()\n        if not d.exec_():\n            return\n        filename = filename_e.text()\n        if not filename:\n            return\n        try:\n            self.do_export_history(self.wallet, filename, csv_button.isChecked())\n        except (IOError, os.error) as reason:\n            export_error_label = _(\"Electrum was unable to produce a transaction export.\")\n            self.show_critical(export_error_label + \"\\n\" + str(reason), title=_(\"Unable to export history\"))\n            return\n        self.show_message(_(\"Your wallet history has been successfully exported.\"))\n\n    def plot_history_dialog(self):\n        if plot_history is None:\n            return\n        wallet = self.wallet\n        history = wallet.get_history()\n        if len(history) > 0:\n            plt = plot_history(self.wallet, history)\n            plt.show()\n\n    def do_export_history(self, wallet, fileName, is_csv):\n        history = wallet.get_history()\n        lines = []\n        for item in history:\n            tx_hash, height, confirmations, timestamp, value, balance = item\n            if height>0:\n                if timestamp is not None:\n                    time_string = format_time(timestamp)\n                else:\n                    time_string = _(\"unverified\")\n            else:\n                time_string = _(\"unconfirmed\")\n\n            if value is not None:\n                value_string = format_satoshis(value, True)\n            else:\n                value_string = '--'\n\n            if tx_hash:\n                label = wallet.get_label(tx_hash)\n            else:\n                label = \"\"\n\n            if is_csv:\n                lines.append([tx_hash, label, confirmations, value_string, time_string])\n            else:\n                lines.append({'txid':tx_hash, 'date':\"%16s\"%time_string, 'label':label, 'value':value_string})\n\n        with open(fileName, \"w+\") as f:\n            if is_csv:\n                transaction = csv.writer(f, lineterminator='\\n')\n                transaction.writerow([\"transaction_hash\",\"label\", \"confirmations\", \"value\", \"timestamp\"])\n                for line in lines:\n                    transaction.writerow(line)\n            else:\n                import json\n                f.write(json.dumps(lines, indent = 4))\n\n    def sweep_key_dialog(self):\n        d = WindowModalDialog(self, title=_('Sweep private keys'))\n        d.setMinimumSize(600, 300)\n\n        vbox = QVBoxLayout(d)\n        vbox.addWidget(QLabel(_(\"Enter private keys:\")))\n\n        keys_e = ScanQRTextEdit(allow_multi=True)\n        keys_e.setTabChangesFocus(True)\n        vbox.addWidget(keys_e)\n\n        addresses = self.wallet.get_unused_addresses()\n        if not addresses:\n            try:\n                addresses = self.wallet.get_receiving_addresses()\n            except AttributeError:\n                addresses = self.wallet.get_addresses()\n        h, address_e = address_field(addresses)\n        vbox.addLayout(h)\n\n        vbox.addStretch(1)\n        button = OkButton(d, _('Sweep'))\n        vbox.addLayout(Buttons(CancelButton(d), button))\n        button.setEnabled(False)\n\n        def get_address():\n            addr = str(address_e.text()).strip()\n            if bitcoin.is_address(addr):\n                return addr\n\n        def get_pk():\n            text = str(keys_e.toPlainText())\n            return keystore.get_private_keys(text)\n\n        f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)\n        on_address = lambda text: address_e.setStyleSheet((ColorScheme.DEFAULT if get_address() else ColorScheme.RED).as_stylesheet())\n        keys_e.textChanged.connect(f)\n        address_e.textChanged.connect(f)\n        address_e.textChanged.connect(on_address)\n        if not d.exec_():\n            return\n        from electrum.wallet import sweep_preparations\n        try:\n            self.do_clear()\n            coins, keypairs = sweep_preparations(get_pk(), self.network)\n            self.tx_external_keypairs = keypairs\n            self.spend_coins(coins)\n            self.payto_e.setText(get_address())\n            self.spend_max()\n            self.payto_e.setFrozen(True)\n            self.amount_e.setFrozen(True)\n        except BaseException as e:\n            self.show_message(str(e))\n            return\n        self.warn_if_watching_only()\n\n    def _do_import(self, title, msg, func):\n        text = text_dialog(self, title, msg + ' :', _('Import'),\n                           allow_multi=True)\n        if not text:\n            return\n        bad = []\n        good = []\n        for key in str(text).split():\n            try:\n                addr = func(key)\n                good.append(addr)\n            except BaseException as e:\n                bad.append(key)\n                continue\n        if good:\n            self.show_message(_(\"The following addresses were added\") + ':\\n' + '\\n'.join(good))\n        if bad:\n            self.show_critical(_(\"The following inputs could not be imported\") + ':\\n'+ '\\n'.join(bad))\n        self.address_list.update()\n        self.history_list.update()\n\n    def import_addresses(self):\n        if not self.wallet.can_import_address():\n            return\n        title, msg = _('Import Addresses'), _(\"Enter addresses\")\n        self._do_import(title, msg, self.wallet.import_address)\n\n    @protected\n    def do_import_privkey(self, password):\n        if not self.wallet.can_import_privkey():\n            return\n        title, msg = _('Import Private Keys'), _(\"Enter private keys\")\n        self._do_import(title, msg, lambda x: self.wallet.import_private_key(x, password))\n\n    def update_fiat(self):\n        b = self.fx and self.fx.is_enabled()\n        self.fiat_send_e.setVisible(b)\n        self.fiat_receive_e.setVisible(b)\n        self.history_list.refresh_headers()\n        self.history_list.update()\n        self.address_list.refresh_headers()\n        self.address_list.update()\n        self.update_status()\n\n    def settings_dialog(self):\n        self.need_restart = False\n        d = WindowModalDialog(self, _('Preferences'))\n        vbox = QVBoxLayout()\n        tabs = QTabWidget()\n        gui_widgets = []\n        fee_widgets = []\n        tx_widgets = []\n        id_widgets = []\n\n        # language\n        lang_help = _('Select which language is used in the GUI (after restart).')\n        lang_label = HelpLabel(_('Language') + ':', lang_help)\n        lang_combo = QComboBox()\n        from electrum.i18n import languages\n        lang_combo.addItems(list(languages.values()))\n        try:\n            index = languages.keys().index(self.config.get(\"language\",''))\n        except Exception:\n            index = 0\n        lang_combo.setCurrentIndex(index)\n        if not self.config.is_modifiable('language'):\n            for w in [lang_combo, lang_label]: w.setEnabled(False)\n        def on_lang(x):\n            lang_request = list(languages.keys())[lang_combo.currentIndex()]\n            if lang_request != self.config.get('language'):\n                self.config.set_key(\"language\", lang_request, True)\n                self.need_restart = True\n        lang_combo.currentIndexChanged.connect(on_lang)\n        gui_widgets.append((lang_label, lang_combo))\n\n        nz_help = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, \"1.\" will be displayed as \"1.00\"')\n        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)\n        nz = QSpinBox()\n        nz.setMinimum(0)\n        nz.setMaximum(self.decimal_point)\n        nz.setValue(self.num_zeros)\n        if not self.config.is_modifiable('num_zeros'):\n            for w in [nz, nz_label]: w.setEnabled(False)\n        def on_nz():\n            value = nz.value()\n            if self.num_zeros != value:\n                self.num_zeros = value\n                self.config.set_key('num_zeros', value, True)\n                self.history_list.update()\n                self.address_list.update()\n        nz.valueChanged.connect(on_nz)\n        gui_widgets.append((nz_label, nz))\n\n\n        use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))\n        use_rbf_cb.setChecked(self.config.get('use_rbf', True))\n        use_rbf_cb.setToolTip(\n            _('If you check this box, your transactions will be marked as non-final,') + '\\n' + \\\n            _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\\n' + \\\n            _('Note that some merchants do not accept non-final transactions until they are confirmed.'))\n        def on_use_rbf(x):\n            self.config.set_key('use_rbf', x == Qt.Checked)\n        use_rbf_cb.stateChanged.connect(on_use_rbf)\n        fee_widgets.append((use_rbf_cb, None))\n\n        self.fee_unit = self.config.get('fee_unit', 0)\n        fee_unit_label = HelpLabel(_('Fee Unit') + ':', '')\n        fee_unit_combo = QComboBox()\n        fee_unit_combo.addItems([_('sat/byte'), _('mBTCP/kB')])\n        fee_unit_combo.setCurrentIndex(self.fee_unit)\n        def on_fee_unit(x):\n            self.fee_unit = x\n            self.config.set_key('fee_unit', x)\n            self.fee_slider.update()\n        fee_unit_combo.currentIndexChanged.connect(on_fee_unit)\n        fee_widgets.append((fee_unit_label, fee_unit_combo))\n\n        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\\n\\n'\\\n              + _('The following alias providers are available:') + '\\n'\\\n              + '\\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\\n\\n'\\\n              + 'For more information, see http://openalias.org'\n        alias_label = HelpLabel(_('OpenAlias') + ':', msg)\n        alias = self.config.get('alias','')\n        alias_e = QLineEdit(alias)\n        def set_alias_color():\n            if not self.config.get('alias'):\n                alias_e.setStyleSheet(\"\")\n                return\n            if self.alias_info:\n                alias_addr, alias_name, validated = self.alias_info\n                alias_e.setStyleSheet((ColorScheme.GREEN if validated else ColorScheme.RED).as_stylesheet(True))\n            else:\n                alias_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))\n        def on_alias_edit():\n            alias_e.setStyleSheet(\"\")\n            alias = str(alias_e.text())\n            self.config.set_key('alias', alias, True)\n            if alias:\n                self.fetch_alias()\n        set_alias_color()\n        self.alias_received_signal.connect(set_alias_color)\n        alias_e.editingFinished.connect(on_alias_edit)\n        id_widgets.append((alias_label, alias_e))\n\n        # SSL certificate\n        msg = ' '.join([\n            _('SSL certificate used to sign payment requests.'),\n            _('Use setconfig to set ssl_chain and ssl_privkey.'),\n        ])\n        if self.config.get('ssl_privkey') or self.config.get('ssl_chain'):\n            try:\n                SSL_identity = paymentrequest.check_ssl_config(self.config)\n                SSL_error = None\n            except BaseException as e:\n                SSL_identity = \"error\"\n                SSL_error = str(e)\n        else:\n            SSL_identity = \"\"\n            SSL_error = None\n        SSL_id_label = HelpLabel(_('SSL certificate') + ':', msg)\n        SSL_id_e = QLineEdit(SSL_identity)\n        SSL_id_e.setStyleSheet((ColorScheme.RED if SSL_error else ColorScheme.GREEN).as_stylesheet(True) if SSL_identity else '')\n        if SSL_error:\n            SSL_id_e.setToolTip(SSL_error)\n        SSL_id_e.setReadOnly(True)\n        id_widgets.append((SSL_id_label, SSL_id_e))\n\n        units = ['BTCP', 'mBTCP', 'bits']\n        msg = _('Base unit of your wallet.')\\\n              + '\\n1BTCP=1000mBTCP.\\n' \\\n              + _(' These settings affects the fields in the Send tab')+' '\n        unit_label = HelpLabel(_('Base unit') + ':', msg)\n        unit_combo = QComboBox()\n        unit_combo.addItems(units)\n        unit_combo.setCurrentIndex(units.index(self.base_unit()))\n        def on_unit(x):\n            unit_result = units[unit_combo.currentIndex()]\n            if self.base_unit() == unit_result:\n                return\n            edits = self.amount_e, self.fee_e, self.receive_amount_e\n            amounts = [edit.get_amount() for edit in edits]\n            if unit_result == 'BTCP':\n                self.decimal_point = 8\n            elif unit_result == 'mBTCP':\n                self.decimal_point = 5\n            elif unit_result == 'bits':\n                self.decimal_point = 2\n            else:\n                raise Exception('Unknown base unit')\n            self.config.set_key('decimal_point', self.decimal_point, True)\n            self.history_list.update()\n            self.request_list.update()\n            self.address_list.update()\n            for edit, amount in zip(edits, amounts):\n                edit.setAmount(amount)\n            self.update_status()\n        unit_combo.currentIndexChanged.connect(on_unit)\n        gui_widgets.append((unit_label, unit_combo))\n\n        block_explorers = sorted(util.block_explorer_info().keys())\n        msg = _('Choose which block explorer to use for transaction info')\n        block_ex_label = HelpLabel(_('Block Explorer') + ':', msg)\n        block_ex_combo = QComboBox()\n        block_ex_combo.addItems(block_explorers)\n        block_ex_combo.setCurrentIndex(block_ex_combo.findText(util.block_explorer(self.config)))\n        def on_be(x):\n            be_result = block_explorers[block_ex_combo.currentIndex()]\n            self.config.set_key('block_explorer', be_result, True)\n        block_ex_combo.currentIndexChanged.connect(on_be)\n        gui_widgets.append((block_ex_label, block_ex_combo))\n\n        from electrum import qrscanner\n        system_cameras = qrscanner._find_system_cameras()\n        qr_combo = QComboBox()\n        qr_combo.addItem(\"Default\",\"default\")\n        for camera, device in system_cameras.items():\n            qr_combo.addItem(camera, device)\n        #combo.addItem(\"Manually specify a device\", config.get(\"video_device\"))\n        index = qr_combo.findData(self.config.get(\"video_device\"))\n        qr_combo.setCurrentIndex(index)\n        msg = _(\"Install the zbar package to enable this.\")\n        qr_label = HelpLabel(_('Video Device') + ':', msg)\n        qr_combo.setEnabled(qrscanner.libzbar is not None)\n        on_video_device = lambda x: self.config.set_key(\"video_device\", qr_combo.itemData(x), True)\n        qr_combo.currentIndexChanged.connect(on_video_device)\n        gui_widgets.append((qr_label, qr_combo))\n\n        usechange_cb = QCheckBox(_('Use change addresses'))\n        usechange_cb.setChecked(self.wallet.use_change)\n        if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)\n        def on_usechange(x):\n            usechange_result = x == Qt.Checked\n            if self.wallet.use_change != usechange_result:\n                self.wallet.use_change = usechange_result\n                self.wallet.storage.put('use_change', self.wallet.use_change)\n                multiple_cb.setEnabled(self.wallet.use_change)\n        usechange_cb.stateChanged.connect(on_usechange)\n        usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))\n        tx_widgets.append((usechange_cb, None))\n\n        def on_multiple(x):\n            multiple = x == Qt.Checked\n            if self.wallet.multiple_change != multiple:\n                self.wallet.multiple_change = multiple\n                self.wallet.storage.put('multiple_change', multiple)\n        multiple_change = self.wallet.multiple_change\n        multiple_cb = QCheckBox(_('Use multiple change addresses'))\n        multiple_cb.setEnabled(self.wallet.use_change)\n        multiple_cb.setToolTip('\\n'.join([\n            _('In some cases, use up to 3 change addresses in order to break '\n              'up large coin amounts and obfuscate the recipient address.'),\n            _('This may result in higher transactions fees.')\n        ]))\n        multiple_cb.setChecked(multiple_change)\n        multiple_cb.stateChanged.connect(on_multiple)\n        tx_widgets.append((multiple_cb, None))\n\n        def fmt_docs(key, klass):\n            lines = [ln.lstrip(\" \") for ln in klass.__doc__.split(\"\\n\")]\n            return '\\n'.join([key, \"\", \" \".join(lines)])\n\n        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())\n        if len(choosers) > 1:\n            chooser_name = coinchooser.get_name(self.config)\n            msg = _('Choose coin (UTXO) selection method.  The following are available:\\n\\n')\n            msg += '\\n\\n'.join(fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())\n            chooser_label = HelpLabel(_('Coin selection') + ':', msg)\n            chooser_combo = QComboBox()\n            chooser_combo.addItems(choosers)\n            i = choosers.index(chooser_name) if chooser_name in choosers else 0\n            chooser_combo.setCurrentIndex(i)\n            def on_chooser(x):\n                chooser_name = choosers[chooser_combo.currentIndex()]\n                self.config.set_key('coin_chooser', chooser_name)\n            chooser_combo.currentIndexChanged.connect(on_chooser)\n            tx_widgets.append((chooser_label, chooser_combo))\n\n        def on_unconf(x):\n            self.config.set_key('confirmed_only', bool(x))\n        conf_only = self.config.get('confirmed_only', False)\n        unconf_cb = QCheckBox(_('Spend only confirmed coins'))\n        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))\n        unconf_cb.setChecked(conf_only)\n        unconf_cb.stateChanged.connect(on_unconf)\n        tx_widgets.append((unconf_cb, None))\n\n        # Fiat Currency\n        hist_checkbox = QCheckBox()\n        fiat_address_checkbox = QCheckBox()\n        ccy_combo = QComboBox()\n        ex_combo = QComboBox()\n\n        def update_currencies():\n            if not self.fx: return\n            currencies = sorted(self.fx.get_currencies(self.fx.get_history_config()))\n            ccy_combo.clear()\n            ccy_combo.addItems([_('None')] + currencies)\n            if self.fx.is_enabled():\n                ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))\n\n        def update_history_cb():\n            if not self.fx: return\n            hist_checkbox.setChecked(self.fx.get_history_config())\n            hist_checkbox.setEnabled(self.fx.is_enabled())\n\n        def update_fiat_address_cb():\n            if not self.fx: return\n            fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())\n\n        def update_exchanges():\n            if not self.fx: return\n            b = self.fx.is_enabled()\n            ex_combo.setEnabled(b)\n            if b:\n                h = self.fx.get_history_config()\n                c = self.fx.get_currency()\n                exchanges = self.fx.get_exchanges_by_ccy(c, h)\n            else:\n                exchanges = self.fx.get_exchanges_by_ccy('USD', False)\n            ex_combo.clear()\n            ex_combo.addItems(sorted(exchanges))\n            ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))\n\n        def on_currency(hh):\n            if not self.fx: return\n            b = bool(ccy_combo.currentIndex())\n            ccy = str(ccy_combo.currentText()) if b else None\n            self.fx.set_enabled(b)\n            if b and ccy != self.fx.ccy:\n                self.fx.set_currency(ccy)\n            update_history_cb()\n            update_exchanges()\n            self.update_fiat()\n\n        def on_exchange(idx):\n            exchange = str(ex_combo.currentText())\n            if self.fx and self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name():\n                self.fx.set_exchange(exchange)\n\n        def on_history(checked):\n            if not self.fx: return\n            self.fx.set_history_config(checked)\n            update_exchanges()\n            self.history_list.refresh_headers()\n            if self.fx.is_enabled() and checked:\n                # reset timeout to get historical rates\n                self.fx.timeout = 0\n\n        def on_fiat_address(checked):\n            if not self.fx: return\n            self.fx.set_fiat_address_config(checked)\n            self.address_list.refresh_headers()\n            self.address_list.update()\n\n        update_currencies()\n        update_history_cb()\n        update_fiat_address_cb()\n        update_exchanges()\n        ccy_combo.currentIndexChanged.connect(on_currency)\n        hist_checkbox.stateChanged.connect(on_history)\n        fiat_address_checkbox.stateChanged.connect(on_fiat_address)\n        ex_combo.currentIndexChanged.connect(on_exchange)\n\n        fiat_widgets = []\n        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))\n        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))\n        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')), fiat_address_checkbox))\n        fiat_widgets.append((QLabel(_('Source')), ex_combo))\n\n        tabs_info = [\n            (fee_widgets, _('Fees')),\n            (tx_widgets, _('Transactions')),\n            (gui_widgets, _('Appearance')),\n            (fiat_widgets, _('Fiat')),\n            (id_widgets, _('Identity')),\n        ]\n        for widgets, name in tabs_info:\n            tab = QWidget()\n            grid = QGridLayout(tab)\n            grid.setColumnStretch(0,1)\n            for a,b in widgets:\n                i = grid.rowCount()\n                if b:\n                    if a:\n                        grid.addWidget(a, i, 0)\n                    grid.addWidget(b, i, 1)\n                else:\n                    grid.addWidget(a, i, 0, 1, 2)\n            tabs.addTab(tab, name)\n\n        vbox.addWidget(tabs)\n        vbox.addStretch(1)\n        vbox.addLayout(Buttons(CloseButton(d)))\n        d.setLayout(vbox)\n\n        # run the dialog\n        d.exec_()\n\n        if self.fx:\n            self.fx.timeout = 0\n\n        self.alias_received_signal.disconnect(set_alias_color)\n\n        run_hook('close_settings_dialog')\n        if self.need_restart:\n            self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))\n\n\n    def closeEvent(self, event):\n        # It seems in some rare cases this closeEvent() is called twice\n        if not self.cleaned_up:\n            self.cleaned_up = True\n            self.clean_up()\n        event.accept()\n\n    def clean_up(self):\n        self.wallet.thread.stop()\n        if self.network:\n            self.network.unregister_callback(self.on_network)\n        self.config.set_key(\"is_maximized\", self.isMaximized())\n        if not self.isMaximized():\n            g = self.geometry()\n            self.wallet.storage.put(\"winpos-qt\", [g.left(),g.top(),\n                                                  g.width(),g.height()])\n        self.config.set_key(\"console-history\", self.console.history[-50:],\n                            True)\n        if self.qr_window:\n            self.qr_window.close()\n        self.close_wallet()\n        self.gui_object.close_window(self)\n\n    def plugins_dialog(self):\n        self.pluginsdialog = d = WindowModalDialog(self, _('Electrum Plugins'))\n\n        plugins = self.gui_object.plugins\n\n        vbox = QVBoxLayout(d)\n\n        # plugins\n        scroll = QScrollArea()\n        scroll.setEnabled(True)\n        scroll.setWidgetResizable(True)\n        scroll.setMinimumSize(400,250)\n        vbox.addWidget(scroll)\n\n        w = QWidget()\n        scroll.setWidget(w)\n        w.setMinimumHeight(plugins.count() * 35)\n\n        grid = QGridLayout()\n        grid.setColumnStretch(0,1)\n        w.setLayout(grid)\n\n        settings_widgets = {}\n\n        def enable_settings_widget(p, name, i):\n            widget = settings_widgets.get(name)\n            if not widget and p and p.requires_settings():\n                widget = settings_widgets[name] = p.settings_widget(d)\n                grid.addWidget(widget, i, 1)\n            if widget:\n                widget.setEnabled(bool(p and p.is_enabled()))\n\n        def do_toggle(cb, name, i):\n            p = plugins.toggle(name)\n            cb.setChecked(bool(p))\n            enable_settings_widget(p, name, i)\n            run_hook('init_qt', self.gui_object)\n\n        for i, descr in enumerate(plugins.descriptions.values()):\n            name = descr['__name__']\n            p = plugins.get(name)\n            if descr.get('registers_keystore'):\n                continue\n            try:\n                cb = QCheckBox(descr['fullname'])\n                plugin_is_loaded = p is not None\n                cb_enabled = (not plugin_is_loaded and plugins.is_available(name, self.wallet)\n                              or plugin_is_loaded and p.can_user_disable())\n                cb.setEnabled(cb_enabled)\n                cb.setChecked(plugin_is_loaded and p.is_enabled())\n                grid.addWidget(cb, i, 0)\n                enable_settings_widget(p, name, i)\n                cb.clicked.connect(partial(do_toggle, cb, name, i))\n                msg = descr['description']\n                if descr.get('requires'):\n                    msg += '\\n\\n' + _('Requires') + ':\\n' + '\\n'.join(map(lambda x: x[1], descr.get('requires')))\n                grid.addWidget(HelpButton(msg), i, 2)\n            except Exception:\n                self.print_msg(\"error: cannot display plugin\", name)\n                traceback.print_exc(file=sys.stdout)\n        grid.setRowStretch(len(plugins.descriptions.values()), 1)\n        vbox.addLayout(Buttons(CloseButton(d)))\n        d.exec_()\n\n    def cpfp(self, parent_tx, new_tx):\n        total_size = parent_tx.estimated_size() + new_tx.estimated_size()\n        d = WindowModalDialog(self, _('Child Pays for Parent'))\n        vbox = QVBoxLayout(d)\n        msg = (\n            \"A CPFP is a transaction that sends an unconfirmed output back to \"\n            \"yourself, with a high fee. The goal is to have miners confirm \"\n            \"the parent transaction in order to get the fee attached to the \"\n            \"child transaction.\")\n        vbox.addWidget(WWLabel(_(msg)))\n        msg2 = (\"The proposed fee is computed using your \"\n            \"fee/kB settings, applied to the total size of both child and \"\n            \"parent transactions. After you broadcast a CPFP transaction, \"\n            \"it is normal to see a new unconfirmed transaction in your history.\")\n        vbox.addWidget(WWLabel(_(msg2)))\n        grid = QGridLayout()\n        grid.addWidget(QLabel(_('Total size') + ':'), 0, 0)\n        grid.addWidget(QLabel('%d bytes'% total_size), 0, 1)\n        max_fee = new_tx.output_value()\n        grid.addWidget(QLabel(_('Input amount') + ':'), 1, 0)\n        grid.addWidget(QLabel(self.format_amount(max_fee) + ' ' + self.base_unit()), 1, 1)\n        output_amount = QLabel('')\n        grid.addWidget(QLabel(_('Output amount') + ':'), 2, 0)\n        grid.addWidget(output_amount, 2, 1)\n        fee_e = BTCAmountEdit(self.get_decimal_point)\n        def f(x):\n            a = max_fee - fee_e.get_amount()\n            output_amount.setText((self.format_amount(a) + ' ' + self.base_unit()) if a else '')\n        fee_e.textChanged.connect(f)\n        fee = self.config.fee_per_kb() * total_size / 1000\n        fee_e.setAmount(fee)\n        grid.addWidget(QLabel(_('Fee' + ':')), 3, 0)\n        grid.addWidget(fee_e, 3, 1)\n        def on_rate(dyn, pos, fee_rate):\n            fee = fee_rate * total_size / 1000\n            fee = min(max_fee, fee)\n            fee_e.setAmount(fee)\n        fee_slider = FeeSlider(self, self.config, on_rate)\n        fee_slider.update()\n        grid.addWidget(fee_slider, 4, 1)\n        vbox.addLayout(grid)\n        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))\n        if not d.exec_():\n            return\n        fee = fee_e.get_amount()\n        if fee > max_fee:\n            self.show_error(_('Max fee exceeded!'))\n            return\n        new_tx = self.wallet.cpfp(parent_tx, fee)\n        new_tx.set_rbf(True)\n        self.show_transaction(new_tx)\n\n    def bump_fee_dialog(self, tx):\n        is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)\n        tx_label = self.wallet.get_label(tx.txid())\n        tx_size = tx.estimated_size()\n        d = WindowModalDialog(self, _('Bump Fee'))\n        vbox = QVBoxLayout(d)\n        vbox.addWidget(QLabel(_('Current fee') + ': %s'% self.format_amount(fee) + ' ' + self.base_unit()))\n        vbox.addWidget(QLabel(_('New fee' + ':')))\n\n        fee_e = BTCAmountEdit(self.get_decimal_point)\n        fee_e.setAmount(fee * 1.5)\n        vbox.addWidget(fee_e)\n\n        def on_rate(dyn, pos, fee_rate):\n            fee = fee_rate * tx_size / 1000\n            fee_e.setAmount(fee)\n        fee_slider = FeeSlider(self, self.config, on_rate)\n        vbox.addWidget(fee_slider)\n        cb = QCheckBox(_('Final'))\n        vbox.addWidget(cb)\n        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))\n        if not d.exec_():\n            return\n        is_final = cb.isChecked()\n        new_fee = fee_e.get_amount()\n        delta = new_fee - fee\n        if delta < 0:\n            self.show_error(\"Fee too low!\")\n            return\n        try:\n            new_tx = self.wallet.bump_fee(tx, delta)\n        except BaseException as e:\n            self.show_error(str(e))\n            return\n        if is_final:\n            new_tx.set_rbf(False)\n        self.show_transaction(new_tx, tx_label)\n"
  },
  {
    "path": "gui/qt/network_dialog.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport socket\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import *\nimport PyQt5.QtCore as QtCore\n\nfrom electrum.i18n import _\nfrom electrum.bitcoin import NetworkConstants\nfrom electrum.util import print_error\n\nfrom .util import *\n\nprotocol_names = ['TCP', 'SSL']\nprotocol_letters = 'ts'\n\nclass NetworkDialog(QDialog):\n    def __init__(self, network, config, network_updated_signal_obj):\n        QDialog.__init__(self)\n        self.setWindowTitle(_('Network'))\n        self.setMinimumSize(500, 20)\n        self.nlayout = NetworkChoiceLayout(network, config)\n        self.network_updated_signal_obj = network_updated_signal_obj\n        vbox = QVBoxLayout(self)\n        vbox.addLayout(self.nlayout.layout())\n        vbox.addLayout(Buttons(CloseButton(self)))\n        self.network_updated_signal_obj.network_updated_signal.connect(\n            self.on_update)\n        network.register_callback(self.on_network, ['updated', 'interfaces'])\n\n    def on_network(self, event, *args):\n        self.network_updated_signal_obj.network_updated_signal.emit(event, args)\n\n    def on_update(self):\n        self.nlayout.update()\n\n\n\nclass NodesListWidget(QTreeWidget):\n\n    def __init__(self, parent):\n        QTreeWidget.__init__(self)\n        self.parent = parent\n        self.setHeaderLabels([_('Connected Node'), _('Height')])\n        self.setContextMenuPolicy(Qt.CustomContextMenu)\n        self.customContextMenuRequested.connect(self.create_menu)\n\n    def create_menu(self, position):\n        item = self.currentItem()\n        if not item:\n            return\n        is_server = not bool(item.data(0, Qt.UserRole))\n        menu = QMenu()\n        if is_server:\n            server = item.data(1, Qt.UserRole)\n            menu.addAction(_(\"Use as server\"), lambda: self.parent.follow_server(server))\n        else:\n            index = item.data(1, Qt.UserRole)\n            menu.addAction(_(\"Follow this branch\"), lambda: self.parent.follow_branch(index))\n        menu.exec_(self.viewport().mapToGlobal(position))\n\n    def keyPressEvent(self, event):\n        if event.key() in [ Qt.Key_F2, Qt.Key_Return ]:\n            self.on_activated(self.currentItem(), self.currentColumn())\n        else:\n            QTreeWidget.keyPressEvent(self, event)\n\n    def on_activated(self, item, column):\n        # on 'enter' we show the menu\n        pt = self.visualItemRect(item).bottomLeft()\n        pt.setX(50)\n        self.customContextMenuRequested.emit(pt)\n\n    def update(self, network):\n        self.clear()\n        self.addChild = self.addTopLevelItem\n        chains = network.get_blockchains()\n        n_chains = len(chains)\n        for k, items in chains.items():\n            b = network.blockchains[k]\n            name = b.get_name()\n            if n_chains >1:\n                x = QTreeWidgetItem([name + '@%d'%b.get_checkpoint(), '%d'%b.height()])\n                x.setData(0, Qt.UserRole, 1)\n                x.setData(1, Qt.UserRole, b.checkpoint)\n            else:\n                x = self\n            for i in items:\n                star = ' *' if i == network.interface else ''\n                item = QTreeWidgetItem([i.host + star, '%d'%i.tip])\n                item.setData(0, Qt.UserRole, 0)\n                item.setData(1, Qt.UserRole, i.server)\n                x.addChild(item)\n            if n_chains>1:\n                self.addTopLevelItem(x)\n                x.setExpanded(True)\n\n        h = self.header()\n        h.setStretchLastSection(False)\n        h.setSectionResizeMode(0, QHeaderView.Stretch)\n        h.setSectionResizeMode(1, QHeaderView.ResizeToContents)\n\n\nclass ServerListWidget(QTreeWidget):\n\n    def __init__(self, parent):\n        QTreeWidget.__init__(self)\n        self.parent = parent\n        self.setHeaderLabels([_('Host'), _('Port')])\n        self.setContextMenuPolicy(Qt.CustomContextMenu)\n        self.customContextMenuRequested.connect(self.create_menu)\n\n    def create_menu(self, position):\n        item = self.currentItem()\n        if not item:\n            return\n        menu = QMenu()\n        server = item.data(1, Qt.UserRole)\n        menu.addAction(_(\"Use as server\"), lambda: self.set_server(server))\n        menu.exec_(self.viewport().mapToGlobal(position))\n\n    def set_server(self, s):\n        host, port, protocol = s.split(':')\n        self.parent.server_host.setText(host)\n        self.parent.server_port.setText(port)\n        self.parent.set_server()\n\n    def keyPressEvent(self, event):\n        if event.key() in [ Qt.Key_F2, Qt.Key_Return ]:\n            self.on_activated(self.currentItem(), self.currentColumn())\n        else:\n            QTreeWidget.keyPressEvent(self, event)\n\n    def on_activated(self, item, column):\n        # on 'enter' we show the menu\n        pt = self.visualItemRect(item).bottomLeft()\n        pt.setX(50)\n        self.customContextMenuRequested.emit(pt)\n\n    def update(self, servers, protocol, use_tor):\n        self.clear()\n        for _host, d in sorted(servers.items()):\n            if _host.endswith('.onion') and not use_tor:\n                continue\n            port = d.get(protocol)\n            if port:\n                x = QTreeWidgetItem([_host, port])\n                server = _host+':'+port+':'+protocol\n                x.setData(1, Qt.UserRole, server)\n                self.addTopLevelItem(x)\n\n        h = self.header()\n        h.setStretchLastSection(False)\n        h.setSectionResizeMode(0, QHeaderView.Stretch)\n        h.setSectionResizeMode(1, QHeaderView.ResizeToContents)\n\n\nclass NetworkChoiceLayout(object):\n\n    def __init__(self, network, config, wizard=False):\n        self.network = network\n        self.config = config\n        self.protocol = None\n        self.tor_proxy = None\n\n        self.tabs = tabs = QTabWidget()\n        server_tab = QWidget()\n        proxy_tab = QWidget()\n        blockchain_tab = QWidget()\n        tabs.addTab(blockchain_tab, _('Overview'))\n        tabs.addTab(server_tab, _('Server'))\n        tabs.addTab(proxy_tab, _('Proxy'))\n\n        # server tab\n        grid = QGridLayout(server_tab)\n        grid.setSpacing(8)\n\n        self.server_host = QLineEdit()\n        self.server_host.setFixedWidth(200)\n        self.server_port = QLineEdit()\n        self.server_port.setFixedWidth(60)\n        self.autoconnect_cb = QCheckBox(_('Select server automatically'))\n        self.autoconnect_cb.setEnabled(self.config.is_modifiable('auto_connect'))\n\n        self.server_host.editingFinished.connect(self.set_server)\n        self.server_port.editingFinished.connect(self.set_server)\n        self.autoconnect_cb.clicked.connect(self.set_server)\n        self.autoconnect_cb.clicked.connect(self.update)\n\n        msg = ' '.join([\n            _(\"If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain.\"),\n            _(\"If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging.\")\n        ])\n        grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3)\n        grid.addWidget(HelpButton(msg), 0, 4)\n\n        grid.addWidget(QLabel(_('Server') + ':'), 1, 0)\n        grid.addWidget(self.server_host, 1, 1, 1, 2)\n        grid.addWidget(self.server_port, 1, 3)\n\n        label = _('Server Peers') if network.is_connected() else _('Default Servers')\n        grid.addWidget(QLabel(label + ':'), 2, 0, 1, 5)\n        self.servers_list = ServerListWidget(self)\n        grid.addWidget(self.servers_list, 3, 0, 1, 5)\n\n        # Proxy tab\n        grid = QGridLayout(proxy_tab)\n        grid.setSpacing(8)\n\n        # proxy setting\n        self.proxy_cb = QCheckBox(_('Use Proxy'))\n        self.proxy_cb.clicked.connect(self.check_disable_proxy)\n        self.proxy_cb.clicked.connect(self.set_proxy)\n\n        self.proxy_mode = QComboBox()\n        self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP'])\n        self.proxy_host = QLineEdit()\n        self.proxy_host.setFixedWidth(200)\n        self.proxy_port = QLineEdit()\n        self.proxy_port.setFixedWidth(60)\n        self.proxy_user = QLineEdit()\n        self.proxy_user.setPlaceholderText(_(\"Proxy user\"))\n        self.proxy_password = QLineEdit()\n        self.proxy_password.setPlaceholderText(_(\"Password\"))\n        self.proxy_password.setEchoMode(QLineEdit.Password)\n        self.proxy_password.setFixedWidth(60)\n\n        self.proxy_mode.currentIndexChanged.connect(self.set_proxy)\n        self.proxy_host.editingFinished.connect(self.set_proxy)\n        self.proxy_port.editingFinished.connect(self.set_proxy)\n        self.proxy_user.editingFinished.connect(self.set_proxy)\n        self.proxy_password.editingFinished.connect(self.set_proxy)\n\n        self.proxy_mode.currentIndexChanged.connect(self.proxy_settings_changed)\n        self.proxy_host.textEdited.connect(self.proxy_settings_changed)\n        self.proxy_port.textEdited.connect(self.proxy_settings_changed)\n        self.proxy_user.textEdited.connect(self.proxy_settings_changed)\n        self.proxy_password.textEdited.connect(self.proxy_settings_changed)\n\n        self.tor_cb = QCheckBox(_(\"Use Tor Proxy\"))\n        self.tor_cb.setIcon(QIcon(\":icons/tor_logo.png\"))\n        self.tor_cb.hide()\n        self.tor_cb.clicked.connect(self.use_tor_proxy)\n\n        grid.addWidget(self.tor_cb, 1, 0, 1, 3)\n        grid.addWidget(self.proxy_cb, 2, 0, 1, 3)\n        grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 2, 4)\n        grid.addWidget(self.proxy_mode, 4, 1)\n        grid.addWidget(self.proxy_host, 4, 2)\n        grid.addWidget(self.proxy_port, 4, 3)\n        grid.addWidget(self.proxy_user, 5, 2)\n        grid.addWidget(self.proxy_password, 5, 3)\n        grid.setRowStretch(7, 1)\n\n        # Blockchain Tab\n        grid = QGridLayout(blockchain_tab)\n        msg =  ' '.join([\n            _(\"Electrum connects to several nodes in order to download block headers and find out the longest blockchain.\"),\n            _(\"This blockchain is used to verify the transactions sent by your transaction server.\")\n        ])\n        self.status_label = QLabel('')\n        grid.addWidget(QLabel(_('Status') + ':'), 0, 0)\n        grid.addWidget(self.status_label, 0, 1, 1, 3)\n        grid.addWidget(HelpButton(msg), 0, 4)\n\n        self.server_label = QLabel('')\n        msg = _(\"Electrum sends your wallet addresses to a single server, in order to receive your transaction history.\")\n        grid.addWidget(QLabel(_('Server') + ':'), 1, 0)\n        grid.addWidget(self.server_label, 1, 1, 1, 3)\n        grid.addWidget(HelpButton(msg), 1, 4)\n\n        self.height_label = QLabel('')\n        msg = _('This is the height of your local copy of the blockchain.')\n        grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0)\n        grid.addWidget(self.height_label, 2, 1)\n        grid.addWidget(HelpButton(msg), 2, 4)\n\n        self.split_label = QLabel('')\n        grid.addWidget(self.split_label, 3, 0, 1, 3)\n\n        self.nodes_list_widget = NodesListWidget(self)\n        grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5)\n\n        vbox = QVBoxLayout()\n        vbox.addWidget(tabs)\n        self.layout_ = vbox\n        # tor detector\n        self.td = td = TorDetector()\n        td.found_proxy.connect(self.suggest_proxy)\n        td.start()\n\n        self.fill_in_proxy_settings()\n        self.update()\n\n    def check_disable_proxy(self, b):\n        if not self.config.is_modifiable('proxy'):\n            b = False\n        for w in [self.proxy_mode, self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password]:\n            w.setEnabled(b)\n\n    def enable_set_server(self):\n        if self.config.is_modifiable('server'):\n            enabled = not self.autoconnect_cb.isChecked()\n            self.server_host.setEnabled(enabled)\n            self.server_port.setEnabled(enabled)\n            self.servers_list.setEnabled(enabled)\n        else:\n            for w in [self.autoconnect_cb, self.server_host, self.server_port, self.servers_list]:\n                w.setEnabled(False)\n\n    def update(self):\n        host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()\n        self.server_host.setText(host)\n        self.server_port.setText(port)\n        self.autoconnect_cb.setChecked(auto_connect)\n\n        host = self.network.interface.host if self.network.interface else _('None')\n        self.server_label.setText(host)\n\n        self.set_protocol(protocol)\n        self.servers = self.network.get_servers()\n        self.servers_list.update(self.servers, self.protocol, self.tor_cb.isChecked())\n        self.enable_set_server()\n\n        height_str = \"%d \"%(self.network.get_local_height()) + _('blocks')\n        self.height_label.setText(height_str)\n        n = len(self.network.get_interfaces())\n        status = _(\"Connected to {0} nodes.\").format(n) if n else _(\"Not connected\")\n        self.status_label.setText(status)\n        chains = self.network.get_blockchains()\n        if len(chains)>1:\n            chain = self.network.blockchain()\n            checkpoint = chain.get_checkpoint()\n            name = chain.get_name()\n            msg = _('Chain split detected at block {0}').format(checkpoint) + '\\n'\n            msg += (_('You are following branch') if auto_connect else _('Your server is on branch'))+ ' ' + name\n            msg += ' (%d %s)' % (chain.get_branch_size(), _('blocks'))\n        else:\n            msg = ''\n        self.split_label.setText(msg)\n        self.nodes_list_widget.update(self.network)\n\n    def fill_in_proxy_settings(self):\n        host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()\n        if not proxy_config:\n            proxy_config = {\"mode\": \"none\", \"host\": \"localhost\", \"port\": \"9050\"}\n\n        b = proxy_config.get('mode') != \"none\"\n        self.check_disable_proxy(b)\n        if b:\n            self.proxy_cb.setChecked(True)\n            self.proxy_mode.setCurrentIndex(\n                self.proxy_mode.findText(str(proxy_config.get(\"mode\").upper())))\n\n        self.proxy_host.setText(proxy_config.get(\"host\"))\n        self.proxy_port.setText(proxy_config.get(\"port\"))\n        self.proxy_user.setText(proxy_config.get(\"user\", \"\"))\n        self.proxy_password.setText(proxy_config.get(\"password\", \"\"))\n\n    def layout(self):\n        return self.layout_\n\n    def set_protocol(self, protocol):\n        if protocol != self.protocol:\n            self.protocol = protocol\n\n    def change_protocol(self, use_ssl):\n        p = 's' if use_ssl else 't'\n        host = self.server_host.text()\n        pp = self.servers.get(host, NetworkConstants.DEFAULT_PORTS)\n        if p not in pp.keys():\n            p = list(pp.keys())[0]\n        port = pp[p]\n        self.server_host.setText(host)\n        self.server_port.setText(port)\n        self.set_protocol(p)\n        self.set_server()\n\n    def follow_branch(self, index):\n        self.network.follow_chain(index)\n        self.update()\n\n    def follow_server(self, server):\n        self.network.switch_to_interface(server)\n        host, port, protocol, proxy, auto_connect = self.network.get_parameters()\n        host, port, protocol = server.split(':')\n        self.network.set_parameters(host, port, protocol, proxy, auto_connect)\n        self.update()\n\n    def server_changed(self, x):\n        if x:\n            self.change_server(str(x.text(0)), self.protocol)\n\n    def change_server(self, host, protocol):\n        pp = self.servers.get(host, NetworkConstants.DEFAULT_PORTS)\n        if protocol and protocol not in protocol_letters:\n            protocol = None\n        if protocol:\n            port = pp.get(protocol)\n            if port is None:\n                protocol = None\n        if not protocol:\n            if 's' in pp.keys():\n                protocol = 's'\n                port = pp.get(protocol)\n            else:\n                protocol = list(pp.keys())[0]\n                port = pp.get(protocol)\n        self.server_host.setText(host)\n        self.server_port.setText(port)\n\n    def accept(self):\n        pass\n\n    def set_server(self):\n        host, port, protocol, proxy, auto_connect = self.network.get_parameters()\n        host = str(self.server_host.text())\n        port = str(self.server_port.text())\n        protocol = 't' if self.config.get('nossl') else 's'\n        auto_connect = self.autoconnect_cb.isChecked()\n        self.network.set_parameters(host, port, protocol, proxy, auto_connect)\n\n    def set_proxy(self):\n        host, port, protocol, proxy, auto_connect = self.network.get_parameters()\n        if self.proxy_cb.isChecked():\n            proxy = { 'mode':str(self.proxy_mode.currentText()).lower(),\n                      'host':str(self.proxy_host.text()),\n                      'port':str(self.proxy_port.text()),\n                      'user':str(self.proxy_user.text()),\n                      'password':str(self.proxy_password.text())}\n        else:\n            proxy = None\n            self.tor_cb.setChecked(False)\n        self.network.set_parameters(host, port, protocol, proxy, auto_connect)\n\n    def suggest_proxy(self, found_proxy):\n        self.tor_proxy = found_proxy\n        self.tor_cb.setText(\"Use Tor proxy at port \" + str(found_proxy[1]))\n        if self.proxy_mode.currentIndex() == self.proxy_mode.findText('SOCKS5') \\\n            and self.proxy_host.text() == \"127.0.0.1\" \\\n                and self.proxy_port.text() == str(found_proxy[1]):\n            self.tor_cb.setChecked(True)\n        self.tor_cb.show()\n\n    def use_tor_proxy(self, use_it):\n        if not use_it:\n            self.proxy_cb.setChecked(False)\n        else:\n            socks5_mode_index = self.proxy_mode.findText('SOCKS5')\n            if socks5_mode_index == -1:\n                print_error(\"[network_dialog] can't find proxy_mode 'SOCKS5'\")\n                return\n            self.proxy_mode.setCurrentIndex(socks5_mode_index)\n            self.proxy_host.setText(\"127.0.0.1\")\n            self.proxy_port.setText(str(self.tor_proxy[1]))\n            self.proxy_user.setText(\"\")\n            self.proxy_password.setText(\"\")\n            self.tor_cb.setChecked(True)\n            self.proxy_cb.setChecked(True)\n        self.check_disable_proxy(use_it)\n        self.set_proxy()\n\n    def proxy_settings_changed(self):\n        self.tor_cb.setChecked(False)\n\n\nclass TorDetector(QThread):\n    found_proxy = pyqtSignal(object)\n\n    def __init__(self):\n        QThread.__init__(self)\n\n    def run(self):\n        # Probable ports for Tor to listen at\n        ports = [9050, 9150]\n        for p in ports:\n            if TorDetector.is_tor_port(p):\n                self.found_proxy.emit((\"127.0.0.1\", p))\n                return\n\n    @staticmethod\n    def is_tor_port(port):\n        try:\n            s = (socket._socketobject if hasattr(socket, \"_socketobject\") else socket.socket)(socket.AF_INET, socket.SOCK_STREAM)\n            s.settimeout(0.1)\n            s.connect((\"127.0.0.1\", port))\n            # Tor responds uniquely to HTTP-like requests\n            s.send(b\"GET\\n\")\n            if b\"Tor is not an HTTP Proxy\" in s.recv(1024):\n                return True\n        except socket.error:\n            pass\n        return False\n"
  },
  {
    "path": "gui/qt/password_dialog.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2013 ecdsa@github\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\nfrom electrum.i18n import _\nfrom .util import *\nimport re\nimport math\n\nfrom electrum.plugins import run_hook\n\ndef check_password_strength(password):\n\n    '''\n    Check the strength of the password entered by the user and return back the same\n    :param password: password entered by user in New Password\n    :return: password strength Weak or Medium or Strong\n    '''\n    password = password\n    n = math.log(len(set(password)))\n    num = re.search(\"[0-9]\", password) is not None and re.match(\"^[0-9]*$\", password) is None\n    caps = password != password.upper() and password != password.lower()\n    extra = re.match(\"^[a-zA-Z0-9]*$\", password) is None\n    score = len(password)*( n + caps + num + extra)/20\n    password_strength = {0:\"Weak\",1:\"Medium\",2:\"Strong\",3:\"Very Strong\"}\n    return password_strength[min(3, int(score))]\n\n\nPW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)\n\n\nclass PasswordLayout(object):\n\n    titles = [_(\"Enter Password\"), _(\"Change Password\"), _(\"Enter Passphrase\")]\n\n    def __init__(self, wallet, msg, kind, OK_button):\n        self.wallet = wallet\n\n        self.pw = QLineEdit()\n        self.pw.setEchoMode(2)\n        self.new_pw = QLineEdit()\n        self.new_pw.setEchoMode(2)\n        self.conf_pw = QLineEdit()\n        self.conf_pw.setEchoMode(2)\n        self.kind = kind\n        self.OK_button = OK_button\n\n        vbox = QVBoxLayout()\n        label = QLabel(msg + \"\\n\")\n        label.setWordWrap(True)\n\n        grid = QGridLayout()\n        grid.setSpacing(8)\n        grid.setColumnMinimumWidth(0, 150)\n        grid.setColumnMinimumWidth(1, 100)\n        grid.setColumnStretch(1,1)\n\n        if kind == PW_PASSPHRASE:\n            vbox.addWidget(label)\n            msgs = [_('Passphrase:'), _('Confirm Passphrase:')]\n        else:\n            logo_grid = QGridLayout()\n            logo_grid.setSpacing(8)\n            logo_grid.setColumnMinimumWidth(0, 70)\n            logo_grid.setColumnStretch(1,1)\n\n            logo = QLabel()\n            logo.setAlignment(Qt.AlignCenter)\n\n            logo_grid.addWidget(logo,  0, 0)\n            logo_grid.addWidget(label, 0, 1, 1, 2)\n            vbox.addLayout(logo_grid)\n\n            m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')\n            msgs = [m1, _('Confirm Password:')]\n            if wallet and wallet.has_password():\n                grid.addWidget(QLabel(_('Current Password:')), 0, 0)\n                grid.addWidget(self.pw, 0, 1)\n                lockfile = \":icons/lock.png\"\n            else:\n                lockfile = \":icons/unlock.png\"\n            logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))\n\n        grid.addWidget(QLabel(msgs[0]), 1, 0)\n        grid.addWidget(self.new_pw, 1, 1)\n\n        grid.addWidget(QLabel(msgs[1]), 2, 0)\n        grid.addWidget(self.conf_pw, 2, 1)\n        vbox.addLayout(grid)\n\n        # Password Strength Label\n        if kind != PW_PASSPHRASE:\n            self.pw_strength = QLabel()\n            grid.addWidget(self.pw_strength, 3, 0, 1, 2)\n            self.new_pw.textChanged.connect(self.pw_changed)\n\n        self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))\n        self.encrypt_cb.setEnabled(False)\n        grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)\n        self.encrypt_cb.setVisible(kind != PW_PASSPHRASE)\n\n        def enable_OK():\n            ok = self.new_pw.text() == self.conf_pw.text()\n            OK_button.setEnabled(ok)\n            self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text()))\n        self.new_pw.textChanged.connect(enable_OK)\n        self.conf_pw.textChanged.connect(enable_OK)\n\n        self.vbox = vbox\n\n    def title(self):\n        return self.titles[self.kind]\n\n    def layout(self):\n        return self.vbox\n\n    def pw_changed(self):\n        password = self.new_pw.text()\n        if password:\n            colors = {\"Weak\":\"Red\", \"Medium\":\"Blue\", \"Strong\":\"Green\",\n                      \"Very Strong\":\"Green\"}\n            strength = check_password_strength(password)\n            label = (_(\"Password Strength\") + \": \" + \"<font color=\"\n                     + colors[strength] + \">\" + strength + \"</font>\")\n        else:\n            label = \"\"\n        self.pw_strength.setText(label)\n\n    def old_password(self):\n        if self.kind == PW_CHANGE:\n            return self.pw.text() or None\n        return None\n\n    def new_password(self):\n        pw = self.new_pw.text()\n        # Empty passphrases are fine and returned empty.\n        if pw == \"\" and self.kind != PW_PASSPHRASE:\n            pw = None\n        return pw\n\n\nclass ChangePasswordDialog(WindowModalDialog):\n\n    def __init__(self, parent, wallet):\n        WindowModalDialog.__init__(self, parent)\n        is_encrypted = wallet.storage.is_encrypted()\n        if not wallet.has_password():\n            msg = _('Your wallet is not protected.')\n            msg += ' ' + _('Use this dialog to add a password to your wallet.')\n        else:\n            if not is_encrypted:\n                msg = _('Your bitcoins are password protected. However, your wallet file is not encrypted.')\n            else:\n                msg = _('Your wallet is password protected and encrypted.')\n            msg += ' ' + _('Use this dialog to change your password.')\n        OK_button = OkButton(self)\n        self.playout = PasswordLayout(wallet, msg, PW_CHANGE, OK_button)\n        self.setWindowTitle(self.playout.title())\n        vbox = QVBoxLayout(self)\n        vbox.addLayout(self.playout.layout())\n        vbox.addStretch(1)\n        vbox.addLayout(Buttons(CancelButton(self), OK_button))\n        self.playout.encrypt_cb.setChecked(is_encrypted or not wallet.has_password())\n\n    def run(self):\n        if not self.exec_():\n            return False, None, None, None\n        return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()\n\n\nclass PasswordDialog(WindowModalDialog):\n\n    def __init__(self, parent=None, msg=None):\n        msg = msg or _('Please enter your password')\n        WindowModalDialog.__init__(self, parent, _(\"Enter Password\"))\n        self.pw = pw = QLineEdit()\n        pw.setEchoMode(2)\n        vbox = QVBoxLayout()\n        vbox.addWidget(QLabel(msg))\n        grid = QGridLayout()\n        grid.setSpacing(8)\n        grid.addWidget(QLabel(_('Password')), 1, 0)\n        grid.addWidget(pw, 1, 1)\n        vbox.addLayout(grid)\n        vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))\n        self.setLayout(vbox)\n        run_hook('password_dialog', pw, grid, 1)\n\n    def run(self):\n        if not self.exec_():\n            return\n        return self.pw.text()\n"
  },
  {
    "path": "gui/qt/paytoedit.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import QCompleter, QPlainTextEdit\nfrom .qrtextedit import ScanQRTextEdit\n\nimport re\nfrom decimal import Decimal\nfrom electrum import bitcoin\n\nfrom . import util\n\nRE_ADDRESS = '[1-9A-HJ-NP-Za-km-z]{26,}'\nRE_ALIAS = '(.*?)\\s*\\<([1-9A-HJ-NP-Za-km-z]{26,})\\>'\n\nfrozen_style = \"QWidget { background-color:none; border:none;}\"\nnormal_style = \"QPlainTextEdit { }\"\n\nclass PayToEdit(ScanQRTextEdit):\n\n    def __init__(self, win):\n        ScanQRTextEdit.__init__(self)\n        self.win = win\n        self.amount_edit = win.amount_e\n        self.document().contentsChanged.connect(self.update_size)\n        self.heightMin = 0\n        self.heightMax = 150\n        self.c = None\n        self.textChanged.connect(self.check_text)\n        self.outputs = []\n        self.errors = []\n        self.is_pr = False\n        self.is_alias = False\n        self.scan_f = win.pay_to_URI\n        self.update_size()\n        self.payto_address = None\n\n        self.previous_payto = ''\n\n    def setFrozen(self, b):\n        self.setReadOnly(b)\n        self.setStyleSheet(frozen_style if b else normal_style)\n        for button in self.buttons:\n            button.setHidden(b)\n\n    def setGreen(self):\n        self.setStyleSheet(util.ColorScheme.GREEN.as_stylesheet(True))\n\n    def setExpired(self):\n        self.setStyleSheet(util.ColorScheme.RED.as_stylesheet(True))\n\n    def parse_address_and_amount(self, line):\n        x, y = line.split(',')\n        out_type, out = self.parse_output(x)\n        amount = self.parse_amount(y)\n        return out_type, out, amount\n\n    def parse_output(self, x):\n        try:\n            address = self.parse_address(x)\n            return bitcoin.TYPE_ADDRESS, address\n        except:\n            script = self.parse_script(x)\n            return bitcoin.TYPE_SCRIPT, script\n\n    def parse_script(self, x):\n        from electrum.transaction import opcodes, push_script\n        script = ''\n        for word in x.split():\n            if word[0:3] == 'OP_':\n                assert word in opcodes.lookup\n                script += chr(opcodes.lookup[word])\n            else:\n                script += push_script(word).decode('hex')\n        return script\n\n    def parse_amount(self, x):\n        if x.strip() == '!':\n            return '!'\n        p = pow(10, self.amount_edit.decimal_point())\n        return int(p * Decimal(x.strip()))\n\n    def parse_address(self, line):\n        r = line.strip()\n        m = re.match('^'+RE_ALIAS+'$', r)\n        address = str(m.group(2) if m else r)\n        assert bitcoin.is_address(address)\n        return address\n\n    def check_text(self):\n        self.errors = []\n        if self.is_pr:\n            return\n        # filter out empty lines\n        lines = [i for i in self.lines() if i]\n        outputs = []\n        total = 0\n        self.payto_address = None\n        if len(lines) == 1:\n            data = lines[0]\n            if data.startswith(\"bitcoin:\"):\n                self.scan_f(data)\n                return\n            try:\n                self.payto_address = self.parse_output(data)\n            except:\n                pass\n            if self.payto_address:\n                self.win.lock_amount(False)\n                return\n\n        is_max = False\n        for i, line in enumerate(lines):\n            try:\n                _type, to_address, amount = self.parse_address_and_amount(line)\n            except:\n                self.errors.append((i, line.strip()))\n                continue\n\n            outputs.append((_type, to_address, amount))\n            if amount == '!':\n                is_max = True\n            else:\n                total += amount\n\n        self.win.is_max = is_max\n        self.outputs = outputs\n        self.payto_address = None\n\n        if self.win.is_max:\n            self.win.do_update_fee()\n        else:\n            self.amount_edit.setAmount(total if outputs else None)\n            self.win.lock_amount(total or len(lines)>1)\n\n    def get_errors(self):\n        return self.errors\n\n    def get_recipient(self):\n        return self.payto_address\n\n    def get_outputs(self, is_max):\n        if self.payto_address:\n            if is_max:\n                amount = '!'\n            else:\n                amount = self.amount_edit.get_amount()\n\n            _type, addr = self.payto_address\n            self.outputs = [(_type, addr, amount)]\n\n        return self.outputs[:]\n\n    def lines(self):\n        return self.toPlainText().split('\\n')\n\n    def is_multiline(self):\n        return len(self.lines()) > 1\n\n    def paytomany(self):\n        self.setText(\"\\n\\n\\n\")\n        self.update_size()\n\n    def update_size(self):\n        lineHeight = QFontMetrics(self.document().defaultFont()).height()\n        docHeight = self.document().size().height()\n        h = docHeight * lineHeight + 11\n        if self.heightMin <= h <= self.heightMax:\n            self.setMinimumHeight(h)\n            self.setMaximumHeight(h)\n        self.verticalScrollBar().hide()\n\n\n    def setCompleter(self, completer):\n        self.c = completer\n        self.c.setWidget(self)\n        self.c.setCompletionMode(QCompleter.PopupCompletion)\n        self.c.activated.connect(self.insertCompletion)\n\n\n    def insertCompletion(self, completion):\n        if self.c.widget() != self:\n            return\n        tc = self.textCursor()\n        extra = len(completion) - len(self.c.completionPrefix())\n        tc.movePosition(QTextCursor.Left)\n        tc.movePosition(QTextCursor.EndOfWord)\n        tc.insertText(completion[-extra:])\n        self.setTextCursor(tc)\n\n\n    def textUnderCursor(self):\n        tc = self.textCursor()\n        tc.select(QTextCursor.WordUnderCursor)\n        return tc.selectedText()\n\n\n    def keyPressEvent(self, e):\n        if self.isReadOnly():\n            return\n\n        if self.c.popup().isVisible():\n            if e.key() in [Qt.Key_Enter, Qt.Key_Return]:\n                e.ignore()\n                return\n\n        if e.key() in [Qt.Key_Tab]:\n            e.ignore()\n            return\n\n        if e.key() in [Qt.Key_Down, Qt.Key_Up] and not self.is_multiline():\n            e.ignore()\n            return\n\n        QPlainTextEdit.keyPressEvent(self, e)\n\n        ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)\n        if self.c is None or (ctrlOrShift and not e.text()):\n            return\n\n        eow = \"~!@#$%^&*()_+{}|:\\\"<>?,./;'[]\\\\-=\"\n        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift\n        completionPrefix = self.textUnderCursor()\n\n        if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0:\n            self.c.popup().hide()\n            return\n\n        if completionPrefix != self.c.completionPrefix():\n            self.c.setCompletionPrefix(completionPrefix)\n            self.c.popup().setCurrentIndex(self.c.completionModel().index(0, 0))\n\n        cr = self.cursorRect()\n        cr.setWidth(self.c.popup().sizeHintForColumn(0) + self.c.popup().verticalScrollBar().sizeHint().width())\n        self.c.complete(cr)\n\n    def qr_input(self):\n        data = super(PayToEdit,self).qr_input()\n        if data.startswith(\"bitcoin:\"):\n            self.scan_f(data)\n            # TODO: update fee\n\n    def resolve(self):\n        self.is_alias = False\n        if self.hasFocus():\n            return\n        if self.is_multiline():  # only supports single line entries atm\n            return\n        if self.is_pr:\n            return\n        key = str(self.toPlainText())\n        if key == self.previous_payto:\n            return\n        self.previous_payto = key\n        if not (('.' in key) and (not '<' in key) and (not ' ' in key)):\n            return\n        parts = key.split(sep=',')  # assuming single line\n        if parts and len(parts) > 0 and bitcoin.is_address(parts[0]):\n            return\n        try:\n            data = self.win.contacts.resolve(key)\n        except:\n            return\n        if not data:\n            return\n        self.is_alias = True\n\n        address = data.get('address')\n        name = data.get('name')\n        new_url = key + ' <' + address + '>'\n        self.setText(new_url)\n        self.previous_payto = new_url\n\n        #if self.win.config.get('openalias_autoadd') == 'checked':\n        self.win.contacts[key] = ('openalias', name)\n        self.win.contact_list.on_update()\n\n        self.setFrozen(True)\n        if data.get('type') == 'openalias':\n            self.validated = data.get('validated')\n            if self.validated:\n                self.setGreen()\n            else:\n                self.setExpired()\n        else:\n            self.validated = None\n"
  },
  {
    "path": "gui/qt/qrcodewidget.py",
    "content": "\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nimport PyQt5.QtGui as QtGui\nfrom PyQt5.QtWidgets import (\n    QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget)\n\nimport os\nimport qrcode\n\nimport electrum\nfrom electrum.i18n import _\nfrom .util import WindowModalDialog\n\n\nclass QRCodeWidget(QWidget):\n\n    def __init__(self, data = None, fixedSize=False):\n        QWidget.__init__(self)\n        self.data = None\n        self.qr = None\n        self.fixedSize=fixedSize\n        if fixedSize:\n            self.setFixedSize(fixedSize, fixedSize)\n        self.setData(data)\n\n\n    def setData(self, data):\n        if self.data != data:\n            self.data = data\n        if self.data:\n            self.qr = qrcode.QRCode()\n            self.qr.add_data(self.data)\n            if not self.fixedSize:\n                k = len(self.qr.get_matrix())\n                self.setMinimumSize(k*5,k*5)\n        else:\n            self.qr = None\n\n        self.update()\n\n\n    def paintEvent(self, e):\n        if not self.data:\n            return\n\n        black = QColor(0, 0, 0, 255)\n        white = QColor(255, 255, 255, 255)\n\n        if not self.qr:\n            qp = QtGui.QPainter()\n            qp.begin(self)\n            qp.setBrush(white)\n            qp.setPen(white)\n            r = qp.viewport()\n            qp.drawRect(0, 0, r.width(), r.height())\n            qp.end()\n            return\n\n        matrix = self.qr.get_matrix()\n        k = len(matrix)\n        qp = QtGui.QPainter()\n        qp.begin(self)\n        r = qp.viewport()\n\n        margin = 10\n        framesize = min(r.width(), r.height())\n        boxsize = int( (framesize - 2*margin)/k )\n        size = k*boxsize\n        left = (r.width() - size)/2\n        top = (r.height() - size)/2\n\n        # Make a white margin around the QR in case of dark theme use\n        qp.setBrush(white)\n        qp.setPen(white)\n        qp.drawRect(left-margin, top-margin, size+(margin*2), size+(margin*2))\n        qp.setBrush(black)\n        qp.setPen(black)\n\n        for r in range(k):\n            for c in range(k):\n                if matrix[r][c]:\n                    qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)\n        qp.end()\n\n\n\nclass QRDialog(WindowModalDialog):\n\n    def __init__(self, data, parent=None, title = \"\", show_text=False):\n        WindowModalDialog.__init__(self, parent, title)\n\n        vbox = QVBoxLayout()\n        qrw = QRCodeWidget(data)\n        qscreen = QApplication.primaryScreen()\n        vbox.addWidget(qrw, 1)\n        if show_text:\n            text = QTextEdit()\n            text.setText(data)\n            text.setReadOnly(True)\n            vbox.addWidget(text)\n        hbox = QHBoxLayout()\n        hbox.addStretch(1)\n\n        config = electrum.get_config()\n        if config:\n            filename = os.path.join(config.path, \"qrcode.png\")\n\n            def print_qr():\n                p = qscreen.grabWindow(qrw.winId())\n                p.save(filename, 'png')\n                self.show_message(_(\"QR code saved to file\") + \" \" + filename)\n\n            def copy_to_clipboard():\n                p = qscreen.grabWindow(qrw.winId())\n                QApplication.clipboard().setPixmap(p)\n                self.show_message(_(\"QR code copied to clipboard\"))\n\n            b = QPushButton(_(\"Copy\"))\n            hbox.addWidget(b)\n            b.clicked.connect(copy_to_clipboard)\n\n            b = QPushButton(_(\"Save\"))\n            hbox.addWidget(b)\n            b.clicked.connect(print_qr)\n\n        b = QPushButton(_(\"Close\"))\n        hbox.addWidget(b)\n        b.clicked.connect(self.accept)\n        b.setDefault(True)\n\n        vbox.addLayout(hbox)\n        self.setLayout(vbox)\n"
  },
  {
    "path": "gui/qt/qrtextedit.py",
    "content": "\nfrom electrum.i18n import _\nfrom electrum.plugins import run_hook\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import QFileDialog\n\nfrom .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme\n\n\nclass ShowQRTextEdit(ButtonsTextEdit):\n\n    def __init__(self, text=None):\n        ButtonsTextEdit.__init__(self, text)\n        self.setReadOnly(1)\n        self.addButton(\":icons/qrcode.png\", self.qr_show, _(\"Show as QR code\"))\n\n        run_hook('show_text_edit', self)\n\n    def qr_show(self):\n        from .qrcodewidget import QRDialog\n        try:\n            s = str(self.toPlainText())\n        except:\n            s = self.toPlainText()\n        QRDialog(s).exec_()\n\n    def contextMenuEvent(self, e):\n        m = self.createStandardContextMenu()\n        m.addAction(_(\"Show as QR code\"), self.qr_show)\n        m.exec_(e.globalPos())\n\n\nclass ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):\n\n    def __init__(self, text=\"\", allow_multi=False):\n        ButtonsTextEdit.__init__(self, text)\n        self.allow_multi = allow_multi\n        self.setReadOnly(0)\n        self.addButton(\":icons/file.png\", self.file_input, _(\"Read file\"))\n        icon = \":icons/qrcode_white.png\" if ColorScheme.dark_scheme else \":icons/qrcode.png\"\n        self.addButton(icon, self.qr_input, _(\"Read QR code\"))\n        run_hook('scan_text_edit', self)\n\n    def file_input(self):\n        fileName, __ = QFileDialog.getOpenFileName(self, 'select file')\n        if not fileName:\n            return\n        with open(fileName, \"r\") as f:\n            data = f.read()\n        self.setText(data)\n\n    def qr_input(self):\n        from electrum import qrscanner, get_config\n        try:\n            data = qrscanner.scan_barcode(get_config().get_video_device())\n        except BaseException as e:\n            self.show_error(str(e))\n            data = ''\n        if not data:\n            data = ''\n        if self.allow_multi:\n            new_text = self.text() + data + '\\n'\n        else:\n            new_text = data\n        self.setText(new_text)\n        return data\n\n    def contextMenuEvent(self, e):\n        m = self.createStandardContextMenu()\n        m.addAction(_(\"Read QR code\"), self.qr_input)\n        m.exec_(e.globalPos())\n"
  },
  {
    "path": "gui/qt/qrwindow.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport platform\n\nfrom PyQt5.QtCore import Qt\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget\n\nfrom electrum_gui.qt.qrcodewidget import QRCodeWidget\nfrom electrum.i18n import _\n\nif platform.system() == 'Windows':\n    MONOSPACE_FONT = 'Lucida Console'\nelif platform.system() == 'Darwin':\n    MONOSPACE_FONT = 'Monaco'\nelse:\n    MONOSPACE_FONT = 'monospace'\n\ncolumn_index = 4\n\nclass QR_Window(QWidget):\n\n    def __init__(self, win):\n        QWidget.__init__(self)\n        self.win = win\n        self.setWindowTitle('Electrum - '+_('Payment Request'))\n        self.setMinimumSize(800, 250)\n        self.address = ''\n        self.label = ''\n        self.amount = 0\n        self.setFocusPolicy(Qt.NoFocus)\n\n        main_box = QHBoxLayout()\n\n        self.qrw = QRCodeWidget()\n        main_box.addWidget(self.qrw, 1)\n\n        vbox = QVBoxLayout()\n        main_box.addLayout(vbox)\n\n        self.address_label = QLabel(\"\")\n        #self.address_label.setFont(QFont(MONOSPACE_FONT))\n        vbox.addWidget(self.address_label)\n\n        self.label_label = QLabel(\"\")\n        vbox.addWidget(self.label_label)\n\n        self.amount_label = QLabel(\"\")\n        vbox.addWidget(self.amount_label)\n\n        vbox.addStretch(1)\n        self.setLayout(main_box)\n\n\n    def set_content(self, address, amount, message, url):\n        address_text = \"<span style='font-size: 18pt'>%s</span>\" % address if address else \"\"\n        self.address_label.setText(address_text)\n        if amount:\n            amount = self.win.format_amount(amount)\n            amount_text = \"<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>%s</span> \" % (amount, self.win.base_unit())\n        else:\n            amount_text = ''\n        self.amount_label.setText(amount_text)\n        label_text = \"<span style='font-size: 21pt'>%s</span>\" % message if message else \"\"\n        self.label_label.setText(label_text)\n        self.qrw.setData(url)\n"
  },
  {
    "path": "gui/qt/request_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom electrum.i18n import _\nfrom electrum.util import format_time, age\nfrom electrum.plugins import run_hook\nfrom electrum.paymentrequest import PR_UNKNOWN\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import QTreeWidgetItem, QMenu\nfrom .util import MyTreeWidget, pr_tooltips, pr_icons\n\n\nclass RequestList(MyTreeWidget):\n    filter_columns = [0, 1, 2, 3, 4]  # Date, Account, Address, Description, Amount\n\n\n    def __init__(self, parent):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3)\n        self.currentItemChanged.connect(self.item_changed)\n        self.itemClicked.connect(self.item_changed)\n        self.setSortingEnabled(True)\n        self.setColumnWidth(0, 180)\n        self.hideColumn(1)\n\n    def item_changed(self, item):\n        if item is None:\n            return\n        if not item.isSelected():\n            return\n        addr = str(item.text(1))\n        req = self.wallet.receive_requests[addr]\n        expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')\n        amount = req['amount']\n        message = self.wallet.labels.get(addr, '')\n        self.parent.receive_address_e.setText(addr)\n        self.parent.receive_message_e.setText(message)\n        self.parent.receive_amount_e.setAmount(amount)\n        self.parent.expires_combo.hide()\n        self.parent.expires_label.show()\n        self.parent.expires_label.setText(expires)\n        self.parent.new_request_button.setEnabled(True)\n\n    def on_update(self):\n        self.wallet = self.parent.wallet\n        # hide receive tab if no receive requests available\n        b = len(self.wallet.receive_requests) > 0\n        self.setVisible(b)\n        self.parent.receive_requests_label.setVisible(b)\n        if not b:\n            self.parent.expires_label.hide()\n            self.parent.expires_combo.show()\n\n        # update the receive address if necessary\n        current_address = self.parent.receive_address_e.text()\n        domain = self.wallet.get_receiving_addresses()\n        addr = self.wallet.get_unused_address()\n        if not current_address in domain and addr:\n            self.parent.set_receive_address(addr)\n        self.parent.new_request_button.setEnabled(addr != current_address)\n\n        # clear the list and fill it again\n        self.clear()\n        for req in self.wallet.get_sorted_requests(self.config):\n            address = req['address']\n            if address not in domain:\n                continue\n            timestamp = req.get('time', 0)\n            amount = req.get('amount')\n            expiration = req.get('exp', None)\n            message = req.get('memo', '')\n            date = format_time(timestamp)\n            status = req.get('status')\n            signature = req.get('sig')\n            requestor = req.get('name', '')\n            amount_str = self.parent.format_amount(amount) if amount else \"\"\n            item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])\n            if signature is not None:\n                item.setIcon(2, QIcon(\":icons/seal.png\"))\n                item.setToolTip(2, 'signed by '+ requestor)\n            if status is not PR_UNKNOWN:\n                item.setIcon(6, QIcon(pr_icons.get(status)))\n            self.addTopLevelItem(item)\n\n\n    def create_menu(self, position):\n        item = self.itemAt(position)\n        if not item:\n            return\n        addr = str(item.text(1))\n        req = self.wallet.receive_requests[addr]\n        column = self.currentColumn()\n        column_title = self.headerItem().text(column)\n        column_data = item.text(column)\n        menu = QMenu(self)\n        menu.addAction(_(\"Copy %s\")%column_title, lambda: self.parent.app.clipboard().setText(column_data))\n        menu.addAction(_(\"Copy URI\"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))\n        menu.addAction(_(\"Save as BIP70 file\"), lambda: self.parent.export_payment_request(addr))\n        menu.addAction(_(\"Delete\"), lambda: self.parent.delete_payment_request(addr))\n        run_hook('receive_list_menu', menu, addr)\n        menu.exec_(self.viewport().mapToGlobal(position))\n"
  },
  {
    "path": "gui/qt/seed_dialog.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2013 ecdsa@github\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import *\nfrom electrum.i18n import _\n\nfrom .util import *\nfrom .qrtextedit import ShowQRTextEdit, ScanQRTextEdit\n\n\ndef seed_warning_msg(seed):\n    return ''.join([\n        \"<p>\",\n        _(\"Please save these {0} words on paper (order is important). \"),\n        _(\"This seed will allow you to recover your wallet in case \"\n          \"of computer failure.\"),\n        \"</p>\",\n        \"<b>\" + _(\"WARNING\") + \":</b>\",\n        \"<ul>\",\n        \"<li>\" + _(\"Never disclose your seed.\") + \"</li>\",\n        \"<li>\" + _(\"Never type it on a website.\") + \"</li>\",\n        \"<li>\" + _(\"Do not store it electronically.\") + \"</li>\",\n        \"</ul>\"\n    ]).format(len(seed.split()))\n\n\nclass SeedLayout(QVBoxLayout):\n    #options\n    is_bip39 = False\n    is_ext = False\n\n    def seed_options(self):\n        dialog = QDialog()\n        vbox = QVBoxLayout(dialog)\n        if 'ext' in self.options:\n            cb_ext = QCheckBox(_('Extend this seed with custom words'))\n            cb_ext.setChecked(self.is_ext)\n            vbox.addWidget(cb_ext)\n        if 'bip39' in self.options:\n            def f(b):\n                self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed\n                self.is_bip39 = b\n                self.on_edit()\n                if b:\n                    msg = ' '.join([\n                        '<b>' + _('Warning') + ':</b>  ',\n                        _('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),\n                        _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),\n                        _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),\n                        _('We do not guarantee that BIP39 imports will always be supported in Electrum.'),\n                    ])\n                else:\n                    msg = ''\n                self.seed_warning.setText(msg)\n            cb_bip39 = QCheckBox(_('BIP39 seed'))\n            cb_bip39.toggled.connect(f)\n            cb_bip39.setChecked(self.is_bip39)\n            vbox.addWidget(cb_bip39)\n        vbox.addLayout(Buttons(OkButton(dialog)))\n        if not dialog.exec_():\n            return None\n        self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False\n        self.is_bip39 = cb_bip39.isChecked() if 'bip39' in self.options else False\n\n    def __init__(self, seed=None, title=None, icon=True, msg=None, options=None, is_seed=None, passphrase=None, parent=None):\n        QVBoxLayout.__init__(self)\n        self.parent = parent\n        self.options = options\n        if title:\n            self.addWidget(WWLabel(title))\n        self.seed_e = ButtonsTextEdit()\n        if seed:\n            self.seed_e.setText(seed)\n        else:\n            self.seed_e.setTabChangesFocus(True)\n            self.is_seed = is_seed\n            self.saved_is_seed = self.is_seed\n            self.seed_e.textChanged.connect(self.on_edit)\n        self.seed_e.setMaximumHeight(75)\n        hbox = QHBoxLayout()\n        if icon:\n            logo = QLabel()\n            logo.setPixmap(QPixmap(\":icons/seed.png\").scaledToWidth(64))\n            logo.setMaximumWidth(60)\n            hbox.addWidget(logo)\n        hbox.addWidget(self.seed_e)\n        self.addLayout(hbox)\n        hbox = QHBoxLayout()\n        hbox.addStretch(1)\n        self.seed_type_label = QLabel('')\n        hbox.addWidget(self.seed_type_label)\n        if options:\n            opt_button = EnterButton(_('Options'), self.seed_options)\n            hbox.addWidget(opt_button)\n            self.addLayout(hbox)\n        if passphrase:\n            hbox = QHBoxLayout()\n            passphrase_e = QLineEdit()\n            passphrase_e.setText(passphrase)\n            passphrase_e.setReadOnly(True)\n            hbox.addWidget(QLabel(_(\"Your seed extension is\") + ':'))\n            hbox.addWidget(passphrase_e)\n            self.addLayout(hbox)\n        self.addStretch(1)\n        self.seed_warning = WWLabel('')\n        if msg:\n            self.seed_warning.setText(seed_warning_msg(seed))\n        self.addWidget(self.seed_warning)\n\n    def get_seed(self):\n        text = self.seed_e.text()\n        return ' '.join(text.split())\n\n    def on_edit(self):\n        from electrum.bitcoin import seed_type\n        s = self.get_seed()\n        b = self.is_seed(s)\n        if not self.is_bip39:\n            t = seed_type(s)\n            label = _('Seed Type') + ': ' + t if t else ''\n        else:\n            from electrum.keystore import bip39_is_checksum_valid\n            is_checksum, is_wordlist = bip39_is_checksum_valid(s)\n            status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'\n            label = 'BIP39' + ' (%s)'%status\n        self.seed_type_label.setText(label)\n        self.parent.next_button.setEnabled(b)\n\n\nclass KeysLayout(QVBoxLayout):\n    def __init__(self, parent=None, title=None, is_valid=None, allow_multi=False):\n        QVBoxLayout.__init__(self)\n        self.parent = parent\n        self.is_valid = is_valid\n        self.text_e = ScanQRTextEdit(allow_multi=allow_multi)\n        self.text_e.textChanged.connect(self.on_edit)\n        self.addWidget(WWLabel(title))\n        self.addWidget(self.text_e)\n\n    def get_text(self):\n        return self.text_e.text()\n\n    def on_edit(self):\n        b = self.is_valid(self.get_text())\n        self.parent.next_button.setEnabled(b)\n\n\nclass SeedDialog(WindowModalDialog):\n\n    def __init__(self, parent, seed, passphrase):\n        WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))\n        self.setMinimumWidth(400)\n        vbox = QVBoxLayout(self)\n        title =  _(\"Your wallet generation seed is:\")\n        slayout = SeedLayout(title=title, seed=seed, msg=True, passphrase=passphrase)\n        vbox.addLayout(slayout)\n        vbox.addLayout(Buttons(CloseButton(self)))\n"
  },
  {
    "path": "gui/qt/transaction_dialog.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport copy\nimport datetime\nimport json\n\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import *\n\nfrom electrum.bitcoin import base_encode\nfrom electrum.i18n import _\nfrom electrum.plugins import run_hook\n\nfrom electrum.util import bfh\nfrom .util import *\n\ndialogs = []  # Otherwise python randomly garbage collects the dialogs...\n\ndef show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):\n    d = TxDialog(tx, parent, desc, prompt_if_unsaved)\n    dialogs.append(d)\n    d.show()\n\nclass TxDialog(QDialog, MessageBoxMixin):\n\n    def __init__(self, tx, parent, desc, prompt_if_unsaved):\n        '''Transactions in the wallet will show their description.\n        Pass desc to give a description for txs not yet in the wallet.\n        '''\n        # We want to be a top-level window\n        QDialog.__init__(self, parent=None)\n        # Take a copy; it might get updated in the main window by\n        # e.g. the FX plugin.  If this happens during or after a long\n        # sign operation the signatures are lost.\n        self.tx = copy.deepcopy(tx)\n        self.tx.deserialize()\n        self.main_window = parent\n        self.wallet = parent.wallet\n        self.prompt_if_unsaved = prompt_if_unsaved\n        self.saved = False\n        self.desc = desc\n\n        self.setMinimumWidth(750)\n        self.setWindowTitle(_(\"Transaction\"))\n\n        vbox = QVBoxLayout()\n        self.setLayout(vbox)\n\n        vbox.addWidget(QLabel(_(\"Transaction ID:\")))\n        self.tx_hash_e  = ButtonsLineEdit()\n        qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)\n        self.tx_hash_e.addButton(\":icons/qrcode.png\", qr_show, _(\"Show as QR code\"))\n        self.tx_hash_e.setReadOnly(True)\n        vbox.addWidget(self.tx_hash_e)\n        self.tx_desc = QLabel()\n        vbox.addWidget(self.tx_desc)\n        self.status_label = QLabel()\n        vbox.addWidget(self.status_label)\n        self.date_label = QLabel()\n        vbox.addWidget(self.date_label)\n        self.amount_label = QLabel()\n        vbox.addWidget(self.amount_label)\n        self.size_label = QLabel()\n        vbox.addWidget(self.size_label)\n        self.fee_label = QLabel()\n        vbox.addWidget(self.fee_label)\n\n        self.add_io(vbox)\n\n        vbox.addStretch(1)\n\n        self.sign_button = b = QPushButton(_(\"Sign\"))\n        b.clicked.connect(self.sign)\n\n        self.broadcast_button = b = QPushButton(_(\"Broadcast\"))\n        b.clicked.connect(self.do_broadcast)\n\n        self.save_button = b = QPushButton(_(\"Save\"))\n        b.clicked.connect(self.save)\n\n        self.cancel_button = b = QPushButton(_(\"Close\"))\n        b.clicked.connect(self.close)\n        b.setDefault(True)\n\n        self.qr_button = b = QPushButton()\n        b.setIcon(QIcon(\":icons/qrcode.png\"))\n        b.clicked.connect(self.show_qr)\n\n        self.copy_button = CopyButton(lambda: str(self.tx), parent.app)\n\n        # Action buttons\n        self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]\n        # Transaction sharing buttons\n        self.sharing_buttons = [self.copy_button, self.qr_button, self.save_button]\n\n        run_hook('transaction_dialog', self)\n\n        hbox = QHBoxLayout()\n        hbox.addLayout(Buttons(*self.sharing_buttons))\n        hbox.addStretch(1)\n        hbox.addLayout(Buttons(*self.buttons))\n        vbox.addLayout(hbox)\n        self.update()\n\n    def do_broadcast(self):\n        self.main_window.push_top_level_window(self)\n        try:\n            self.main_window.broadcast_transaction(self.tx, self.desc)\n        finally:\n            self.main_window.pop_top_level_window(self)\n        self.saved = True\n        self.update()\n\n    def closeEvent(self, event):\n        if (self.prompt_if_unsaved and not self.saved\n            and not self.question(_('This transaction is not saved. Close anyway?'), title=_(\"Warning\"))):\n            event.ignore()\n        else:\n            event.accept()\n            dialogs.remove(self)\n\n    def show_qr(self):\n        text = bfh(str(self.tx))\n        text = base_encode(text, base=43)\n        try:\n            self.main_window.show_qrcode(text, 'Transaction', parent=self)\n        except Exception as e:\n            self.show_message(str(e))\n\n    def sign(self):\n        def sign_done(success):\n            if success:\n                self.prompt_if_unsaved = True\n                self.saved = False\n            self.update()\n            self.main_window.pop_top_level_window(self)\n\n        self.sign_button.setDisabled(True)\n        self.main_window.push_top_level_window(self)\n        self.main_window.sign_tx(self.tx, sign_done)\n\n    def save(self):\n        name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'\n        fileName = self.main_window.getSaveFileName(_(\"Select where to save your signed transaction\"), name, \"*.txn\")\n        if fileName:\n            with open(fileName, \"w+\") as f:\n                f.write(json.dumps(self.tx.as_dict(), indent=4) + '\\n')\n            self.show_message(_(\"Transaction saved successfully\"))\n            self.saved = True\n\n    def update(self):\n        desc = self.desc\n        base_unit = self.main_window.base_unit()\n        format_amount = self.main_window.format_amount\n        tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)\n        size = self.tx.estimated_size()\n        self.broadcast_button.setEnabled(can_broadcast)\n        can_sign = not self.tx.is_complete() and \\\n            (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))\n        self.sign_button.setEnabled(can_sign)\n        self.tx_hash_e.setText(tx_hash or _('Unknown'))\n        if desc is None:\n            self.tx_desc.hide()\n        else:\n            self.tx_desc.setText(_(\"Description\") + ': ' + desc)\n            self.tx_desc.show()\n        self.status_label.setText(_('Status:') + ' ' + status)\n\n        if timestamp:\n            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]\n            self.date_label.setText(_(\"Date: %s\")%time_str)\n            self.date_label.show()\n        elif exp_n:\n            text = '%d blocks'%(exp_n) if exp_n > 0 else _('unknown (low fee)')\n            self.date_label.setText(_('Expected confirmation time') + ': ' + text)\n            self.date_label.show()\n        else:\n            self.date_label.hide()\n        if amount is None:\n            amount_str = _(\"Transaction unrelated to your wallet\")\n        elif amount > 0:\n            amount_str = _(\"Amount received:\") + ' %s'% format_amount(amount) + ' ' + base_unit\n        else:\n            amount_str = _(\"Amount sent:\") + ' %s'% format_amount(-amount) + ' ' + base_unit\n        size_str = _(\"Size:\") + ' %d bytes'% size\n        fee_str = _(\"Fee\") + ': %s'% (format_amount(fee) + ' ' + base_unit if fee is not None else _('Unknown'))\n        if fee is not None:\n            fee_str += '  ( %s ) '%  self.main_window.format_fee_rate(fee/size*1000)\n        self.amount_label.setText(amount_str)\n        self.fee_label.setText(fee_str)\n        self.size_label.setText(size_str)\n        run_hook('transaction_dialog_update', self)\n\n    def add_io(self, vbox):\n        if self.tx.locktime > 0:\n            vbox.addWidget(QLabel(\"LockTime: %d\\n\" % self.tx.locktime))\n\n        vbox.addWidget(QLabel(_(\"Inputs\") + ' (%d)'%len(self.tx.inputs())))\n        ext = QTextCharFormat()\n        rec = QTextCharFormat()\n        rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))\n        rec.setToolTip(_(\"Wallet receive address\"))\n        chg = QTextCharFormat()\n        chg.setBackground(QBrush(QColor(\"yellow\")))\n        chg.setToolTip(_(\"Wallet change address\"))\n\n        def text_format(addr):\n            if self.wallet.is_mine(addr):\n                return chg if self.wallet.is_change(addr) else rec\n            return ext\n\n        def format_amount(amt):\n            return self.main_window.format_amount(amt, whitespaces = True)\n\n        i_text = QTextEdit()\n        i_text.setFont(QFont(MONOSPACE_FONT))\n        i_text.setReadOnly(True)\n        i_text.setMaximumHeight(100)\n        cursor = i_text.textCursor()\n        for x in self.tx.inputs():\n            if x['type'] == 'coinbase':\n                cursor.insertText('coinbase')\n            else:\n                prevout_hash = x.get('prevout_hash')\n                prevout_n = x.get('prevout_n')\n                cursor.insertText(prevout_hash[0:8] + '...', ext)\n                cursor.insertText(prevout_hash[-8:] + \":%-4d \" % prevout_n, ext)\n                addr = x.get('address')\n                if addr == \"(pubkey)\":\n                    _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n)\n                    if _addr:\n                        addr = _addr\n                if addr is None:\n                    addr = _('Unknown')\n                cursor.insertText(addr, text_format(addr))\n                if x.get('value'):\n                    cursor.insertText(format_amount(x['value']), ext)\n            cursor.insertBlock()\n\n        vbox.addWidget(i_text)\n        vbox.addWidget(QLabel(_(\"Outputs\") + ' (%d)'%len(self.tx.outputs())))\n        o_text = QTextEdit()\n        o_text.setFont(QFont(MONOSPACE_FONT))\n        o_text.setReadOnly(True)\n        o_text.setMaximumHeight(100)\n        cursor = o_text.textCursor()\n        for addr, v in self.tx.get_outputs():\n            cursor.insertText(addr, text_format(addr))\n            if v is not None:\n                cursor.insertText('\\t', ext)\n                cursor.insertText(format_amount(v), ext)\n            cursor.insertBlock()\n        vbox.addWidget(o_text)\n"
  },
  {
    "path": "gui/qt/util.py",
    "content": "import os.path\nimport time\nimport sys\nimport platform\nimport queue\nfrom collections import namedtuple\nfrom functools import partial\n\nfrom electrum.i18n import _\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import *\n\nif platform.system() == 'Windows':\n    MONOSPACE_FONT = 'Lucida Console'\nelif platform.system() == 'Darwin':\n    MONOSPACE_FONT = 'Monaco'\nelse:\n    MONOSPACE_FONT = 'monospace'\n\n\ndialogs = []\n\nfrom electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED\n\npr_icons = {\n    PR_UNPAID:\":icons/unpaid.png\",\n    PR_PAID:\":icons/confirmed.png\",\n    PR_EXPIRED:\":icons/expired.png\"\n}\n\npr_tooltips = {\n    PR_UNPAID:_('Pending'),\n    PR_PAID:_('Paid'),\n    PR_EXPIRED:_('Expired')\n}\n\nexpiration_values = [\n    (_('1 hour'), 60*60),\n    (_('1 day'), 24*60*60),\n    (_('1 week'), 7*24*60*60),\n    (_('Never'), None)\n]\n\n\nclass Timer(QThread):\n    stopped = False\n    timer_signal = pyqtSignal()\n\n    def run(self):\n        while not self.stopped:\n            self.timer_signal.emit()\n            time.sleep(0.5)\n\n    def stop(self):\n        self.stopped = True\n        self.wait()\n\nclass EnterButton(QPushButton):\n    def __init__(self, text, func):\n        QPushButton.__init__(self, text)\n        self.func = func\n        self.clicked.connect(func)\n\n    def keyPressEvent(self, e):\n        if e.key() == Qt.Key_Return:\n            self.func()\n\n\nclass ThreadedButton(QPushButton):\n    def __init__(self, text, task, on_success=None, on_error=None):\n        QPushButton.__init__(self, text)\n        self.task = task\n        self.on_success = on_success\n        self.on_error = on_error\n        self.clicked.connect(self.run_task)\n\n    def run_task(self):\n        self.setEnabled(False)\n        self.thread = TaskThread(self)\n        self.thread.add(self.task, self.on_success, self.done, self.on_error)\n\n    def done(self):\n        self.setEnabled(True)\n        self.thread.stop()\n\n\nclass WWLabel(QLabel):\n    def __init__ (self, text=\"\", parent=None):\n        QLabel.__init__(self, text, parent)\n        self.setWordWrap(True)\n\n\nclass HelpLabel(QLabel):\n\n    def __init__(self, text, help_text):\n        QLabel.__init__(self, text)\n        self.help_text = help_text\n        self.app = QCoreApplication.instance()\n        self.font = QFont()\n\n    def mouseReleaseEvent(self, x):\n        QMessageBox.information(self, 'Help', self.help_text)\n\n    def enterEvent(self, event):\n        self.font.setUnderline(True)\n        self.setFont(self.font)\n        self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))\n        return QLabel.enterEvent(self, event)\n\n    def leaveEvent(self, event):\n        self.font.setUnderline(False)\n        self.setFont(self.font)\n        self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))\n        return QLabel.leaveEvent(self, event)\n\n\nclass HelpButton(QPushButton):\n    def __init__(self, text):\n        QPushButton.__init__(self, '?')\n        self.help_text = text\n        self.setFocusPolicy(Qt.NoFocus)\n        self.setFixedWidth(20)\n        self.clicked.connect(self.onclick)\n\n    def onclick(self):\n        QMessageBox.information(self, 'Help', self.help_text)\n\nclass Buttons(QHBoxLayout):\n    def __init__(self, *buttons):\n        QHBoxLayout.__init__(self)\n        self.addStretch(1)\n        for b in buttons:\n            self.addWidget(b)\n\nclass CloseButton(QPushButton):\n    def __init__(self, dialog):\n        QPushButton.__init__(self, _(\"Close\"))\n        self.clicked.connect(dialog.close)\n        self.setDefault(True)\n\nclass CopyButton(QPushButton):\n    def __init__(self, text_getter, app):\n        QPushButton.__init__(self, _(\"Copy\"))\n        self.clicked.connect(lambda: app.clipboard().setText(text_getter()))\n\nclass CopyCloseButton(QPushButton):\n    def __init__(self, text_getter, app, dialog):\n        QPushButton.__init__(self, _(\"Copy and Close\"))\n        self.clicked.connect(lambda: app.clipboard().setText(text_getter()))\n        self.clicked.connect(dialog.close)\n        self.setDefault(True)\n\nclass OkButton(QPushButton):\n    def __init__(self, dialog, label=None):\n        QPushButton.__init__(self, label or _(\"OK\"))\n        self.clicked.connect(dialog.accept)\n        self.setDefault(True)\n\nclass CancelButton(QPushButton):\n    def __init__(self, dialog, label=None):\n        QPushButton.__init__(self, label or _(\"Cancel\"))\n        self.clicked.connect(dialog.reject)\n\nclass MessageBoxMixin(object):\n    def top_level_window_recurse(self, window=None):\n        window = window or self\n        classes = (WindowModalDialog, QMessageBox)\n        for n, child in enumerate(window.children()):\n            # Test for visibility as old closed dialogs may not be GC-ed\n            if isinstance(child, classes) and child.isVisible():\n                return self.top_level_window_recurse(child)\n        return window\n\n    def top_level_window(self):\n        return self.top_level_window_recurse()\n\n    def question(self, msg, parent=None, title=None, icon=None):\n        Yes, No = QMessageBox.Yes, QMessageBox.No\n        return self.msg_box(icon or QMessageBox.Question,\n                            parent, title or '',\n                            msg, buttons=Yes|No, defaultButton=No) == Yes\n\n    def show_warning(self, msg, parent=None, title=None):\n        return self.msg_box(QMessageBox.Warning, parent,\n                            title or _('Warning'), msg)\n\n    def show_error(self, msg, parent=None):\n        return self.msg_box(QMessageBox.Warning, parent,\n                            _('Error'), msg)\n\n    def show_critical(self, msg, parent=None, title=None):\n        return self.msg_box(QMessageBox.Critical, parent,\n                            title or _('Critical Error'), msg)\n\n    def show_message(self, msg, parent=None, title=None):\n        return self.msg_box(QMessageBox.Information, parent,\n                            title or _('Information'), msg)\n\n    def msg_box(self, icon, parent, title, text, buttons=QMessageBox.Ok,\n                defaultButton=QMessageBox.NoButton):\n        parent = parent or self.top_level_window()\n        d = QMessageBox(icon, title, str(text), buttons, parent)\n        d.setWindowModality(Qt.WindowModal)\n        d.setDefaultButton(defaultButton)\n        return d.exec_()\n\nclass WindowModalDialog(QDialog, MessageBoxMixin):\n    '''Handy wrapper; window modal dialogs are better for our multi-window\n    daemon model as other wallet windows can still be accessed.'''\n    def __init__(self, parent, title=None):\n        QDialog.__init__(self, parent)\n        self.setWindowModality(Qt.WindowModal)\n        if title:\n            self.setWindowTitle(title)\n\n\nclass WaitingDialog(WindowModalDialog):\n    '''Shows a please wait dialog whilst runnning a task.  It is not\n    necessary to maintain a reference to this dialog.'''\n    def __init__(self, parent, message, task, on_success=None, on_error=None):\n        assert parent\n        if isinstance(parent, MessageBoxMixin):\n            parent = parent.top_level_window()\n        WindowModalDialog.__init__(self, parent, _(\"Please wait...\"))\n        vbox = QVBoxLayout(self)\n        vbox.addWidget(QLabel(message))\n        self.accepted.connect(self.on_accepted)\n        self.show()\n        self.thread = TaskThread(self)\n        self.thread.add(task, on_success, self.accept, on_error)\n\n    def wait(self):\n        self.thread.wait()\n\n    def on_accepted(self):\n        self.thread.stop()\n\n\ndef line_dialog(parent, title, label, ok_label, default=None):\n    dialog = WindowModalDialog(parent, title)\n    dialog.setMinimumWidth(500)\n    l = QVBoxLayout()\n    dialog.setLayout(l)\n    l.addWidget(QLabel(label))\n    txt = QLineEdit()\n    if default:\n        txt.setText(default)\n    l.addWidget(txt)\n    l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))\n    if dialog.exec_():\n        return txt.text()\n\ndef text_dialog(parent, title, label, ok_label, default=None, allow_multi=False):\n    from .qrtextedit import ScanQRTextEdit\n    dialog = WindowModalDialog(parent, title)\n    dialog.setMinimumWidth(500)\n    l = QVBoxLayout()\n    dialog.setLayout(l)\n    l.addWidget(QLabel(label))\n    txt = ScanQRTextEdit(allow_multi=allow_multi)\n    if default:\n        txt.setText(default)\n    l.addWidget(txt)\n    l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))\n    if dialog.exec_():\n        return txt.toPlainText()\n\nclass ChoicesLayout(object):\n    def __init__(self, msg, choices, on_clicked=None, checked_index=0):\n        vbox = QVBoxLayout()\n        if len(msg) > 50:\n            vbox.addWidget(WWLabel(msg))\n            msg = \"\"\n        gb2 = QGroupBox(msg)\n        vbox.addWidget(gb2)\n\n        vbox2 = QVBoxLayout()\n        gb2.setLayout(vbox2)\n\n        self.group = group = QButtonGroup()\n        for i,c in enumerate(choices):\n            button = QRadioButton(gb2)\n            button.setText(c)\n            vbox2.addWidget(button)\n            group.addButton(button)\n            group.setId(button, i)\n            if i==checked_index:\n                button.setChecked(True)\n\n        if on_clicked:\n            group.buttonClicked.connect(partial(on_clicked, self))\n\n        self.vbox = vbox\n\n    def layout(self):\n        return self.vbox\n\n    def selected_index(self):\n        return self.group.checkedId()\n\ndef address_field(addresses):\n    hbox = QHBoxLayout()\n    address_e = QLineEdit()\n    if addresses and len(addresses) > 0:\n        address_e.setText(addresses[0])\n    else:\n        addresses = []\n    def func():\n        try:\n            i = addresses.index(str(address_e.text())) + 1\n            i = i % len(addresses)\n            address_e.setText(addresses[i])\n        except ValueError:\n            # the user might have changed address_e to an\n            # address not in the wallet (or to something that isn't an address)\n            if addresses and len(addresses) > 0:\n                address_e.setText(addresses[0])\n    button = QPushButton(_('Address'))\n    button.clicked.connect(func)\n    hbox.addWidget(button)\n    hbox.addWidget(address_e)\n    return hbox, address_e\n\n\ndef filename_field(parent, config, defaultname, select_msg):\n\n    vbox = QVBoxLayout()\n    vbox.addWidget(QLabel(_(\"Export Format\")))\n    gb = QGroupBox(\"format\", parent)\n    b1 = QRadioButton(gb)\n    b1.setText(_(\"CSV\"))\n    b1.setChecked(True)\n    b2 = QRadioButton(gb)\n    b2.setText(_(\"JSON\"))\n    vbox.addWidget(b1)\n    vbox.addWidget(b2)\n\n    hbox = QHBoxLayout()\n\n    directory = config.get('io_dir', os.path.expanduser('~'))\n    path = os.path.join( directory, defaultname )\n    filename_e = QLineEdit()\n    filename_e.setText(path)\n\n    def func():\n        text = filename_e.text()\n        _filter = \"*.csv\" if text.endswith(\".csv\") else \"*.json\" if text.endswith(\".json\") else None\n        p, __ = QFileDialog.getSaveFileName(None, select_msg, text, _filter)\n        if p:\n            filename_e.setText(p)\n\n    button = QPushButton(_('File'))\n    button.clicked.connect(func)\n    hbox.addWidget(button)\n    hbox.addWidget(filename_e)\n    vbox.addLayout(hbox)\n\n    def set_csv(v):\n        text = filename_e.text()\n        text = text.replace(\".json\",\".csv\") if v else text.replace(\".csv\",\".json\")\n        filename_e.setText(text)\n\n    b1.clicked.connect(lambda: set_csv(True))\n    b2.clicked.connect(lambda: set_csv(False))\n\n    return vbox, filename_e, b1\n\nclass ElectrumItemDelegate(QStyledItemDelegate):\n    def createEditor(self, parent, option, index):\n        return self.parent().createEditor(parent, option, index)\n\nclass MyTreeWidget(QTreeWidget):\n\n    def __init__(self, parent, create_menu, headers, stretch_column=None,\n                 editable_columns=None):\n        QTreeWidget.__init__(self, parent)\n        self.parent = parent\n        self.config = self.parent.config\n        self.stretch_column = stretch_column\n        self.setContextMenuPolicy(Qt.CustomContextMenu)\n        self.customContextMenuRequested.connect(create_menu)\n        self.setUniformRowHeights(True)\n        # extend the syntax for consistency\n        self.addChild = self.addTopLevelItem\n        self.insertChild = self.insertTopLevelItem\n\n        # Control which columns are editable\n        self.editor = None\n        self.pending_update = False\n        if editable_columns is None:\n            editable_columns = [stretch_column]\n        self.editable_columns = editable_columns\n        self.setItemDelegate(ElectrumItemDelegate(self))\n        self.itemDoubleClicked.connect(self.on_doubleclick)\n        self.update_headers(headers)\n        self.current_filter = \"\"\n\n    def update_headers(self, headers):\n        self.setColumnCount(len(headers))\n        self.setHeaderLabels(headers)\n        self.header().setStretchLastSection(False)\n        for col in range(len(headers)):\n            sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents\n            self.header().setSectionResizeMode(col, sm)\n\n    def editItem(self, item, column):\n        if column in self.editable_columns:\n            self.editing_itemcol = (item, column, item.text(column))\n            # Calling setFlags causes on_changed events for some reason\n            item.setFlags(item.flags() | Qt.ItemIsEditable)\n            QTreeWidget.editItem(self, item, column)\n            item.setFlags(item.flags() & ~Qt.ItemIsEditable)\n\n    def keyPressEvent(self, event):\n        if event.key() in [ Qt.Key_F2, Qt.Key_Return ] and self.editor is None:\n            self.on_activated(self.currentItem(), self.currentColumn())\n        else:\n            QTreeWidget.keyPressEvent(self, event)\n\n    def permit_edit(self, item, column):\n        return (column in self.editable_columns\n                and self.on_permit_edit(item, column))\n\n    def on_permit_edit(self, item, column):\n        return True\n\n    def on_doubleclick(self, item, column):\n        if self.permit_edit(item, column):\n            self.editItem(item, column)\n\n    def on_activated(self, item, column):\n        # on 'enter' we show the menu\n        pt = self.visualItemRect(item).bottomLeft()\n        pt.setX(50)\n        self.customContextMenuRequested.emit(pt)\n\n    def createEditor(self, parent, option, index):\n        self.editor = QStyledItemDelegate.createEditor(self.itemDelegate(),\n                                                       parent, option, index)\n        self.editor.editingFinished.connect(self.editing_finished)\n        return self.editor\n\n    def editing_finished(self):\n        # Long-time QT bug - pressing Enter to finish editing signals\n        # editingFinished twice.  If the item changed the sequence is\n        # Enter key:  editingFinished, on_change, editingFinished\n        # Mouse: on_change, editingFinished\n        # This mess is the cleanest way to ensure we make the\n        # on_edited callback with the updated item\n        if self.editor:\n            (item, column, prior_text) = self.editing_itemcol\n            if self.editor.text() == prior_text:\n                self.editor = None  # Unchanged - ignore any 2nd call\n            elif item.text(column) == prior_text:\n                pass # Buggy first call on Enter key, item not yet updated\n            else:\n                # What we want - the updated item\n                self.on_edited(*self.editing_itemcol)\n                self.editor = None\n\n            # Now do any pending updates\n            if self.editor is None and self.pending_update:\n                self.pending_update = False\n                self.on_update()\n\n    def on_edited(self, item, column, prior):\n        '''Called only when the text actually changes'''\n        key = item.data(0, Qt.UserRole)\n        text = item.text(column)\n        self.parent.wallet.set_label(key, text)\n        self.parent.history_list.update_labels()\n        self.parent.update_completions()\n\n    def update(self):\n        # Defer updates if editing\n        if self.editor:\n            self.pending_update = True\n        else:\n            self.setUpdatesEnabled(False)\n            self.on_update()\n            self.setUpdatesEnabled(True)\n        if self.current_filter:\n            self.filter(self.current_filter)\n\n    def on_update(self):\n        pass\n\n    def get_leaves(self, root):\n        child_count = root.childCount()\n        if child_count == 0:\n            yield root\n        for i in range(child_count):\n            item = root.child(i)\n            for x in self.get_leaves(item):\n                yield x\n\n    def filter(self, p):\n        columns = self.__class__.filter_columns\n        p = p.lower()\n        self.current_filter = p\n        for item in self.get_leaves(self.invisibleRootItem()):\n            item.setHidden(all([item.text(column).lower().find(p) == -1\n                                for column in columns]))\n\n\nclass ButtonsWidget(QWidget):\n\n    def __init__(self):\n        super(QWidget, self).__init__()\n        self.buttons = []\n\n    def resizeButtons(self):\n        frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)\n        x = self.rect().right() - frameWidth\n        y = self.rect().bottom() - frameWidth\n        for button in self.buttons:\n            sz = button.sizeHint()\n            x -= sz.width()\n            button.move(x, y - sz.height())\n\n    def addButton(self, icon_name, on_click, tooltip):\n        button = QToolButton(self)\n        button.setIcon(QIcon(icon_name))\n        button.setStyleSheet(\"QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }\")\n        button.setVisible(True)\n        button.setToolTip(tooltip)\n        button.clicked.connect(on_click)\n        self.buttons.append(button)\n        return button\n\n    def addCopyButton(self, app):\n        self.app = app\n        self.addButton(\":icons/copy.png\", self.on_copy, _(\"Copy to clipboard\"))\n\n    def on_copy(self):\n        self.app.clipboard().setText(self.text())\n        QToolTip.showText(QCursor.pos(), _(\"Text copied to clipboard\"), self)\n\nclass ButtonsLineEdit(QLineEdit, ButtonsWidget):\n    def __init__(self, text=None):\n        QLineEdit.__init__(self, text)\n        self.buttons = []\n\n    def resizeEvent(self, e):\n        o = QLineEdit.resizeEvent(self, e)\n        self.resizeButtons()\n        return o\n\nclass ButtonsTextEdit(QPlainTextEdit, ButtonsWidget):\n    def __init__(self, text=None):\n        QPlainTextEdit.__init__(self, text)\n        self.setText = self.setPlainText\n        self.text = self.toPlainText\n        self.buttons = []\n\n    def resizeEvent(self, e):\n        o = QPlainTextEdit.resizeEvent(self, e)\n        self.resizeButtons()\n        return o\n\n\nclass TaskThread(QThread):\n    '''Thread that runs background tasks.  Callbacks are guaranteed\n    to happen in the context of its parent.'''\n\n    Task = namedtuple(\"Task\", \"task cb_success cb_done cb_error\")\n    doneSig = pyqtSignal(object, object, object)\n\n    def __init__(self, parent, on_error=None):\n        super(TaskThread, self).__init__(parent)\n        self.on_error = on_error\n        self.tasks = queue.Queue()\n        self.doneSig.connect(self.on_done)\n        self.start()\n\n    def add(self, task, on_success=None, on_done=None, on_error=None):\n        on_error = on_error or self.on_error\n        self.tasks.put(TaskThread.Task(task, on_success, on_done, on_error))\n\n    def run(self):\n        while True:\n            task = self.tasks.get()\n            if not task:\n                break\n            try:\n                result = task.task()\n                self.doneSig.emit(result, task.cb_done, task.cb_success)\n            except BaseException:\n                self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)\n\n    def on_done(self, result, cb_done, cb):\n        # This runs in the parent's thread.\n        if cb_done:\n            cb_done()\n        if cb:\n            cb(result)\n\n    def stop(self):\n        self.tasks.put(None)\n\n\nclass ColorSchemeItem:\n    def __init__(self, fg_color, bg_color):\n        self.colors = (fg_color, bg_color)\n\n    def _get_color(self, background):\n        return self.colors[(int(background) + int(ColorScheme.dark_scheme)) % 2]\n\n    def as_stylesheet(self, background=False):\n        css_prefix = \"background-\" if background else \"\"\n        color = self._get_color(background)\n        return \"QWidget {{ {}color:{}; }}\".format(css_prefix, color)\n\n    def as_color(self, background=False):\n        color = self._get_color(background)\n        return QColor(color)\n\n\nclass ColorScheme:\n    dark_scheme = False\n\n    GREEN = ColorSchemeItem(\"#117c11\", \"#8af296\")\n    RED = ColorSchemeItem(\"#7c1111\", \"#f18c8c\")\n    BLUE = ColorSchemeItem(\"#123b7c\", \"#8cb3f2\")\n    DEFAULT = ColorSchemeItem(\"black\", \"white\")\n\n    @staticmethod\n    def has_dark_background(widget):\n        brightness = sum(widget.palette().color(QPalette.Background).getRgb()[0:3])\n        return brightness < (255*3/2)\n\n    @staticmethod\n    def update_from_widget(widget):\n        if ColorScheme.has_dark_background(widget):\n            ColorScheme.dark_scheme = True\n\nif __name__ == \"__main__\":\n    app = QApplication([])\n    t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', \"done\"))\n    t.start()\n    app.exec_()\n"
  },
  {
    "path": "gui/qt/utxo_list.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom .util import *\nfrom electrum.i18n import _\n\n\nclass UTXOList(MyTreeWidget):\n    filter_columns = [0, 2]  # Address, Label\n\n    def __init__(self, parent=None):\n        MyTreeWidget.__init__(self, parent, self.create_menu, [ _('Address'), _('Label'), _('Amount'), _('Height'), _('Output point')], 1)\n        self.setSelectionMode(QAbstractItemView.ExtendedSelection)\n\n    def get_name(self, x):\n        return x.get('prevout_hash') + \":%d\"%x.get('prevout_n')\n\n    def on_update(self):\n        self.wallet = self.parent.wallet\n        item = self.currentItem()\n        self.clear()\n        self.utxos = self.wallet.get_utxos()\n        for x in self.utxos:\n            address = x.get('address')\n            height = x.get('height')\n            name = self.get_name(x)\n            label = self.wallet.get_label(x.get('prevout_hash'))\n            amount = self.parent.format_amount(x['value'])\n            utxo_item = QTreeWidgetItem([address, label, amount, '%d'%height, name[0:10] + '...' + name[-2:]])\n            utxo_item.setFont(0, QFont(MONOSPACE_FONT))\n            utxo_item.setFont(4, QFont(MONOSPACE_FONT))\n            utxo_item.setData(0, Qt.UserRole, name)\n            if self.wallet.is_frozen(address):\n                utxo_item.setBackground(0, ColorScheme.BLUE.as_color(True))\n            self.addChild(utxo_item)\n\n    def create_menu(self, position):\n        selected = [x.data(0, Qt.UserRole) for x in self.selectedItems()]\n        if not selected:\n            return\n        menu = QMenu()\n        coins = filter(lambda x: self.get_name(x) in selected, self.utxos)\n\n        menu.addAction(_(\"Spend\"), lambda: self.parent.spend_coins(coins))\n        if len(selected) == 1:\n            txid = selected[0].split(':')[0]\n            tx = self.wallet.transactions.get(txid)\n            menu.addAction(_(\"Details\"), lambda: self.parent.show_transaction(tx))\n\n        menu.exec_(self.viewport().mapToGlobal(position))\n\n    def on_permit_edit(self, item, column):\n        # disable editing fields in this tab (labels)\n        return False\n"
  },
  {
    "path": "gui/stdio.py",
    "content": "from decimal import Decimal\n_ = lambda x:x\n#from i18n import _\nfrom electrum import WalletStorage, Wallet\nfrom electrum.util import format_satoshis, set_verbosity\nfrom electrum.bitcoin import is_address, COIN, TYPE_ADDRESS\nimport getpass, datetime\n\n# minimal fdisk like gui for console usage\n# written by rofl0r, with some bits stolen from the text gui (ncurses)\n\nclass ElectrumGui:\n\n    def __init__(self, config, daemon, plugins):\n        self.config = config\n        self.network = daemon.network\n        storage = WalletStorage(config.get_wallet_path())\n        if not storage.file_exists:\n            print(\"Wallet not found. try 'electrum create'\")\n            exit()\n        if storage.is_encrypted():\n            password = getpass.getpass('Password:', stream=None)\n            storage.decrypt(password)\n\n        self.done = 0\n        self.last_balance = \"\"\n\n        set_verbosity(False)\n\n        self.str_recipient = \"\"\n        self.str_description = \"\"\n        self.str_amount = \"\"\n        self.str_fee = \"\"\n\n        self.wallet = Wallet(storage)\n        self.wallet.start_threads(self.network)\n        self.contacts = self.wallet.contacts\n\n        self.network.register_callback(self.on_network, ['updated', 'banner'])\n        self.commands = [_(\"[h] - displays this help text\"), \\\n                         _(\"[i] - display transaction history\"), \\\n                         _(\"[o] - enter payment order\"), \\\n                         _(\"[p] - print stored payment order\"), \\\n                         _(\"[s] - send stored payment order\"), \\\n                         _(\"[r] - show own receipt addresses\"), \\\n                         _(\"[c] - display contacts\"), \\\n                         _(\"[b] - print server banner\"), \\\n                         _(\"[q] - quit\") ]\n        self.num_commands = len(self.commands)\n\n    def on_network(self, event, *args):\n        if event == 'updated':\n            self.updated()\n        elif event == 'banner':\n            self.print_banner()\n\n    def main_command(self):\n        self.print_balance()\n        c = input(\"enter command: \")\n        if c == \"h\" : self.print_commands()\n        elif c == \"i\" : self.print_history()\n        elif c == \"o\" : self.enter_order()\n        elif c == \"p\" : self.print_order()\n        elif c == \"s\" : self.send_order()\n        elif c == \"r\" : self.print_addresses()\n        elif c == \"c\" : self.print_contacts()\n        elif c == \"b\" : self.print_banner()\n        elif c == \"n\" : self.network_dialog()\n        elif c == \"e\" : self.settings_dialog()\n        elif c == \"q\" : self.done = 1\n        else: self.print_commands()\n\n    def updated(self):\n        s = self.get_balance()\n        if s != self.last_balance:\n            print(s)\n        self.last_balance = s\n        return True\n\n    def print_commands(self):\n        self.print_list(self.commands, \"Available commands\")\n\n    def print_history(self):\n        width = [20, 40, 14, 14]\n        delta = (80 - sum(width) - 4)/3\n        format_str = \"%\"+\"%d\"%width[0]+\"s\"+\"%\"+\"%d\"%(width[1]+delta)+\"s\"+\"%\" \\\n        + \"%d\"%(width[2]+delta)+\"s\"+\"%\"+\"%d\"%(width[3]+delta)+\"s\"\n        messages = []\n\n        for item in self.wallet.get_history():\n            tx_hash, height, conf, timestamp, delta, balance = item\n            if conf:\n                try:\n                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]\n                except Exception:\n                    time_str = \"unknown\"\n            else:\n                time_str = 'unconfirmed'\n\n            label = self.wallet.get_label(tx_hash)\n            messages.append( format_str%( time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )\n\n        self.print_list(messages[::-1], format_str%( _(\"Date\"), _(\"Description\"), _(\"Amount\"), _(\"Balance\")))\n\n\n    def print_balance(self):\n        print(self.get_balance())\n\n    def get_balance(self):\n        if self.wallet.network.is_connected():\n            if not self.wallet.up_to_date:\n                msg = _( \"Synchronizing...\" )\n            else:\n                c, u, x =  self.wallet.get_balance()\n                msg = _(\"Balance\")+\": %f  \"%(Decimal(c) / COIN)\n                if u:\n                    msg += \"  [%f unconfirmed]\"%(Decimal(u) / COIN)\n                if x:\n                    msg += \"  [%f unmatured]\"%(Decimal(x) / COIN)\n        else:\n                msg = _( \"Not connected\" )\n\n        return(msg)\n\n\n    def print_contacts(self):\n        messages = map(lambda x: \"%20s   %45s \"%(x[0], x[1][1]), self.contacts.items())\n        self.print_list(messages, \"%19s  %25s \"%(\"Key\", \"Value\"))\n\n    def print_addresses(self):\n        messages = map(lambda addr: \"%30s    %30s       \"%(addr, self.wallet.labels.get(addr,\"\")), self.wallet.get_addresses())\n        self.print_list(messages, \"%19s  %25s \"%(\"Address\", \"Label\"))\n\n    def print_order(self):\n        print(\"send order to \" + self.str_recipient + \", amount: \" + self.str_amount \\\n              + \"\\nfee: \" + self.str_fee + \", desc: \" + self.str_description)\n\n    def enter_order(self):\n        self.str_recipient = input(\"Pay to: \")\n        self.str_description = input(\"Description : \")\n        self.str_amount = input(\"Amount: \")\n        self.str_fee = input(\"Fee: \")\n\n    def send_order(self):\n        self.do_send()\n\n    def print_banner(self):\n        for i, x in enumerate( self.wallet.network.banner.split('\\n') ):\n            print( x )\n\n    def print_list(self, lst, firstline):\n        lst = list(lst)\n        self.maxpos = len(lst)\n        if not self.maxpos: return\n        print(firstline)\n        for i in range(self.maxpos):\n            msg = lst[i] if i < len(lst) else \"\"\n            print(msg)\n\n\n    def main(self):\n        while self.done == 0: self.main_command()\n\n    def do_send(self):\n        if not is_address(self.str_recipient):\n            print(_('Invalid BTCP address'))\n            return\n        try:\n            amount = int(Decimal(self.str_amount) * COIN)\n        except Exception:\n            print(_('Invalid Amount'))\n            return\n        try:\n            fee = int(Decimal(self.str_fee) * COIN)\n        except Exception:\n            print(_('Invalid Fee'))\n            return\n\n        if self.wallet.has_password():\n            password = self.password_dialog()\n            if not password:\n                return\n        else:\n            password = None\n\n        c = \"\"\n        while c != \"y\":\n            c = input(\"ok to send (y/n)?\")\n            if c == \"n\": return\n\n        try:\n            tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee)\n        except Exception as e:\n            print(str(e))\n            return\n\n        if self.str_description:\n            self.wallet.labels[tx.txid()] = self.str_description\n\n        print(_(\"Please wait...\"))\n        status, msg = self.network.broadcast(tx)\n\n        if status:\n            print(_('Payment sent.'))\n            #self.do_clear()\n            #self.update_contacts_tab()\n        else:\n            print(_('Error'))\n\n    def network_dialog(self):\n        print(\"use 'electrum setconfig server/proxy' to change your network settings\")\n        return True\n\n\n    def settings_dialog(self):\n        print(\"use 'electrum setconfig' to change your settings\")\n        return True\n\n    def password_dialog(self):\n        return getpass.getpass()\n\n\n#   XXX unused\n\n    def run_receive_tab(self, c):\n        #if c == 10:\n        #    out = self.run_popup('Address', [\"Edit label\", \"Freeze\", \"Prioritize\"])\n        return\n\n    def run_contacts_tab(self, c):\n        pass\n"
  },
  {
    "path": "gui/text.py",
    "content": "import tty, sys\nimport curses, datetime, locale\nfrom decimal import Decimal\nimport getpass\n\nimport electrum\nfrom electrum.util import format_satoshis, set_verbosity\nfrom electrum.bitcoin import is_address, COIN, TYPE_ADDRESS\nfrom electrum import Wallet, WalletStorage\n\n_ = lambda x:x\n\n\n\nclass ElectrumGui:\n\n    def __init__(self, config, daemon, plugins):\n\n        self.config = config\n        self.network = daemon.network\n        storage = WalletStorage(config.get_wallet_path())\n        if not storage.file_exists():\n            print(\"Wallet not found. try 'electrum create'\")\n            exit()\n        if storage.is_encrypted():\n            password = getpass.getpass('Password:', stream=None)\n            storage.decrypt(password)\n        self.wallet = Wallet(storage)\n        self.wallet.start_threads(self.network)\n        self.contacts = self.wallet.contacts\n\n        locale.setlocale(locale.LC_ALL, '')\n        self.encoding = locale.getpreferredencoding()\n\n        self.stdscr = curses.initscr()\n        curses.noecho()\n        curses.cbreak()\n        curses.start_color()\n        curses.use_default_colors()\n        curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)\n        curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN)\n        curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)\n        self.stdscr.keypad(1)\n        self.stdscr.border(0)\n        self.maxy, self.maxx = self.stdscr.getmaxyx()\n        self.set_cursor(0)\n        self.w = curses.newwin(10, 50, 5, 5)\n\n        set_verbosity(False)\n        self.tab = 0\n        self.pos = 0\n        self.popup_pos = 0\n\n        self.str_recipient = \"\"\n        self.str_description = \"\"\n        self.str_amount = \"\"\n        self.str_fee = \"\"\n        self.history = None\n\n        if self.network:\n            self.network.register_callback(self.update, ['updated'])\n\n        self.tab_names = [_(\"History\"), _(\"Send\"), _(\"Receive\"), _(\"Addresses\"), _(\"Contacts\"), _(\"Banner\")]\n        self.num_tabs = len(self.tab_names)\n\n\n    def set_cursor(self, x):\n        try:\n            curses.curs_set(x)\n        except Exception:\n            pass\n\n    def restore_or_create(self):\n        pass\n\n    def verify_seed(self):\n        pass\n\n    def get_string(self, y, x):\n        self.set_cursor(1)\n        curses.echo()\n        self.stdscr.addstr( y, x, \" \"*20, curses.A_REVERSE)\n        s = self.stdscr.getstr(y,x)\n        curses.noecho()\n        self.set_cursor(0)\n        return s\n\n    def update(self, event):\n        self.update_history()\n        if self.tab == 0:\n            self.print_history()\n        self.refresh()\n\n    def print_history(self):\n\n        width = [20, 40, 14, 14]\n        delta = (self.maxx - sum(width) - 4)/3\n        format_str = \"%\"+\"%d\"%width[0]+\"s\"+\"%\"+\"%d\"%(width[1]+delta)+\"s\"+\"%\"+\"%d\"%(width[2]+delta)+\"s\"+\"%\"+\"%d\"%(width[3]+delta)+\"s\"\n\n        if self.history is None:\n            self.update_history()\n\n        self.print_list(self.history[::-1], format_str%( _(\"Date\"), _(\"Description\"), _(\"Amount\"), _(\"Balance\")))\n\n    def update_history(self):\n        width = [20, 40, 14, 14]\n        delta = (self.maxx - sum(width) - 4)/3\n        format_str = \"%\"+\"%d\"%width[0]+\"s\"+\"%\"+\"%d\"%(width[1]+delta)+\"s\"+\"%\"+\"%d\"%(width[2]+delta)+\"s\"+\"%\"+\"%d\"%(width[3]+delta)+\"s\"\n\n        b = 0\n        self.history = []\n        for item in self.wallet.get_history():\n            tx_hash, height, conf, timestamp, value, balance = item\n            if conf:\n                try:\n                    time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]\n                except Exception:\n                    time_str = \"------\"\n            else:\n                time_str = 'unconfirmed'\n\n            label = self.wallet.get_label(tx_hash)\n            if len(label) > 40:\n                label = label[0:37] + '...'\n            self.history.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )\n\n\n    def print_balance(self):\n        if not self.network:\n            msg = _(\"Offline\")\n        elif self.network.is_connected():\n            if not self.wallet.up_to_date:\n                msg = _(\"Synchronizing...\")\n            else:\n                c, u, x =  self.wallet.get_balance()\n                msg = _(\"Balance\")+\": %f  \"%(Decimal(c) / COIN)\n                if u:\n                    msg += \"  [%f unconfirmed]\"%(Decimal(u) / COIN)\n                if x:\n                    msg += \"  [%f unmatured]\"%(Decimal(x) / COIN)\n        else:\n            msg = _(\"Not connected\")\n\n        self.stdscr.addstr( self.maxy -1, 3, msg)\n\n        for i in range(self.num_tabs):\n            self.stdscr.addstr( 0, 2 + 2*i + len(''.join(self.tab_names[0:i])), ' '+self.tab_names[i]+' ', curses.A_BOLD if self.tab == i else 0)\n\n        self.stdscr.addstr(self.maxy -1, self.maxx-30, ' '.join([_(\"Settings\"), _(\"Network\"), _(\"Quit\")]))\n\n    def print_receive(self):\n        addr = self.wallet.get_receiving_address()\n        self.stdscr.addstr(2, 1, \"Address: \"+addr)\n        self.print_qr(addr)\n\n    def print_contacts(self):\n        messages = map(lambda x: \"%20s   %45s \"%(x[0], x[1][1]), self.contacts.items())\n        self.print_list(messages, \"%19s  %15s \"%(\"Key\", \"Value\"))\n\n    def print_addresses(self):\n        fmt = \"%-35s  %-30s\"\n        messages = map(lambda addr: fmt % (addr, self.wallet.labels.get(addr,\"\")), self.wallet.get_addresses())\n        self.print_list(messages,   fmt % (\"Address\", \"Label\"))\n\n    def print_edit_line(self, y, label, text, index, size):\n        text += \" \"*(size - len(text) )\n        self.stdscr.addstr( y, 2, label)\n        self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1))\n\n    def print_send_tab(self):\n        self.stdscr.clear()\n        self.print_edit_line(3, _(\"Pay to\"), self.str_recipient, 0, 40)\n        self.print_edit_line(5, _(\"Description\"), self.str_description, 1, 40)\n        self.print_edit_line(7, _(\"Amount\"), self.str_amount, 2, 15)\n        self.print_edit_line(9, _(\"Fee\"), self.str_fee, 3, 15)\n        self.stdscr.addstr( 12, 15, _(\"[Send]\"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2))\n        self.stdscr.addstr( 12, 25, _(\"[Clear]\"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2))\n        self.maxpos = 6\n\n    def print_banner(self):\n        if self.network:\n            self.print_list( self.network.banner.split('\\n'))\n\n    def print_qr(self, data):\n        import qrcode\n        try:\n            from StringIO import StringIO\n        except ImportError:\n            from io import StringIO\n\n        s = StringIO()\n        self.qr = qrcode.QRCode()\n        self.qr.add_data(data)\n        self.qr.print_ascii(out=s, invert=False)\n        msg = s.getvalue()\n        lines = msg.split('\\n')\n        for i, l in enumerate(lines):\n            l = l.encode(\"utf-8\")\n            self.stdscr.addstr(i+5, 5, l, curses.color_pair(3))\n\n    def print_list(self, lst, firstline = None):\n        lst = list(lst)\n        self.maxpos = len(lst)\n        if not self.maxpos: return\n        if firstline:\n            firstline += \" \"*(self.maxx -2 - len(firstline))\n            self.stdscr.addstr( 1, 1, firstline )\n        for i in range(self.maxy-4):\n            msg = lst[i] if i < len(lst) else \"\"\n            msg += \" \"*(self.maxx - 2 - len(msg))\n            m = msg[0:self.maxx - 2]\n            m = m.encode(self.encoding)\n            self.stdscr.addstr( i+2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0)\n\n    def refresh(self):\n        if self.tab == -1: return\n        self.stdscr.border(0)\n        self.print_balance()\n        self.stdscr.refresh()\n\n    def main_command(self):\n        c = self.stdscr.getch()\n        print(c)\n        cc = curses.unctrl(c).decode()\n        if   c == curses.KEY_RIGHT: self.tab = (self.tab + 1)%self.num_tabs\n        elif c == curses.KEY_LEFT: self.tab = (self.tab - 1)%self.num_tabs\n        elif c == curses.KEY_DOWN: self.pos +=1\n        elif c == curses.KEY_UP: self.pos -= 1\n        elif c == 9: self.pos +=1 # tab\n        elif cc in ['^W', '^C', '^X', '^Q']: self.tab = -1\n        elif cc in ['^N']: self.network_dialog()\n        elif cc == '^S': self.settings_dialog()\n        else: return c\n        if self.pos<0: self.pos=0\n        if self.pos>=self.maxpos: self.pos=self.maxpos - 1\n\n    def run_tab(self, i, print_func, exec_func):\n        while self.tab == i:\n            self.stdscr.clear()\n            print_func()\n            self.refresh()\n            c = self.main_command()\n            if c: exec_func(c)\n\n\n    def run_history_tab(self, c):\n        if c == 10:\n            out = self.run_popup('',[\"blah\",\"foo\"])\n\n\n    def edit_str(self, target, c, is_num=False):\n        # detect backspace\n        cc = curses.unctrl(c).decode()\n        if c in [8, 127, 263] and target:\n            target = target[:-1]\n        elif not is_num or cc in '0123456789.':\n            target += cc\n        return target\n\n\n    def run_send_tab(self, c):\n        if self.pos%6 == 0:\n            self.str_recipient = self.edit_str(self.str_recipient, c)\n        if self.pos%6 == 1:\n            self.str_description = self.edit_str(self.str_description, c)\n        if self.pos%6 == 2:\n            self.str_amount = self.edit_str(self.str_amount, c, True)\n        elif self.pos%6 == 3:\n            self.str_fee = self.edit_str(self.str_fee, c, True)\n        elif self.pos%6==4:\n            if c == 10: self.do_send()\n        elif self.pos%6==5:\n            if c == 10: self.do_clear()\n\n\n    def run_receive_tab(self, c):\n        if c == 10:\n            out = self.run_popup('Address', [\"Edit label\", \"Freeze\", \"Prioritize\"])\n\n    def run_contacts_tab(self, c):\n        if c == 10 and self.contacts:\n            out = self.run_popup('Address', [\"Copy\", \"Pay to\", \"Edit label\", \"Delete\"]).get('button')\n            key = list(self.contacts.keys())[self.pos%len(self.contacts.keys())]\n            if out == \"Pay to\":\n                self.tab = 1\n                self.str_recipient = key\n                self.pos = 2\n            elif out == \"Edit label\":\n                s = self.get_string(6 + self.pos, 18)\n                if s:\n                    self.wallet.labels[key] = s\n\n    def run_banner_tab(self, c):\n        self.show_message(repr(c))\n        pass\n\n    def main(self):\n\n        tty.setraw(sys.stdin)\n        while self.tab != -1:\n            self.run_tab(0, self.print_history, self.run_history_tab)\n            self.run_tab(1, self.print_send_tab, self.run_send_tab)\n            self.run_tab(2, self.print_receive, self.run_receive_tab)\n            self.run_tab(3, self.print_addresses, self.run_banner_tab)\n            self.run_tab(4, self.print_contacts, self.run_contacts_tab)\n            self.run_tab(5, self.print_banner, self.run_banner_tab)\n\n        tty.setcbreak(sys.stdin)\n        curses.nocbreak()\n        self.stdscr.keypad(0)\n        curses.echo()\n        curses.endwin()\n\n\n    def do_clear(self):\n        self.str_amount = ''\n        self.str_recipient = ''\n        self.str_fee = ''\n        self.str_description = ''\n\n    def do_send(self):\n        if not is_address(self.str_recipient):\n            self.show_message(_('Invalid BTCP address'))\n            return\n        try:\n            amount = int(Decimal(self.str_amount) * COIN)\n        except Exception:\n            self.show_message(_('Invalid Amount'))\n            return\n        try:\n            fee = int(Decimal(self.str_fee) * COIN)\n        except Exception:\n            self.show_message(_('Invalid Fee'))\n            return\n\n        if self.wallet.has_password():\n            password = self.password_dialog()\n            if not password:\n                return\n        else:\n            password = None\n        try:\n            tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee)\n        except Exception as e:\n            self.show_message(str(e))\n            return\n\n        if self.str_description:\n            self.wallet.labels[tx.txid()] = self.str_description\n\n        self.show_message(_(\"Please wait...\"), getchar=False)\n        status, msg = self.network.broadcast(tx)\n\n        if status:\n            self.show_message(_('Payment sent.'))\n            self.do_clear()\n            #self.update_contacts_tab()\n        else:\n            self.show_message(_('Error'))\n\n\n    def show_message(self, message, getchar = True):\n        w = self.w\n        w.clear()\n        w.border(0)\n        for i, line in enumerate(message.split('\\n')):\n            w.addstr(2+i,2,line)\n        w.refresh()\n        if getchar: c = self.stdscr.getch()\n\n    def run_popup(self, title, items):\n        return self.run_dialog(title, list(map(lambda x: {'type':'button','label':x}, items)), interval=1, y_pos = self.pos+3)\n\n    def network_dialog(self):\n        if not self.network:\n            return\n        params = self.network.get_parameters()\n        host, port, protocol, proxy_config, auto_connect = params\n        srv = 'auto-connect' if auto_connect else self.network.default_server\n        out = self.run_dialog('Network', [\n            {'label':'server', 'type':'str', 'value':srv},\n            {'label':'proxy', 'type':'str', 'value':self.config.get('proxy', '')},\n            ], buttons = 1)\n        if out:\n            if out.get('server'):\n                server = out.get('server')\n                auto_connect = server == 'auto-connect'\n                if not auto_connect:\n                    try:\n                        host, port, protocol = server.split(':')\n                    except Exception:\n                        self.show_message(\"Error:\" + server + \"\\nIn doubt, type \\\"auto-connect\\\"\")\n                        return False\n            if out.get('server') or out.get('proxy'):\n                proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config\n                self.network.set_parameters(host, port, protocol, proxy, auto_connect)\n\n    def settings_dialog(self):\n        fee = str(Decimal(self.config.fee_per_kb()) / COIN)\n        out = self.run_dialog('Settings', [\n            {'label':'Default fee', 'type':'satoshis', 'value': fee }\n            ], buttons = 1)\n        if out:\n            if out.get('Default fee'):\n                fee = int(Decimal(out['Default fee']) * COIN)\n                self.config.set_key('fee_per_kb', fee, True)\n\n\n    def password_dialog(self):\n        out = self.run_dialog('Password', [\n            {'label':'Password', 'type':'password', 'value':''}\n            ], buttons = 1)\n        return out.get('Password')\n\n\n    def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3):\n        self.popup_pos = 0\n\n        self.w = curses.newwin( 5 + len(list(items))*interval + (2 if buttons else 0), 50, y_pos, 5)\n        w = self.w\n        out = {}\n        while True:\n            w.clear()\n            w.border(0)\n            w.addstr( 0, 2, title)\n\n            num = len(list(items))\n\n            numpos = num\n            if buttons: numpos += 2\n\n            for i in range(num):\n                item = items[i]\n                label = item.get('label')\n                if item.get('type') == 'list':\n                    value = item.get('value','')\n                elif item.get('type') == 'satoshis':\n                    value = item.get('value','')\n                elif item.get('type') == 'str':\n                    value = item.get('value','')\n                elif item.get('type') == 'password':\n                    value = '*'*len(item.get('value',''))\n                else:\n                    value = ''\n                if value is None:\n                    value = ''\n                if len(value)<20:\n                    value += ' '*(20-len(value))\n\n                if 'value' in item:\n                    w.addstr( 2+interval*i, 2, label)\n                    w.addstr( 2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1) )\n                else:\n                    w.addstr( 2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0)\n\n            if buttons:\n                w.addstr( 5+interval*i, 10, \"[  ok  ]\", curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2))\n                w.addstr( 5+interval*i, 25, \"[cancel]\", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2))\n\n            w.refresh()\n\n            c = self.stdscr.getch()\n            if c in [ord('q'), 27]: break\n            elif c in [curses.KEY_LEFT, curses.KEY_UP]: self.popup_pos -= 1\n            elif c in [curses.KEY_RIGHT, curses.KEY_DOWN]: self.popup_pos +=1\n            else:\n                i = self.popup_pos%numpos\n                if buttons and c==10:\n                    if i == numpos-2:\n                        return out\n                    elif i == numpos -1:\n                        return {}\n\n                item = items[i]\n                _type = item.get('type')\n\n                if _type == 'str':\n                    item['value'] = self.edit_str(item['value'], c)\n                    out[item.get('label')] = item.get('value')\n\n                elif _type == 'password':\n                    item['value'] = self.edit_str(item['value'], c)\n                    out[item.get('label')] = item ['value']\n\n                elif _type == 'satoshis':\n                    item['value'] = self.edit_str(item['value'], c, True)\n                    out[item.get('label')] = item.get('value')\n\n                elif _type == 'list':\n                    choices = item.get('choices')\n                    try:\n                        j = choices.index(item.get('value'))\n                    except Exception:\n                        j = 0\n                    new_choice = choices[(j + 1)% len(choices)]\n                    item['value'] = new_choice\n                    out[item.get('label')] = item.get('value')\n\n                elif _type == 'button':\n                    out['button'] = item.get('label')\n                    break\n\n        return out\n"
  },
  {
    "path": "icns-from-vector.sh",
    "content": "# Tool to generate a .iconset from a png\n# (used 400x400 input.png)\n\nmkdir output.iconset\nsips -z 16 16     input.png --out output.iconset/icon_16x16.png\nsips -z 32 32     input.png --out output.iconset/icon_16x16@2x.png\nsips -z 32 32     input.png --out output.iconset/icon_32x32.png\nsips -z 64 64     input.png --out output.iconset/icon_32x32@2x.png\nsips -z 128 128   input.png --out output.iconset/icon_128x128.png\nsips -z 256 256   input.png --out output.iconset/icon_128x128@2x.png\nsips -z 256 256   input.png --out output.iconset/icon_256x256.png\n#sips -z 512 512   input.png --out output.iconset/icon_256x256@2x.png\n#sips -z 512 512   input.png --out output.iconset/icon_512x512.png\n#cp input.png output.iconset/icon_512x512@2x.png\niconutil -c icns output.iconset\nrm -R output.iconset\n"
  },
  {
    "path": "icons.qrc",
    "content": "<RCC>\n  <qresource prefix=\"/\" >\n    <file>icons/electrum.png</file>\n    <file>icons/clock1.png</file>\n    <file>icons/clock2.png</file>\n    <file>icons/clock3.png</file>\n    <file>icons/clock4.png</file>\n    <file>icons/clock5.png</file>\n    <file>icons/confirmed.png</file>\n    <file>icons/copy.png</file>\n    <file>icons/digitalbitbox.png</file>\n    <file>icons/digitalbitbox_unpaired.png</file>\n    <file>icons/expired.png</file>\n    <file>icons/electrum_light_icon.png</file>\n    <file>icons/electrum_dark_icon.png</file>\n    <file>icons/file.png</file>\n    <file>icons/keepkey.png</file>\n    <file>icons/keepkey_unpaired.png</file>\n    <file>icons/key.png</file>\n    <file>icons/ledger.png</file>\n    <file>icons/ledger_unpaired.png</file>\n    <file>icons/lock.png</file>\n    <file>icons/microphone.png</file>\n    <file>icons/network.png</file>\n    <file>icons/qrcode.png</file>\n    <file>icons/qrcode_white.png</file>\n    <file>icons/preferences.png</file>\n    <file>icons/seed.png</file>\n    <file>icons/status_connected.png</file>\n    <file>icons/status_connected_proxy.png</file>\n    <file>icons/status_disconnected.png</file>\n    <file>icons/status_waiting.png</file>\n    <file>icons/status_lagging.png</file>\n    <file>icons/seal.png</file>\n    <file>icons/tab_addresses.png</file>\n    <file>icons/tab_coins.png</file>\n    <file>icons/tab_console.png</file>\n    <file>icons/tab_contacts.png</file>\n    <file>icons/tab_history.png</file>\n    <file>icons/tab_receive.png</file>\n    <file>icons/tab_send.png</file>\n    <file>icons/tor_logo.png</file>\n    <file>icons/speaker.png</file>\n    <file>icons/trezor_unpaired.png</file>\n    <file>icons/trezor.png</file>\n    <file>icons/trustedcoin-status.png</file>\n    <file>icons/trustedcoin-wizard.png</file>\n    <file>icons/unconfirmed.png</file>\n    <file>icons/unpaid.png</file>\n    <file>icons/unlock.png</file>\n    <file>icons/warning.png</file>\n    <file>icons/zoom.png</file>\n  </qresource>\n</RCC>\n"
  },
  {
    "path": "lib/__init__.py",
    "content": "from .version import ELECTRUM_VERSION\nfrom .util import format_satoshis, print_msg, print_error, set_verbosity\nfrom .wallet import Synchronizer, Wallet\nfrom .storage import WalletStorage\nfrom .coinchooser import COIN_CHOOSERS\nfrom .network import Network, pick_random_server\nfrom .interface import Connection, Interface\nfrom .simple_config import SimpleConfig, get_config, set_config\nfrom . import bitcoin\nfrom . import transaction\nfrom . import daemon\nfrom . import equihash\nfrom .transaction import Transaction\nfrom .plugins import BasePlugin\nfrom .commands import Commands, known_commands\n"
  },
  {
    "path": "lib/base_wizard.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2016 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport os\nfrom . import bitcoin\nfrom . import keystore\nfrom .keystore import bip44_derivation\nfrom .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types\nfrom .i18n import _\n\n\nclass ScriptTypeNotSupported(Exception): pass\n\n\nclass BaseWizard(object):\n\n    def __init__(self, config, storage):\n        super(BaseWizard, self).__init__()\n        self.config = config\n        self.storage = storage\n        self.wallet = None\n        self.stack = []\n        self.plugin = None\n        self.keystores = []\n        self.is_kivy = config.get('gui') == 'kivy'\n        self.seed_type = None\n\n    def run(self, *args):\n        action = args[0]\n        args = args[1:]\n        self.stack.append((action, args))\n        if not action:\n            return\n        if type(action) is tuple:\n            self.plugin, action = action\n        if self.plugin and hasattr(self.plugin, action):\n            f = getattr(self.plugin, action)\n            f(self, *args)\n        elif hasattr(self, action):\n            f = getattr(self, action)\n            f(*args)\n        else:\n            raise BaseException(\"unknown action\", action)\n\n    def can_go_back(self):\n        return len(self.stack)>1\n\n    def go_back(self):\n        if not self.can_go_back():\n            return\n        self.stack.pop()\n        action, args = self.stack.pop()\n        self.run(action, *args)\n\n    def new(self):\n        name = os.path.basename(self.storage.path)\n        title = _(\"Create\") + ' ' + name\n        message = '\\n'.join([\n            _(\"What kind of wallet do you want to create?\")\n        ])\n        wallet_kinds = [\n            ('standard',  _(\"Standard wallet\")),\n            ('multisig',  _(\"Multi-signature wallet\")),\n            ('imported',  _(\"Import BTCP addresses or private keys\")),\n        ]\n        choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]\n        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)\n\n    def load_2fa(self):\n        self.storage.put('wallet_type', '2fa')\n        self.storage.put('use_trustedcoin', True)\n        self.plugin = self.plugins.load_plugin('trustedcoin')\n\n    def on_wallet_type(self, choice):\n        self.wallet_type = choice\n        if choice == 'standard':\n            action = 'choose_keystore'\n        elif choice == 'multisig':\n            action = 'choose_multisig'\n        elif choice == '2fa':\n            self.load_2fa()\n            action = self.storage.get_action()\n        elif choice == 'imported':\n            action = 'import_addresses_or_keys'\n        self.run(action)\n\n    def choose_multisig(self):\n        def on_multisig(m, n):\n            self.multisig_type = \"%dof%d\"%(m, n)\n            self.storage.put('wallet_type', self.multisig_type)\n            self.n = n\n            self.run('choose_keystore')\n        self.multisig_dialog(run_next=on_multisig)\n\n    def choose_keystore(self):\n        assert self.wallet_type in ['standard', 'multisig']\n        i = len(self.keystores)\n        title = _('Add cosigner') + ' (%d of %d)'%(i+1, self.n) if self.wallet_type=='multisig' else _('Keystore')\n        if self.wallet_type =='standard' or i==0:\n            message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')\n            choices = [\n                ('choose_seed_type', _('Create a new seed')),\n                ('restore_from_seed', _('I already have a seed')),\n                ('restore_from_key', _('Use public or private keys')),\n            ]\n            if not self.is_kivy:\n                choices.append(('choose_hw_device',  _('Use a hardware device')))\n        else:\n            message = _('Add a cosigner to your multi-sig wallet')\n            choices = [\n                ('restore_from_key', _('Enter cosigner key')),\n                ('restore_from_seed', _('Enter cosigner seed')),\n            ]\n            if not self.is_kivy:\n                choices.append(('choose_hw_device',  _('Cosign with hardware device')))\n\n        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)\n\n    def import_addresses_or_keys(self):\n        v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)\n        title = _(\"Import BTCP Addresses\")\n        message = _(\"Enter a list of BTCP addresses (this will create a watching-only wallet), or a list of private keys.\")\n        self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,\n                             is_valid=v, allow_multi=True)\n\n    def on_import(self, text):\n        if keystore.is_address_list(text):\n            self.wallet = Imported_Wallet(self.storage)\n            for x in text.split():\n                self.wallet.import_address(x)\n        elif keystore.is_private_key_list(text):\n            k = keystore.Imported_KeyStore({})\n            self.storage.put('keystore', k.dump())\n            self.wallet = Imported_Wallet(self.storage)\n            for x in text.split():\n                self.wallet.import_private_key(x, None)\n        self.terminate()\n\n    def restore_from_key(self):\n        if self.wallet_type == 'standard':\n            v = keystore.is_master_key\n            title = _(\"Create keystore from a master key\")\n            message = ' '.join([\n                _(\"To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub).\"),\n                _(\"To create a spending wallet, please enter a master private key (xprv/yprv/zprv).\")\n            ])\n            self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)\n        else:\n            i = len(self.keystores) + 1\n            self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key)\n\n    def on_restore_from_key(self, text):\n        k = keystore.from_master_key(text)\n        self.on_keystore(k)\n\n    def choose_hw_device(self):\n        title = _('Hardware Keystore')\n        # check available plugins\n        support = self.plugins.get_hardware_support()\n        if not support:\n            msg = '\\n'.join([\n                _('No hardware wallet support found on your system.'),\n                _('Please install the relevant libraries (eg python-trezor for Trezor).'),\n            ])\n            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device())\n            return\n        # scan devices\n        devices = []\n        devmgr = self.plugins.device_manager\n        for name, description, plugin in support:\n            try:\n                # FIXME: side-effect: unpaired_device_info sets client.handler\n                u = devmgr.unpaired_device_infos(None, plugin)\n            except:\n                devmgr.print_error(\"error\", name)\n                continue\n            devices += list(map(lambda x: (name, x), u))\n        if not devices:\n            msg = ''.join([\n                _('No hardware device detected.') + '\\n',\n                _('To trigger a rescan, press \\'Next\\'.') + '\\n\\n',\n                _('If your device is not detected on Windows, go to \"Settings\", \"Devices\", \"Connected devices\", and do \"Remove device\". Then, plug your device again.') + ' ',\n                _('On Linux, you might have to add a new permission to your udev rules.'),\n            ])\n            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device())\n            return\n        # select device\n        self.devices = devices\n        choices = []\n        for name, info in devices:\n            state = _(\"initialized\") if info.initialized else _(\"wiped\")\n            label = info.label or _(\"An unnamed %s\")%name\n            descr = \"%s [%s, %s]\" % (label, name, state)\n            choices.append(((name, info), descr))\n        msg = _('Select a device') + ':'\n        self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_device)\n\n    def on_device(self, name, device_info):\n        self.plugin = self.plugins.get_plugin(name)\n        try:\n            self.plugin.setup_device(device_info, self)\n        except BaseException as e:\n            self.show_error(str(e))\n            self.choose_hw_device()\n            return\n        if self.wallet_type=='multisig':\n            # There is no general standard for HD multisig.\n            # This is partially compatible with BIP45; assumes index=0\n            self.on_hw_derivation(name, device_info, \"m/45'/0\")\n        else:\n            f = lambda x: self.run('on_hw_derivation', name, device_info, str(x))\n            self.derivation_dialog(f)\n\n    def derivation_dialog(self, f):\n        default = bip44_derivation(0, bip43_purpose=44)\n        message = '\\n'.join([\n            _('Enter your wallet derivation here.'),\n            _('If you are not sure what this is, leave this field unchanged.')\n        ])\n        presets = (\n            ('legacy BIP44', bip44_derivation(0, bip43_purpose=44)),\n            #('p2sh-segwit BIP49', bip44_derivation(0, bip43_purpose=49)),\n            #('native-segwit BIP84', bip44_derivation(0, bip43_purpose=84)),\n        )\n        while True:\n            try:\n                self.line_dialog(run_next=f, title=_('Derivation'), message=message,\n                                 default=default, test=bitcoin.is_bip32_derivation,\n                                 presets=presets)\n                return\n            except ScriptTypeNotSupported as e:\n                self.show_error(e)\n                # let the user choose again\n\n    def on_hw_derivation(self, name, device_info, derivation):\n        from .keystore import hardware_keystore\n        xtype = keystore.xtype_from_derivation(derivation)\n        try:\n            xpub = self.plugin.get_xpub(device_info.device.id_, derivation, xtype, self)\n        except ScriptTypeNotSupported:\n            raise  # this is handled in derivation_dialog\n        except BaseException as e:\n            self.show_error(e)\n            return\n        d = {\n            'type': 'hardware',\n            'hw_type': name,\n            'derivation': derivation,\n            'xpub': xpub,\n            'label': device_info.label,\n        }\n        k = hardware_keystore(d)\n        self.on_keystore(k)\n\n    def passphrase_dialog(self, run_next):\n        title = _('Seed extension')\n        message = '\\n'.join([\n            _('You may extend your seed with custom words.'),\n            _('Your seed extension must be saved together with your seed.'),\n        ])\n        warning = '\\n'.join([\n            _('Note that this is NOT your encryption password.'),\n            _('If you do not know what this is, leave this field empty.'),\n        ])\n        self.line_dialog(title=title, message=message, warning=warning, default='', test=lambda x:True, run_next=run_next)\n\n    def restore_from_seed(self):\n        self.opt_bip39 = True\n        self.opt_ext = True\n        is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit']\n        test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed\n        self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)\n\n    def on_restore_seed(self, seed, is_bip39, is_ext):\n        self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)\n        if self.seed_type == 'bip39':\n            f = lambda passphrase: self.on_restore_bip39(seed, passphrase)\n            self.passphrase_dialog(run_next=f) if is_ext else f('')\n        elif self.seed_type in ['standard', 'segwit']:\n            f = lambda passphrase: self.run('create_keystore', seed, passphrase)\n            self.passphrase_dialog(run_next=f) if is_ext else f('')\n        elif self.seed_type == 'old':\n            self.run('create_keystore', seed, '')\n        elif self.seed_type == '2fa':\n            if self.is_kivy:\n                self.show_error(_('2FA seeds are not supported in this version'))\n                self.run('restore_from_seed')\n            else:\n                self.load_2fa()\n                self.run('on_restore_seed', seed, is_ext)\n        else:\n            raise BaseException('Unknown seed type', self.seed_type)\n\n    def on_restore_bip39(self, seed, passphrase):\n        f = lambda x: self.run('on_bip43', seed, passphrase, str(x))\n        self.derivation_dialog(f)\n\n    def create_keystore(self, seed, passphrase):\n        k = keystore.from_seed(seed, passphrase, self.wallet_type == 'multisig')\n        self.on_keystore(k)\n\n    def on_bip43(self, seed, passphrase, derivation):\n        k = keystore.from_bip39_seed(seed, passphrase, derivation)\n        self.on_keystore(k)\n\n    def on_keystore(self, k):\n        has_xpub = isinstance(k, keystore.Xpub)\n        if has_xpub:\n            from .bitcoin import xpub_type\n            t1 = xpub_type(k.xpub)\n        if self.wallet_type == 'standard':\n            if has_xpub and t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:\n                self.show_error(_('Wrong key type') + ' %s'%t1)\n                self.run('choose_keystore')\n                return\n            self.keystores.append(k)\n            self.run('create_wallet')\n        elif self.wallet_type == 'multisig':\n            assert has_xpub\n            if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']:\n                self.show_error(_('Wrong key type') + ' %s'%t1)\n                self.run('choose_keystore')\n                return\n            if k.xpub in map(lambda x: x.xpub, self.keystores):\n                self.show_error(_('Error: duplicate master public key'))\n                self.run('choose_keystore')\n                return\n            if len(self.keystores)>0:\n                t2 = xpub_type(self.keystores[0].xpub)\n                if t1 != t2:\n                    self.show_error(_('Cannot add this cosigner:') + '\\n' + \"Their key type is '%s', we are '%s'\"%(t1, t2))\n                    self.run('choose_keystore')\n                    return\n            self.keystores.append(k)\n            if len(self.keystores) == 1:\n                xpub = k.get_master_public_key()\n                self.stack = []\n                self.run('show_xpub_and_add_cosigners', xpub)\n            elif len(self.keystores) < self.n:\n                self.run('choose_keystore')\n            else:\n                self.run('create_wallet')\n\n    def create_wallet(self):\n        if any(k.may_have_password() for k in self.keystores):\n            self.request_password(run_next=self.on_password)\n        else:\n            self.on_password(None, False)\n\n    def on_password(self, password, encrypt):\n        self.storage.set_password(password, encrypt)\n        for k in self.keystores:\n            if k.may_have_password():\n                k.update_password(None, password)\n        if self.wallet_type == 'standard':\n            self.storage.put('seed_type', self.seed_type)\n            keys = self.keystores[0].dump()\n            self.storage.put('keystore', keys)\n            self.wallet = Standard_Wallet(self.storage)\n            self.run('create_addresses')\n        elif self.wallet_type == 'multisig':\n            for i, k in enumerate(self.keystores):\n                self.storage.put('x%d/'%(i+1), k.dump())\n            self.storage.write()\n            self.wallet = Multisig_Wallet(self.storage)\n            self.run('create_addresses')\n\n    def show_xpub_and_add_cosigners(self, xpub):\n        self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))\n\n    def on_cosigner(self, text, password, i):\n        k = keystore.from_master_key(text, password)\n        self.on_keystore(k)\n\n    def choose_seed_type(self):\n        title = _('Choose Seed type')\n        message = ' '.join([\n            _(\"The type of addresses used by your wallet will depend on your seed.\"),\n        ])\n        choices = [\n            ('create_standard_seed', _('Standard')),\n            #('create_segwit_seed', _('Segwit')),\n        ]\n        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)\n\n    def create_segwit_seed(self): self.create_seed('segwit')\n    def create_standard_seed(self): self.create_seed('standard')\n\n    def create_seed(self, seed_type):\n        from . import mnemonic\n        self.seed_type = seed_type\n        seed = mnemonic.Mnemonic('en').make_seed(self.seed_type)\n        self.opt_bip39 = False\n        f = lambda x: self.request_passphrase(seed, x)\n        self.show_seed_dialog(run_next=f, seed_text=seed)\n\n    def request_passphrase(self, seed, opt_passphrase):\n        if opt_passphrase:\n            f = lambda x: self.confirm_seed(seed, x)\n            self.passphrase_dialog(run_next=f)\n        else:\n            self.run('confirm_seed', seed, '')\n\n    def confirm_seed(self, seed, passphrase):\n        f = lambda x: self.confirm_passphrase(seed, passphrase)\n        self.confirm_seed_dialog(run_next=f, test=lambda x: x==seed)\n\n    def confirm_passphrase(self, seed, passphrase):\n        f = lambda x: self.run('create_keystore', seed, x)\n        if passphrase:\n            title = _('Confirm Seed Extension')\n            message = '\\n'.join([\n                _('Your seed extension must be saved together with your seed.'),\n                _('Please type it here.'),\n            ])\n            self.line_dialog(run_next=f, title=title, message=message, default='', test=lambda x: x==passphrase)\n        else:\n            f('')\n\n    def create_addresses(self):\n        def task():\n            self.wallet.synchronize()\n            self.wallet.storage.write()\n            self.terminate()\n        msg = _(\"Electrum is generating your addresses, please wait.\")\n        self.waiting_dialog(task, msg)\n"
  },
  {
    "path": "lib/bitcoin.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport hashlib\nimport base64\nimport hmac\nimport os\nimport json\n\nimport struct\nimport ecdsa\nimport pyaes\n\nfrom .util import bfh, bh2u, to_string\nfrom . import version\nfrom .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict\nfrom . import segwit_addr\n\ndef read_json(filename, default):\n    path = os.path.join(os.path.dirname(__file__), filename)\n    try:\n        with open(path, 'r') as f:\n            r = json.loads(f.read())\n    except:\n        r = default\n    return r\n\n\n\n\n# Version numbers for BIP32 extended keys\n# standard: xprv, xpub\n# segwit in p2sh: yprv, ypub\n# native segwit: zprv, zpub\nXPRV_HEADERS = {\n    'standard': 0x0488ade4,\n    'p2wpkh-p2sh': 0x049d7878,\n    'p2wsh-p2sh': 0x295b005,\n    'p2wpkh': 0x4b2430c,\n    'p2wsh': 0x2aa7a99\n}\nXPUB_HEADERS = {\n    'standard': 0x0488b21e,\n    'p2wpkh-p2sh': 0x049d7cb2,\n    'p2wsh-p2sh': 0x295b43f,\n    'p2wpkh': 0x4b24746,\n    'p2wsh': 0x2aa7ed3\n}\n\n\nclass NetworkConstants:\n\n    @classmethod\n    def set_mainnet(cls):\n        cls.TESTNET = False\n        cls.WIF_PREFIX = 0x80\n        cls.ADDRTYPE_P2PKH = [0x13, 0x25]\n        cls.ADDRTYPE_P2SH = [0x13, 0xAF]\n        cls.ADDRTYPE_SHIELDED = [0x16, 0xA8]\n        cls.SEGWIT_HRP = \"bc\" #TODO btcp has no segwit\n        cls.GENESIS = \"0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602\"\n        cls.DEFAULT_PORTS = {'t': '50001', 's': '50002'}\n        cls.DEFAULT_SERVERS = read_json('servers.json', {})\n        cls.CHECKPOINTS = read_json('checkpoints.json', [])\n        cls.EQUIHASH_N = 200\n        cls.EQUIHASH_K = 9\n        cls.HEADERS_URL = \"http://headers.btcprivate.org/blockchain_headers\"\n\n        cls.CHUNK_SIZE = 200\n\n    @classmethod\n    def set_testnet(cls):\n        cls.TESTNET = True\n        cls.WIF_PREFIX = 0xef\n        cls.ADDRTYPE_P2PKH = [0x19, 0x58]\n        cls.ADDRTYPE_P2SH = [0x19, 0xE0]\n        cls.ADDRTYPE_SHIELDED = [0x16, 0xC0]\n        cls.SEGWIT_HRP = \"tb\" #TODO btcp has no segwit\n        cls.GENESIS = \"03e1c4bb705c871bf9bfda3e74b7f8f86bff267993c215a89d5795e3708e5e1f\"\n        cls.DEFAULT_PORTS = {'t': '51001', 's': '51002'}\n        cls.DEFAULT_SERVERS = read_json('servers_testnet.json', {})\n        cls.CHECKPOINTS = read_json('checkpoints_testnet.json', [])\n        cls.EQUIHASH_N = 200\n        cls.EQUIHASH_K = 9\n\n        #cls.HEADERS_URL = \"http://35.224.186.7/blockchain_headers\"\n\n        cls.CHUNK_SIZE = 200\n\nNetworkConstants.set_mainnet()\n\n################################## transactions\n\nFEE_STEP = 10000\nDEFAULT_FEE_RATE = 10000\nMAX_FEE_RATE = 300000\nFEE_TARGETS = [25, 10, 5, 2]\n\nCOINBASE_MATURITY = 100\nCOIN = 100000000\n\n# supported types of transaction outputs\nTYPE_ADDRESS = 0\nTYPE_PUBKEY  = 1\nTYPE_SCRIPT  = 2\n\n# AES encryption\ntry:\n    from Cryptodome.Cipher import AES\nexcept:\n    AES = None\n\n\nclass InvalidPadding(Exception):\n    pass\n\n\ndef append_PKCS7_padding(data):\n    assert_bytes(data)\n    padlen = 16 - (len(data) % 16)\n    return data + bytes([padlen]) * padlen\n\n\ndef strip_PKCS7_padding(data):\n    assert_bytes(data)\n    if len(data) % 16 != 0 or len(data) == 0:\n        raise InvalidPadding(\"invalid length\")\n    padlen = data[-1]\n    if padlen > 16:\n        raise InvalidPadding(\"invalid padding byte (large)\")\n    for i in data[-padlen:]:\n        if i != padlen:\n            raise InvalidPadding(\"invalid padding byte (inconsistent)\")\n    return data[0:-padlen]\n\n\ndef aes_encrypt_with_iv(key, iv, data):\n    assert_bytes(key, iv, data)\n    data = append_PKCS7_padding(data)\n    if AES:\n        e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)\n    else:\n        aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)\n        aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)\n        e = aes.feed(data) + aes.feed()  # empty aes.feed() flushes buffer\n    return e\n\n\ndef aes_decrypt_with_iv(key, iv, data):\n    assert_bytes(key, iv, data)\n    if AES:\n        cipher = AES.new(key, AES.MODE_CBC, iv)\n        data = cipher.decrypt(data)\n    else:\n        aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)\n        aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)\n        data = aes.feed(data) + aes.feed()  # empty aes.feed() flushes buffer\n    try:\n        return strip_PKCS7_padding(data)\n    except InvalidPadding:\n        raise InvalidPassword()\n\n\ndef EncodeAES(secret, s):\n    assert_bytes(s)\n    iv = bytes(os.urandom(16))\n    ct = aes_encrypt_with_iv(secret, iv, s)\n    e = iv + ct\n    return base64.b64encode(e)\n\ndef DecodeAES(secret, e):\n    e = bytes(base64.b64decode(e))\n    iv, e = e[:16], e[16:]\n    s = aes_decrypt_with_iv(secret, iv, e)\n    return s\n\ndef pw_encode(s, password):\n    if password:\n        secret = Hash(password)\n        return EncodeAES(secret, to_bytes(s, \"utf8\")).decode('utf8')\n    else:\n        return s\n\ndef pw_decode(s, password):\n    if password is not None:\n        secret = Hash(password)\n        try:\n            d = to_string(DecodeAES(secret, s), \"utf8\")\n        except Exception:\n            raise InvalidPassword()\n        return d\n    else:\n        return s\n\n\ndef rev_hex(s):\n    return bh2u(bfh(s)[::-1])\n\n\ndef int_to_hex(i, length=1):\n    assert isinstance(i, int)\n    s = hex(i)[2:].rstrip('L')\n    s = \"0\"*(2*length - len(s)) + s\n    return rev_hex(s)\n\n\ndef var_int(i):\n    # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer\n    if i<0xfd:\n        return int_to_hex(i)\n    elif i<=0xffff:\n        return \"fd\"+int_to_hex(i,2)\n    elif i<=0xffffffff:\n        return \"fe\"+int_to_hex(i,4)\n    else:\n        return \"ff\"+int_to_hex(i,8)\n\n\ndef op_push(i):\n    if i<0x4c:\n        return int_to_hex(i)\n    elif i<0xff:\n        return '4c' + int_to_hex(i)\n    elif i<0xffff:\n        return '4d' + int_to_hex(i,2)\n    else:\n        return '4e' + int_to_hex(i,4)\n\ndef push_script(x):\n    return op_push(len(x)//2) + x\n\n# ZCASH specific utils methods\n# https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/mininode.py\n\nHEADER_SIZE = 1487\n\nhash_to_str = lambda x: bytes(reversed(x)).hex()\nstr_to_hash = lambda x: bytes(reversed(bytes.fromhex(x)))\n\ndef read_vector_size(f):\n    nit = struct.unpack(\"<B\", f.read(1))[0]\n    if nit == 253:\n        return struct.unpack(\"<H\", f.read(2))[0]\n    elif nit == 254:\n        return struct.unpack(\"<I\", f.read(4))[0]\n    elif nit == 255:\n        return struct.unpack(\"<Q\", f.read(8))[0]\n    return nit\n\ndef ser_char_vector(l):\n    if l is None:\n        l = b''\n    if len(l) < 253:\n        r = struct.pack(\"<B\", len(l))\n    elif len(l) < 0x10000:\n        r = struct.pack(\"<B\", 253) + struct.pack(\"<H\", len(l))\n    elif len(l) < 0x100000000:\n        r = struct.pack(\"<B\", 254) + struct.pack(\"<I\", len(l))\n    else:\n        r = struct.pack(\"<B\", 255) + struct.pack(\"<Q\", len(l))\n    r += bytes(l)\n    return r\n\n\ndef deser_char_vector(f):\n    nit = struct.unpack(\"<B\", f.read(1))[0]\n    if nit == 253:\n        nit = struct.unpack(\"<H\", f.read(2))[0]\n    elif nit == 254:\n        nit = struct.unpack(\"<I\", f.read(4))[0]\n    elif nit == 255:\n        nit = struct.unpack(\"<Q\", f.read(8))[0]\n    r = []\n    for i in range(nit):\n        t = struct.unpack(\"<B\", f.read(1))[0]\n        r.append(t)\n    return r\n\ndef vector_from_bytes(s):\n    return [v for v in s]\n\n\ndef deser_uint256(f):\n    r = 0\n    for i in range(8):\n        t = struct.unpack(\"<I\", f.read(4))[0]\n        r += t << (i * 32)\n    return r\n\n\ndef uint256_from_bytes(s):\n    r = 0\n    t = struct.unpack(\"<IIIIIIII\", s[:32])\n    for i in range(8):\n        r += t[i] << (i * 32)\n    return r\n\n\ndef ser_uint256(u):\n    if isinstance(u, str):\n        u = int(u, 16)\n    if u is None:\n        u = 0\n    rs = b''\n    for i in range(8):\n        rs += struct.pack(\"<I\", u & 0xFFFFFFFF)\n        u >>= 32\n    return rs\n\ndef sha256(x):\n    if isinstance(x, str):\n        x = x.encode('utf8')\n    return bytes(hashlib.sha256(x).digest())\n\ndef Hash(x):\n    out = bytes(sha256(sha256(x)))\n    return out\n\nhash_encode = lambda x: bh2u(x[::-1])\nhash_decode = lambda x: bfh(x)[::-1]\nhmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest()\n\ndef is_new_seed(x, prefix=version.SEED_PREFIX):\n    from . import mnemonic\n    x = mnemonic.normalize_text(x)\n    s = bh2u(hmac_sha_512(b\"Seed version\", x.encode('utf8')))\n    return s.startswith(prefix)\n\n\ndef is_old_seed(seed):\n    from . import old_mnemonic, mnemonic\n    seed = mnemonic.normalize_text(seed)\n    words = seed.split()\n    try:\n        # checks here are deliberately left weak for legacy reasons, see #3149\n        old_mnemonic.mn_decode(words)\n        uses_electrum_words = True\n    except Exception:\n        uses_electrum_words = False\n    try:\n        seed = bfh(seed)\n        is_hex = (len(seed) == 16 or len(seed) == 32)\n    except Exception:\n        is_hex = False\n    return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))\n\n\ndef seed_type(x):\n    if is_old_seed(x):\n        return 'old'\n    elif is_new_seed(x):\n        return 'standard'\n    elif is_new_seed(x, version.SEED_PREFIX_SW):\n        return 'segwit'\n    elif is_new_seed(x, version.SEED_PREFIX_2FA):\n        return '2fa'\n    return ''\n\nis_seed = lambda x: bool(seed_type(x))\n\n# pywallet openssl private key implementation\n\ndef i2o_ECPublicKey(pubkey, compressed=False):\n    # public keys are 65 bytes long (520 bits)\n    # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate\n    # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed\n    # compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd\n    if compressed:\n        if pubkey.point.y() & 1:\n            key = '03' + '%064x' % pubkey.point.x()\n        else:\n            key = '02' + '%064x' % pubkey.point.x()\n    else:\n        key = '04' + \\\n              '%064x' % pubkey.point.x() + \\\n              '%064x' % pubkey.point.y()\n\n    return bfh(key)\n# end pywallet openssl private key implementation\n\n\n############ functions from pywallet #####################\ndef hash_160(public_key):\n    try:\n        md = hashlib.new('ripemd160')\n        md.update(sha256(public_key))\n        return md.digest()\n    except BaseException:\n        from . import ripemd\n        md = ripemd.new(sha256(public_key))\n        return md.digest()\n\n\ndef hash160_to_b58_address(h160, addrtype, witness_program_version=1):\n    s = bytes([addrtype[0]])\n    s += bytes([addrtype[1]])\n    s += h160\n    return base_encode(s+Hash(s)[0:4], base=58)\n\n\ndef b58_address_to_hash160(addr):\n    addr = to_bytes(addr, 'ascii')\n    _bytes = base_decode(addr, 26, base=58)\n    return [_bytes[0], _bytes[1]], _bytes[2:22]\n\n\ndef hash160_to_p2pkh(h160):\n    return hash160_to_b58_address(h160, NetworkConstants.ADDRTYPE_P2PKH)\n\ndef hash160_to_p2sh(h160):\n    return hash160_to_b58_address(h160, NetworkConstants.ADDRTYPE_P2SH)\n\ndef public_key_to_p2pkh(public_key):\n    return hash160_to_p2pkh(hash_160(public_key))\n\ndef hash_to_segwit_addr(h):\n    return segwit_addr.encode(NetworkConstants.SEGWIT_HRP, 0, h)\n\ndef public_key_to_p2wpkh(public_key):\n    return hash_to_segwit_addr(hash_160(public_key))\n\ndef script_to_p2wsh(script):\n    return hash_to_segwit_addr(sha256(bfh(script)))\n\ndef p2wpkh_nested_script(pubkey):\n    pkh = bh2u(hash_160(bfh(pubkey)))\n    return '00' + push_script(pkh)\n\ndef p2wsh_nested_script(witness_script):\n    wsh = bh2u(sha256(bfh(witness_script)))\n    return '00' + push_script(wsh)\n\ndef pubkey_to_address(txin_type, pubkey):\n    if txin_type == 'p2pkh':\n        return public_key_to_p2pkh(bfh(pubkey))\n    elif txin_type == 'p2wpkh':\n        return hash_to_segwit_addr(hash_160(bfh(pubkey)))\n    elif txin_type == 'p2wpkh-p2sh':\n        scriptSig = p2wpkh_nested_script(pubkey)\n        return hash160_to_p2sh(hash_160(bfh(scriptSig)))\n    else:\n        raise NotImplementedError(txin_type)\n\ndef redeem_script_to_address(txin_type, redeem_script):\n    if txin_type == 'p2sh':\n        return hash160_to_p2sh(hash_160(bfh(redeem_script)))\n    elif txin_type == 'p2wsh':\n        return script_to_p2wsh(redeem_script)\n    elif txin_type == 'p2wsh-p2sh':\n        scriptSig = p2wsh_nested_script(redeem_script)\n        return hash160_to_p2sh(hash_160(bfh(scriptSig)))\n    else:\n        raise NotImplementedError(txin_type)\n\n\ndef script_to_address(script):\n    from .transaction import get_address_from_output_script\n    t, addr = get_address_from_output_script(bfh(script))\n    assert t == TYPE_ADDRESS\n    return addr\n\ndef address_to_script(addr):\n    witver, witprog = segwit_addr.decode(NetworkConstants.SEGWIT_HRP, addr)\n    if witprog is not None:\n        assert (0 <= witver <= 16)\n        OP_n = witver + 0x50 if witver > 0 else 0\n        script = bh2u(bytes([OP_n]))\n        script += push_script(bh2u(bytes(witprog)))\n        return script\n    addrtype, hash_160 = b58_address_to_hash160(addr)\n    if addrtype == NetworkConstants.ADDRTYPE_P2PKH:\n        script = '76a9'                                      # op_dup, op_hash_160\n        script += push_script(bh2u(hash_160))\n        script += '88ac'                                     # op_equalverify, op_checksig\n    elif addrtype == NetworkConstants.ADDRTYPE_P2SH:\n        script = 'a9'                                        # op_hash_160\n        script += push_script(bh2u(hash_160))\n        script += '87'                                       # op_equal\n    else:\n        raise BaseException('unknown address type')\n    return script\n\ndef address_to_scripthash(addr):\n    script = address_to_script(addr)\n    return script_to_scripthash(script)\n\ndef script_to_scripthash(script):\n    h = sha256(bytes.fromhex(script))[0:32]\n    return bh2u(bytes(reversed(h)))\n\ndef public_key_to_p2pk_script(pubkey):\n    script = push_script(pubkey)\n    script += 'ac'                                           # op_checksig\n    return script\n\n__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'\nassert len(__b58chars) == 58\n\n__b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'\nassert len(__b43chars) == 43\n\n\ndef base_encode(v, base):\n    \"\"\" encode v, which is a string of bytes, to base58.\"\"\"\n    assert_bytes(v)\n    assert base in (58, 43)\n    chars = __b58chars\n    if base == 43:\n        chars = __b43chars\n    long_value = 0\n    for (i, c) in enumerate(v[::-1]):\n        long_value += (256**i) * c\n    result = bytearray()\n    while long_value >= base:\n        div, mod = divmod(long_value, base)\n        result.append(chars[mod])\n        long_value = div\n    result.append(chars[long_value])\n    # Bitcoin does a little leading-zero-compression:\n    # leading 0-bytes in the input become leading-1s\n    nPad = 0\n    for c in v:\n        if c == 0x00:\n            nPad += 1\n        else:\n            break\n    result.extend([chars[0]] * nPad)\n    result.reverse()\n    return result.decode('ascii')\n\n\ndef base_decode(v, length, base):\n    \"\"\" decode v into a string of len bytes.\"\"\"\n    # assert_bytes(v)\n    v = to_bytes(v, 'ascii')\n    assert base in (58, 43)\n    chars = __b58chars\n    if base == 43:\n        chars = __b43chars\n    long_value = 0\n    for (i, c) in enumerate(v[::-1]):\n        long_value += chars.find(bytes([c])) * (base**i)\n    result = bytearray()\n    while long_value >= 256:\n        div, mod = divmod(long_value, 256)\n        result.append(mod)\n        long_value = div\n    result.append(long_value)\n    nPad = 0\n    for c in v:\n        if c == chars[0]:\n            nPad += 1\n        else:\n            break\n    result.extend(b'\\x00' * nPad)\n    if length is not None and len(result) != length:\n        return None\n    result.reverse()\n    return bytes(result)\n\n\ndef EncodeBase58Check(vchIn):\n    hash = Hash(vchIn)\n    return base_encode(vchIn + hash[0:4], base=58)\n\n\ndef DecodeBase58Check(psz):\n    vchRet = base_decode(psz, None, base=58)\n    key = vchRet[0:-4]\n    csum = vchRet[-4:]\n    hash = Hash(key)\n    cs32 = hash[0:4]\n    if cs32 != csum:\n        return None\n    else:\n        return key\n\n\n\n# extended key export format for segwit\n\nSCRIPT_TYPES = {\n    'p2pkh':0,\n    'p2wpkh':1,\n    'p2wpkh-p2sh':2,\n    'p2sh':5,\n    'p2wsh':6,\n    'p2wsh-p2sh':7\n}\n\n\ndef serialize_privkey(secret, compressed, txin_type):\n    prefix = bytes([(SCRIPT_TYPES[txin_type]+NetworkConstants.WIF_PREFIX)&255])\n    suffix = b'\\01' if compressed else b''\n    vchIn = prefix + secret + suffix\n    return EncodeBase58Check(vchIn)\n\n\ndef deserialize_privkey(key):\n    # whether the pubkey is compressed should be visible from the keystore\n    vch = DecodeBase58Check(key)\n    if is_minikey(key):\n        return 'p2pkh', minikey_to_private_key(key), True\n    elif vch:\n        txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - NetworkConstants.WIF_PREFIX]\n        assert len(vch) in [33, 34]\n        compressed = len(vch) == 34\n        return txin_type, vch[1:33], compressed\n    else:\n        raise BaseException(\"cannot deserialize\", key)\n\ndef regenerate_key(pk):\n    assert len(pk) == 32\n    return EC_KEY(pk)\n\n\ndef GetPubKey(pubkey, compressed=False):\n    return i2o_ECPublicKey(pubkey, compressed)\n\n\ndef GetSecret(pkey):\n    return bfh('%064x' % pkey.secret)\n\n\ndef is_compressed(sec):\n    return deserialize_privkey(sec)[2]\n\n\ndef public_key_from_private_key(pk, compressed):\n    pkey = regenerate_key(pk)\n    public_key = GetPubKey(pkey.pubkey, compressed)\n    return bh2u(public_key)\n\ndef address_from_private_key(sec):\n    txin_type, privkey, compressed = deserialize_privkey(sec)\n    public_key = public_key_from_private_key(privkey, compressed)\n    return pubkey_to_address(txin_type, public_key)\n\ndef is_segwit_address(addr):\n    try:\n        witver, witprog = segwit_addr.decode(NetworkConstants.SEGWIT_HRP, addr)\n    except Exception as e:\n        return False\n    return witprog is not None\n\ndef is_b58_address(addr):\n    try:\n        addrtype, h = b58_address_to_hash160(addr)\n    except Exception as e:\n        return False\n    if addrtype not in [NetworkConstants.ADDRTYPE_P2PKH, NetworkConstants.ADDRTYPE_P2SH]:\n        return False\n    return addr == hash160_to_b58_address(h, addrtype)\n\ndef is_address(addr):\n    return is_segwit_address(addr) or is_b58_address(addr)\n\n\ndef is_private_key(key):\n    try:\n        k = deserialize_privkey(key)\n        return k is not False\n    except:\n        return False\n\n\n########### end pywallet functions #######################\n\ndef is_minikey(text):\n    # Minikeys are typically 22 or 30 characters, but this routine\n    # permits any length of 20 or more provided the minikey is valid.\n    # A valid minikey must begin with an 'S', be in base58, and when\n    # suffixed with '?' have its SHA256 hash begin with a zero byte.\n    # They are widely used in Casascius physical bitcoins.\n    return (len(text) >= 20 and text[0] == 'S'\n            and all(ord(c) in __b58chars for c in text)\n            and sha256(text + '?')[0] == 0x00)\n\ndef minikey_to_private_key(text):\n    return sha256(text)\n\nfrom ecdsa.ecdsa import curve_secp256k1, generator_secp256k1\nfrom ecdsa.curves import SECP256k1\nfrom ecdsa.ellipticcurve import Point\nfrom ecdsa.util import string_to_number, number_to_string\n\n\ndef msg_magic(message):\n    length = bfh(var_int(len(message)))\n    return b\"\\x19BitcoinPrivate Signed Message:\\n\" + length + message\n\n\ndef verify_message(address, sig, message):\n    assert_bytes(sig, message)\n    try:\n        h = Hash(msg_magic(message))\n        public_key, compressed = pubkey_from_signature(sig, h)\n        # check public key using the address\n        pubkey = point_to_ser(public_key.pubkey.point, compressed)\n        for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:\n            addr = pubkey_to_address(txin_type, bh2u(pubkey))\n            if address == addr:\n                break\n        else:\n            raise Exception(\"Bad signature\")\n        # check message\n        public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)\n        return True\n    except Exception as e:\n        print_error(\"Verification error: {0}\".format(e))\n        return False\n\n\ndef encrypt_message(message, pubkey):\n    return EC_KEY.encrypt_message(message, bfh(pubkey))\n\n\ndef chunks(l, n):\n    return [l[i:i+n] for i in range(0, len(l), n)]\n\n\ndef ECC_YfromX(x,curved=curve_secp256k1, odd=True):\n    _p = curved.p()\n    _a = curved.a()\n    _b = curved.b()\n    for offset in range(128):\n        Mx = x + offset\n        My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p\n        My = pow(My2, (_p+1)//4, _p )\n\n        if curved.contains_point(Mx,My):\n            if odd == bool(My&1):\n                return [My,offset]\n            return [_p-My,offset]\n    raise Exception('ECC_YfromX: No Y found')\n\n\ndef negative_point(P):\n    return Point( P.curve(), P.x(), -P.y(), P.order() )\n\n\ndef point_to_ser(P, comp=True ):\n    if comp:\n        return bfh( ('%02x'%(2+(P.y()&1)))+('%064x'%P.x()) )\n    return bfh( '04'+('%064x'%P.x())+('%064x'%P.y()) )\n\n\ndef ser_to_point(Aser):\n    curve = curve_secp256k1\n    generator = generator_secp256k1\n    _r  = generator.order()\n    assert Aser[0] in [0x02, 0x03, 0x04]\n    if Aser[0] == 0x04:\n        return Point( curve, string_to_number(Aser[1:33]), string_to_number(Aser[33:]), _r )\n    Mx = string_to_number(Aser[1:])\n    return Point( curve, Mx, ECC_YfromX(Mx, curve, Aser[0] == 0x03)[0], _r )\n\n\nclass MyVerifyingKey(ecdsa.VerifyingKey):\n    @classmethod\n    def from_signature(klass, sig, recid, h, curve):\n        \"\"\" See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 \"\"\"\n        from ecdsa import util, numbertheory\n        from . import msqr\n        curveFp = curve.curve\n        G = curve.generator\n        order = G.order()\n        # extract r,s from signature\n        r, s = util.sigdecode_string(sig, order)\n        # 1.1\n        x = r + (recid//2) * order\n        # 1.3\n        alpha = ( x * x * x  + curveFp.a() * x + curveFp.b() ) % curveFp.p()\n        beta = msqr.modular_sqrt(alpha, curveFp.p())\n        y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta\n        # 1.4 the constructor checks that nR is at infinity\n        R = Point(curveFp, x, y, order)\n        # 1.5 compute e from message:\n        e = string_to_number(h)\n        minus_e = -e % order\n        # 1.6 compute Q = r^-1 (sR - eG)\n        inv_r = numbertheory.inverse_mod(r,order)\n        Q = inv_r * ( s * R + minus_e * G )\n        return klass.from_public_point( Q, curve )\n\n\ndef pubkey_from_signature(sig, h):\n    if len(sig) != 65:\n        raise Exception(\"Wrong encoding\")\n    nV = sig[0]\n    if nV < 27 or nV >= 35:\n        raise Exception(\"Bad encoding\")\n    if nV >= 31:\n        compressed = True\n        nV -= 4\n    else:\n        compressed = False\n    recid = nV - 27\n    return MyVerifyingKey.from_signature(sig[1:], recid, h, curve = SECP256k1), compressed\n\n\nclass MySigningKey(ecdsa.SigningKey):\n    \"\"\"Enforce low S values in signatures\"\"\"\n\n    def sign_number(self, number, entropy=None, k=None):\n        curve = SECP256k1\n        G = curve.generator\n        order = G.order()\n        r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k)\n        if s > order//2:\n            s = order - s\n        return r, s\n\n\nclass EC_KEY(object):\n\n    def __init__( self, k ):\n        secret = string_to_number(k)\n        self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )\n        self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )\n        self.secret = secret\n\n    def get_public_key(self, compressed=True):\n        return bh2u(point_to_ser(self.pubkey.point, compressed))\n\n    def sign(self, msg_hash):\n        private_key = MySigningKey.from_secret_exponent(self.secret, curve = SECP256k1)\n        public_key = private_key.get_verifying_key()\n        signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string)\n        assert public_key.verify_digest(signature, msg_hash, sigdecode = ecdsa.util.sigdecode_string)\n        return signature\n\n    def sign_message(self, message, is_compressed):\n        message = to_bytes(message, 'utf8')\n        signature = self.sign(Hash(msg_magic(message)))\n        for i in range(4):\n            sig = bytes([27 + i + (4 if is_compressed else 0)]) + signature\n            try:\n                self.verify_message(sig, message)\n                return sig\n            except Exception as e:\n                continue\n        else:\n            raise Exception(\"error: cannot sign message\")\n\n    def verify_message(self, sig, message):\n        assert_bytes(message)\n        h = Hash(msg_magic(message))\n        public_key, compressed = pubkey_from_signature(sig, h)\n        # check public key\n        if point_to_ser(public_key.pubkey.point, compressed) != point_to_ser(self.pubkey.point, compressed):\n            raise Exception(\"Bad signature\")\n        # check message\n        public_key.verify_digest(sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)\n\n\n    # ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac\n\n    @classmethod\n    def encrypt_message(self, message, pubkey):\n        assert_bytes(message)\n\n        pk = ser_to_point(pubkey)\n        if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, pk.x(), pk.y()):\n            raise Exception('invalid pubkey')\n\n        ephemeral_exponent = number_to_string(ecdsa.util.randrange(pow(2,256)), generator_secp256k1.order())\n        ephemeral = EC_KEY(ephemeral_exponent)\n        ecdh_key = point_to_ser(pk * ephemeral.privkey.secret_multiplier)\n        key = hashlib.sha512(ecdh_key).digest()\n        iv, key_e, key_m = key[0:16], key[16:32], key[32:]\n        ciphertext = aes_encrypt_with_iv(key_e, iv, message)\n        ephemeral_pubkey = bfh(ephemeral.get_public_key(compressed=True))\n        encrypted = b'BIE1' + ephemeral_pubkey + ciphertext\n        mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()\n\n        return base64.b64encode(encrypted + mac)\n\n    def decrypt_message(self, encrypted):\n        encrypted = base64.b64decode(encrypted)\n        if len(encrypted) < 85:\n            raise Exception('invalid ciphertext: length')\n        magic = encrypted[:4]\n        ephemeral_pubkey = encrypted[4:37]\n        ciphertext = encrypted[37:-32]\n        mac = encrypted[-32:]\n        if magic != b'BIE1':\n            raise Exception('invalid ciphertext: invalid magic bytes')\n        try:\n            ephemeral_pubkey = ser_to_point(ephemeral_pubkey)\n        except AssertionError as e:\n            raise Exception('invalid ciphertext: invalid ephemeral pubkey')\n        if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()):\n            raise Exception('invalid ciphertext: invalid ephemeral pubkey')\n        ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier)\n        key = hashlib.sha512(ecdh_key).digest()\n        iv, key_e, key_m = key[0:16], key[16:32], key[32:]\n        if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():\n            raise InvalidPassword()\n        return aes_decrypt_with_iv(key_e, iv, ciphertext)\n\n\n###################################### BIP32 ##############################\n\nrandom_seed = lambda n: \"%032x\"%ecdsa.util.randrange( pow(2,n) )\nBIP32_PRIME = 0x80000000\n\n\ndef get_pubkeys_from_secret(secret):\n    # public key\n    private_key = ecdsa.SigningKey.from_string( secret, curve = SECP256k1 )\n    public_key = private_key.get_verifying_key()\n    K = public_key.to_string()\n    K_compressed = GetPubKey(public_key.pubkey,True)\n    return K, K_compressed\n\n\n# Child private key derivation function (from master private key)\n# k = master private key (32 bytes)\n# c = master chain code (extra entropy for key derivation) (32 bytes)\n# n = the index of the key we want to derive. (only 32 bits will be used)\n# If n is negative (i.e. the 32nd bit is set), the resulting private key's\n#  corresponding public key can NOT be determined without the master private key.\n# However, if n is positive, the resulting private key's corresponding\n#  public key can be determined without the master private key.\ndef CKD_priv(k, c, n):\n    is_prime = n & BIP32_PRIME\n    return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)\n\n\ndef _CKD_priv(k, c, s, is_prime):\n    order = generator_secp256k1.order()\n    keypair = EC_KEY(k)\n    cK = GetPubKey(keypair.pubkey,True)\n    data = bytes([0]) + k + s if is_prime else cK + s\n    I = hmac.new(c, data, hashlib.sha512).digest()\n    k_n = number_to_string( (string_to_number(I[0:32]) + string_to_number(k)) % order , order )\n    c_n = I[32:]\n    return k_n, c_n\n\n# Child public key derivation function (from public key only)\n# K = master public key\n# c = master chain code\n# n = index of key we want to derive\n# This function allows us to find the nth public key, as long as n is\n#  non-negative. If n is negative, we need the master private key to find it.\ndef CKD_pub(cK, c, n):\n    if n & BIP32_PRIME: raise\n    return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))\n\n# helper function, callable with arbitrary string\ndef _CKD_pub(cK, c, s):\n    order = generator_secp256k1.order()\n    I = hmac.new(c, cK + s, hashlib.sha512).digest()\n    curve = SECP256k1\n    pubkey_point = string_to_number(I[0:32])*curve.generator + ser_to_point(cK)\n    public_key = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )\n    c_n = I[32:]\n    cK_n = GetPubKey(public_key.pubkey,True)\n    return cK_n, c_n\n\n\ndef xprv_header(xtype):\n    return bfh(\"%08x\" % XPRV_HEADERS[xtype])\n\n\ndef xpub_header(xtype):\n    return bfh(\"%08x\" % XPUB_HEADERS[xtype])\n\n\ndef serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\\x00'*4, child_number=b'\\x00'*4):\n    xprv = xprv_header(xtype) + bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k\n    return EncodeBase58Check(xprv)\n\n\ndef serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\\x00'*4, child_number=b'\\x00'*4):\n    xpub = xpub_header(xtype) + bytes([depth]) + fingerprint + child_number + c + cK\n    return EncodeBase58Check(xpub)\n\n\ndef deserialize_xkey(xkey, prv):\n    xkey = DecodeBase58Check(xkey)\n    if len(xkey) != 78:\n        raise BaseException('Invalid length')\n    depth = xkey[4]\n    fingerprint = xkey[5:9]\n    child_number = xkey[9:13]\n    c = xkey[13:13+32]\n    header = int('0x' + bh2u(xkey[0:4]), 16)\n    headers = XPRV_HEADERS if prv else XPUB_HEADERS\n    if header not in headers.values():\n        raise BaseException('Invalid xpub format', hex(header))\n    xtype = list(headers.keys())[list(headers.values()).index(header)]\n    n = 33 if prv else 32\n    K_or_k = xkey[13+n:]\n    return xtype, depth, fingerprint, child_number, c, K_or_k\n\n\ndef deserialize_xpub(xkey):\n    return deserialize_xkey(xkey, False)\n\ndef deserialize_xprv(xkey):\n    return deserialize_xkey(xkey, True)\n\ndef xpub_type(x):\n    return deserialize_xpub(x)[0]\n\n\ndef is_xpub(text):\n    try:\n        deserialize_xpub(text)\n        return True\n    except:\n        return False\n\n\ndef is_xprv(text):\n    try:\n        deserialize_xprv(text)\n        return True\n    except:\n        return False\n\n\ndef xpub_from_xprv(xprv):\n    xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)\n    K, cK = get_pubkeys_from_secret(k)\n    return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)\n\n\ndef bip32_root(seed, xtype):\n    I = hmac.new(b\"Bitcoin seed\", seed, hashlib.sha512).digest()\n    master_k = I[0:32]\n    master_c = I[32:]\n    K, cK = get_pubkeys_from_secret(master_k)\n    xprv = serialize_xprv(xtype, master_c, master_k)\n    xpub = serialize_xpub(xtype, master_c, cK)\n    return xprv, xpub\n\n\ndef xpub_from_pubkey(xtype, cK):\n    assert cK[0] in [0x02, 0x03]\n    return serialize_xpub(xtype, b'\\x00'*32, cK)\n\n\ndef bip32_derivation(s):\n    assert s.startswith('m/')\n    s = s[2:]\n    for n in s.split('/'):\n        if n == '': continue\n        i = int(n[:-1]) + BIP32_PRIME if n[-1] == \"'\" else int(n)\n        yield i\n\ndef is_bip32_derivation(x):\n    try:\n        [ i for i in bip32_derivation(x)]\n        return True\n    except :\n        return False\n\ndef bip32_private_derivation(xprv, branch, sequence):\n    assert sequence.startswith(branch)\n    if branch == sequence:\n        return xprv, xpub_from_xprv(xprv)\n    xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)\n    sequence = sequence[len(branch):]\n    for n in sequence.split('/'):\n        if n == '': continue\n        i = int(n[:-1]) + BIP32_PRIME if n[-1] == \"'\" else int(n)\n        parent_k = k\n        k, c = CKD_priv(k, c, i)\n        depth += 1\n    _, parent_cK = get_pubkeys_from_secret(parent_k)\n    fingerprint = hash_160(parent_cK)[0:4]\n    child_number = bfh(\"%08X\"%i)\n    K, cK = get_pubkeys_from_secret(k)\n    xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)\n    xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)\n    return xprv, xpub\n\n\ndef bip32_public_derivation(xpub, branch, sequence):\n    xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)\n    assert sequence.startswith(branch)\n    sequence = sequence[len(branch):]\n    for n in sequence.split('/'):\n        if n == '': continue\n        i = int(n)\n        parent_cK = cK\n        cK, c = CKD_pub(cK, c, i)\n        depth += 1\n    fingerprint = hash_160(parent_cK)[0:4]\n    child_number = bfh(\"%08X\"%i)\n    return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)\n\n\ndef bip32_private_key(sequence, k, chain):\n    for i in sequence:\n        k, chain = CKD_priv(k, chain, i)\n    return k\n"
  },
  {
    "path": "lib/blockchain.py",
    "content": "# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@ecdsa.org\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport os\nimport threading\nimport struct\nfrom io import BytesIO\n\nfrom . import util\nfrom . import bitcoin\nfrom .bitcoin import *\nimport base64\n\nfrom .equihash import is_gbp_valid\nimport logging\nlogging.basicConfig(level=logging.INFO)\n\n# https://en.bitcoin.it/wiki/Target\nMAX_TARGET = 0x0007FFFFFFFF0000000000000000000000000000000000000000000000000000\n\ndef serialize_header(res):\n    r = b''\n    r += struct.pack(\"<i\", res.get('version'))\n    r += str_to_hash(res.get('prev_block_hash'))\n    r += str_to_hash(res.get('merkle_root'))\n    r += str_to_hash(res.get('hash_reserved'))\n    r += struct.pack(\"<I\", res.get('timestamp'))\n    r += struct.pack(\"<I\", res.get('bits'))\n    r += str_to_hash(res.get('nonce'))\n    r += ser_char_vector(base64.b64decode(res.get('n_solution').encode('utf8')))\n    return r\n\ndef deserialize_header(f, height):\n    f = BytesIO(f)\n    h = {}\n    h['version'] = struct.unpack(\"<I\", f.read(4))[0]\n    h['prev_block_hash'] = hash_to_str(f.read(32))\n    h['merkle_root'] = hash_to_str(f.read(32))\n    h['hash_reserved'] = hash_to_str(f.read(32))\n    h['timestamp'] = struct.unpack(\"<I\", f.read(4))[0]\n    h['bits'] = struct.unpack(\"<I\", f.read(4))[0]\n    h['nonce'] = hash_to_str(f.read(32))\n    h['n_solution'] = base64.b64encode(bytes(deser_char_vector(f))).decode('utf8')\n    h['block_height'] = height\n    return h\n\n# def deserialize_header(f, height):\n#     h = {}\n#     h['version'] = struct.unpack(\"<I\", f.read(4))[0]\n#     h['prev_block_hash'] = hash_to_str(f.read(32))\n#     h['merkle_root'] = hash_to_str(f.read(32))\n#     h['hash_reserved'] = hash_to_str(f.read(32))\n#     h['timestamp'] = struct.unpack(\"<I\", f.read(4))[0]\n#     h['bits'] = struct.unpack(\"<I\", f.read(4))[0]\n#     h['nonce'] = hash_to_str(f.read(32))\n#     h['n_solution'] = base64.b64encode(bytes(deser_char_vector(f))).decode('utf8')\n#     h['block_height'] = height\n#     return h\n\ndef sha256_header(header):\n    return uint256_from_bytes(Hash(serialize_header(header)))\n\ndef hash_header(header):\n    if header is None:\n        return '0' * 64\n    if header.get('prev_block_hash') is None:\n        header['prev_block_hash'] = '00'*64\n    return hash_to_str(Hash(serialize_header(header)))\n\n\nblockchains = {}\n\ndef read_blockchains(config):\n    blockchains[0] = Blockchain(config, 0, None)\n    fdir = os.path.join(util.get_headers_dir(config), 'forks')\n    if not os.path.exists(fdir):\n        os.mkdir(fdir)\n    l = filter(lambda x: x.startswith('fork_'), os.listdir(fdir))\n    l = sorted(l, key = lambda x: int(x.split('_')[1]))\n    for filename in l:\n        checkpoint = int(filename.split('_')[2])\n        parent_id = int(filename.split('_')[1])\n        b = Blockchain(config, checkpoint, parent_id)\n        h = b.read_header(b.checkpoint)\n        if b.parent().can_connect(h, check_height=False):\n            blockchains[b.checkpoint] = b\n        else:\n            util.print_error(\"cannot connect\", filename)\n    return blockchains\n\ndef check_header(header):\n    if type(header) is not dict:\n        return False\n    for b in blockchains.values():\n        if b.check_header(header):\n            return b\n    return False\n\ndef can_connect(header):\n    for b in blockchains.values():\n        if b.can_connect(header):\n            return b\n    return False\n\n\nclass Blockchain(util.PrintError):\n    \"\"\"\n    Manages blockchain headers and their verification\n    \"\"\"\n\n    def __init__(self, config, checkpoint, parent_id):\n        self.config = config\n        self.catch_up = None # interface catching up\n        self.checkpoint = checkpoint\n        self.checkpoints = NetworkConstants.CHECKPOINTS\n        self.parent_id = parent_id\n        self.lock = threading.Lock()\n        with self.lock:\n            self.update_size()\n\n    def parent(self):\n        return blockchains[self.parent_id]\n\n    def get_max_child(self):\n        children = list(filter(lambda y: y.parent_id==self.checkpoint, blockchains.values()))\n        return max([x.checkpoint for x in children]) if children else None\n\n    def get_checkpoint(self):\n        mc = self.get_max_child()\n        return mc if mc is not None else self.checkpoint\n\n    def get_branch_size(self):\n        return self.height() - self.get_checkpoint() + 1\n\n    def get_name(self):\n        return self.get_hash(self.get_checkpoint()).lstrip('00')[0:10]\n\n    def check_header(self, header):\n        header_hash = hash_header(header)\n        height = header.get('block_height')\n        return header_hash == self.get_hash(height)\n\n    def fork(parent, header):\n        checkpoint = header.get('block_height')\n        self = Blockchain(parent.config, checkpoint, parent.checkpoint)\n        open(self.path(), 'w+').close()\n        self.save_header(header)\n        return self\n\n    def height(self):\n        return self.checkpoint + self.size() - 1\n\n    def size(self):\n        with self.lock:\n            return self._size\n\n    def update_size(self):\n        p = self.path()\n        self._size = os.path.getsize(p)//bitcoin.HEADER_SIZE if os.path.exists(p) else 0\n\n    def verify_header(self, header, prev_header):\n        if prev_header:\n            prev_hash = hash_header(prev_header)\n            if prev_hash != header.get('prev_block_hash'):\n                raise BaseException(\"prev hash mismatch: %s vs %s\" % (prev_hash, header.get('prev_block_hash')))\n        _powhash = sha256_header(header)\n        target = self.bits_to_target(header['bits'])\n        if _powhash > target:\n            raise BaseException(\"insufficient proof of work: %s vs target %s\" % (int('0x' + _powhash, 16), target))\n        nonce = uint256_from_bytes(str_to_hash(header.get('nonce')))\n        n_solution = vector_from_bytes(base64.b64decode(header.get('n_solution').encode('utf8')))\n        if not is_gbp_valid(serialize_header(header), nonce, n_solution,\n            NetworkConstants.EQUIHASH_N, NetworkConstants.EQUIHASH_K):\n            raise BaseException(\"Equihash invalid\")\n\n    def verify_chunk(self, index, data):\n        num = len(data) // bitcoin.HEADER_SIZE\n        prev_header = None\n        if index != 0:\n            prev_header = self.read_header(index * NetworkConstants.CHUNK_SIZE - 1)\n\n        for i in range(num):\n            raw_header = data[i*bitcoin.HEADER_SIZE:(i+1) * bitcoin.HEADER_SIZE]\n            header = deserialize_header(raw_header, index*NetworkConstants.CHUNK_SIZE + i)\n            self.verify_header(header, prev_header)\n            prev_header = header\n\n    def path(self):\n        d = util.get_headers_dir(self.config)\n        filename = 'blockchain_headers' if self.parent_id is None else os.path.join('forks', 'fork_%d_%d'%(self.parent_id, self.checkpoint))\n        return os.path.join(d, filename)\n\n    def save_chunk(self, index, chunk):\n        filename = self.path()\n        d = (index * NetworkConstants.CHUNK_SIZE - self.checkpoint) * bitcoin.HEADER_SIZE\n        if d < 0:\n            chunk = chunk[-d:]\n            d = 0\n        self.write(chunk, d)\n        self.swap_with_parent()\n\n    def swap_with_parent(self):\n        if self.parent_id is None:\n            return\n        parent_branch_size = self.parent().height() - self.checkpoint + 1\n        if parent_branch_size >= self.size():\n            return\n        self.print_error(\"swap\", self.checkpoint, self.parent_id)\n        parent_id = self.parent_id\n        checkpoint = self.checkpoint\n        parent = self.parent()\n        with open(self.path(), 'rb') as f:\n            my_data = f.read()\n        with open(parent.path(), 'rb') as f:\n            f.seek((checkpoint - parent.checkpoint)*bitcoin.HEADER_SIZE)\n            parent_data = f.read(parent_branch_size*bitcoin.HEADER_SIZE)\n\n        self.write(parent_data, 0)\n        parent.write(my_data, (checkpoint - parent.checkpoint)*bitcoin.HEADER_SIZE)\n        # store file path\n        for b in blockchains.values():\n            b.old_path = b.path()\n        # swap parameters\n        self.parent_id = parent.parent_id; parent.parent_id = parent_id\n        self.checkpoint = parent.checkpoint; parent.checkpoint = checkpoint\n        self._size = parent._size; parent._size = parent_branch_size\n        # move files\n        for b in blockchains.values():\n            if b in [self, parent]: continue\n            if b.old_path != b.path():\n                self.print_error(\"renaming\", b.old_path, b.path())\n                os.rename(b.old_path, b.path())\n        # update pointers\n        blockchains[self.checkpoint] = self\n        blockchains[parent.checkpoint] = parent\n\n    def write(self, data, offset):\n        filename = self.path()\n        with self.lock:\n            with open(filename, 'rb+') as f:\n                if offset != self._size*bitcoin.HEADER_SIZE:\n                    f.seek(offset)\n                    f.truncate()\n                f.seek(offset)\n                f.write(data)\n                f.flush()\n                os.fsync(f.fileno())\n            self.update_size()\n\n    def save_header(self, header):\n        delta = header.get('block_height') - self.checkpoint\n        data = serialize_header(header)\n        assert delta == self.size()\n        assert len(data) == bitcoin.HEADER_SIZE\n        self.write(data, delta*bitcoin.HEADER_SIZE)\n        self.swap_with_parent()\n\n    def read_header(self, height):\n        assert self.parent_id != self.checkpoint\n        if height < 0:\n            return\n        if height < self.checkpoint:\n            return self.parent().read_header(height)\n        if height > self.height():\n            return\n\n        delta = height - self.checkpoint\n        name = self.path()\n        if os.path.exists(name):\n            with open(name, 'rb') as f:\n                f.seek(delta * bitcoin.HEADER_SIZE)\n                h = f.read(bitcoin.HEADER_SIZE)\n        return deserialize_header(h, height)\n\n    def get_hash(self, height):\n        return self.hash_header(self.read_header(height))\n\n    def hash_header(self, header):\n        return hash_header(header)\n\n    def bits_to_target(self, bits):\n        bitsN = (bits >> 24) & 0xff\n        # if not (bitsN >= 0x03 and bitsN <= 0x1d):\n        #     raise BaseException(\"First part of bits should be in [0x03, 0x1d]\")\n        bitsBase = bits & 0xffffff\n        # if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):\n        #     raise BaseException(\"Second part of bits should be in [0x8000, 0x7fffff]\")\n        if bitsN <= 3:\n            return bitsBase >> (8 * (3 - bitsN))\n        else:\n            return bitsBase << (8 * (bitsN - 3))\n\n    def target_to_bits(self, target):\n        c = (\"%064x\" % target)[2:]\n        while c[:2] == '00' and len(c) > 6:\n            c = c[2:]\n        bitsN, bitsBase = len(c) // 2, int('0x' + c[:6], 16)\n        if bitsBase >= 0x800000:\n            bitsN += 1\n            bitsBase >>= 8\n        return bitsN << 24 | bitsBase\n\n    def can_connect(self, header, check_height=True):\n        # import pdb; pdb.set_trace()\n        height = header['block_height']\n        if check_height and self.height() != height - 1:\n            self.print_error(\"cannot connect at height\", height)\n            return False\n        if height == 0:\n            return hash_header(header) == NetworkConstants.GENESIS\n        try:\n            prev_header = self.read_header(height - 1)\n            prev_hash = self.hash_header(prev_header)\n        except:\n            return False\n        if prev_hash != header.get('prev_block_hash'):\n            return False\n        try:\n            self.verify_header(header, prev_header)\n        except BaseException as e:\n            import traceback\n            traceback.print_exc()\n            self.print_error('verify_header failed', str(e))\n            return False\n        return True\n\n    def connect_chunk(self, idx, hexdata):\n        try:\n            data = bytes.fromhex(hexdata)\n            self.verify_chunk(idx, data)\n            self.print_error(\"validated chunk %d\" % idx)\n            self.save_chunk(idx, data)\n            return True\n        except BaseException as e:\n            import traceback\n            traceback.print_exc()\n            self.print_error('verify_chunk failed', str(e))\n            return False\n"
  },
  {
    "path": "lib/checkpoints.json",
    "content": "[\n    [\n        \"000000065093005a1a46ee95d6d66c2b07008220ca64dd3b3a93bbd1945480c0\",\n 14134776517815698497336078495404605830980533548759267698564454644503805952\n    ]\n\n]\n\n"
  },
  {
    "path": "lib/checkpoints_testnet.json",
    "content": "[\n    [\n        \"00000000864b744c5025331036aa4a16e9ed1cbb362908c625272150fa059b29\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002e9ccffc999166ccf8d72129e1b2e9c754f6c90ad2f77cab0d9fb4c7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000009b9f0436a9c733e2c9a9d9c8fe3475d383bdc1beb7bfa995f90be70\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000a9c9c79f246042b9e2819822287f2be7cd6487aecf7afab6a88bed5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003a7002e1247b0008cba36cd46f57cd7ce56ac9d9dc5644265064df09\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000061e01e82afff6e7aaea4eb841b78cc0eed3af11f6706b14471fa9c8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003911e011ae2459e44d4581ac69ba703fb26e1421529bd326c538f12d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000a5984d6c73396fe40de392935f5fc2a8e48eedf38034ce0a3178a60\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000786bdc642fa54c0a791d58b732ed5676516fffaeca04492be97c243\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001359c49f9618f3ee69afbd1b3196f1832acc47557d42256fcc6b7f48\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000270dde98d582af35dff5aed02087dad8529dc5c808c67573d6dabaf4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000425c160908c215c4adf998771a2d1c472051bc58320696f3a5eb0644\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000006a5976471986377805d4a148d8822bb7f458138c83f167d197817c9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000318394ea17038ef369f3cccc79b3d7dfda957af6c8cd4a471ffa814\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000ad4f9d0b8e86871478cc849f7bc42fb108ebec50e4a795afc284926\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000207e63e68f2a7a4c067135883d726fd65e3620142fb9bdf50cce1f6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003b426d2c12ee66b2eedb4dcc05d5e158685b222240d31e43687762\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000017cf6ee86e3d483f9a978ded72be1fa5af37d287a71c5dfb87cdd83\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004b1d9fe16fc0c72cfa0395c98a3e460cd2affb8640e28bca295a4a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000046d191b09f7726e4f8bfaffed6c30734afbf1f95e6bddbe0b07d9e88\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000082cec8200e9ea055c2991bf74560eb7e7140691ea53e7828dbdc9553\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003775b96d6b362d4804afe2d9c3cf3cbb46a45c3ccc377c94e83edd23\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000037835a92404acb2f18768a49d4f93685ead30aad6bb3b073f411e02\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000006cf75d17706d1f62e6b08e6ba5facfde38a8920b7d808a6b6781ff2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000003dff257cdae43703fcd0ca91fda0970f5fc04258b4608fb1942a6f6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000532d97d18867658e08c789f627535652382147e33bf8626d4131bc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000266dfb79bb11dedd0ae748505863ab3ab731269cd71a2c2fbd159b3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000349ff0119d5c0dd8ffad8bf41cd6126a88416148b81fa4dcaebc42e1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003c61939b4799eeea4335218d30de9b1071605126d719dce0f0d14810\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003d9284570ed648d2b12ad24046ac8b9abcf05c4e9813ea110490cf73\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001360b66e6dc0ccfbd75356034e721ae55c3d5c71a58be5d281c252b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000c114f42504916bfb2ee26ed8307b3f7f74226c1cfe1f5302ec23d26\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000007acac3fcf97b4ca81821263b704364adaa2736fce0a0722bfed4f8d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000059768ef7731d27f9c2be48c6e16d7cb56680625f08ff25ead504280\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000351c8908f1f52518ce4bd251b896ca3fbccb69a2607db6624bafcfc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000068d7ccae048e212e9e2ecb4d944f583b4490df4fbf654b4915597052\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000e2aaa36417187233ff55325473bd5b7a164b358da60c96d1920fd77\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001eb11ef6dbe0647bc87a8d218f6e59c2b9690f17edcf0dbd39cd0308\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000022e7855e24cc3fff67ce093242434a8ffa45882333a0f08a40aad9c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000210130ff4e3186258c09a8463c1e196f5c5432b4c7b6954e907bf63\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000e01372ede322bf88ee5ed8a46dd4fd8df832eca16180263fc8b1ef\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000a0701896e26d5d884834b267512e0af52c92edc4bccf1c5c803d3c4f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000869fc8d9ac1588f3e5bdfd60253e9824083800b7794010e0e9c6b6fe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001d43b3165ec30736f28f0761600b092686f861db23ec38f2d92b0ec6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000ef4092da8c2056e5933de0e1530194c3ad941a9b393fbb26f98862e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001e3fed39f70023909f962bea146b03bc8e94e5d19d7da93123f4f64\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000b4b8c877bbe3cde97649845290bb78999ecff4621b9bf2ab16aa2e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000006095ba3b4742883a0ec427a3fd685ffb65b987ea77ebfedea7da82\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000168f0a76a6068a34fc042553aff4aa63b906028f28c2a4c327328e1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000af10f3079b4989ac4ff0baaecab38220510cdae9672d6922e93919\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000312791ada0f6a4c5eaf2a1cd57cd06f5970a8ab49923817b862c35\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000055f3d4f45c4d199d9c230cb2cfeb68c8e934cfd061bd616358655a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000036b6129bb5a786bfdd75cb4b932f7dcae9da469d3ba35096f1e821\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002fbccf271c13e486673251ecd7951ecc12ee73c4390e0ff09e9b59\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000314e297a81bf002fc40eb391d8883ea45ee4e782385aa0fdba6452\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000d3c473819ec3b3c268f7b555df22772e407bc8f246a47cfc579ec61f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000075a438fda6bdb391263d0a2a6e8e68edd9dd8f70fe5734eab9351eb8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000017ebae0a2bec50008b4a4ea8839798cbd9ff228e76aba087d0ff1736\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000800466ba31c0bbc12b125f16d05ed27788de045e25d6f093817d29c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002163c41f2264f202e611aeb9ba6c0a3ee95cd8e5e7e571edc64edf\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000de9882d417786fce8c755cfaad17f40cda744d4badedfe5e414e31\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002af352cf41f60a5ebf033bf7e4967c0597cee706ba877b795aefb4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000009ca0030f1dd0b09cc628f2d4d278c87b20781a1b136dc395debf\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000ffd27370a76d06a0da0e3805f47e35e2cf584d73d2c5ecaa2e525642\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000720da6910aa75099baa020cb8db37e1dc19cdff66152225b7609c23a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000a5c2cc704bce5e8527ce91bac7430c659624ecd86e6a1bb9b697962\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000084273545134e9a06483c8fab00c2b0628056bb1967f310c74a971bc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002f66f4da52804647b1c3e1f89d17bdb05e9cd4ebbd922007c773f21\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000c46146c9d0a67a354b3f82947e52670a3bded6d8513ab34a68ae18bd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002f61c429d7dbe7bde75796086efe574998766806138710a2d6001eba\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001daf3e3e78a57df2c2d2ddd14093d10515925e75c818bec3bbd30c2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002e133a7427a9aac6ceca969b27507c14111a45512cdf8f52a436de0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f7c4374d458666740de1d0e8c55229a209ced7c38e38708781487c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000035bb9ea329ba30b83eeb4ea6f57c2fe703b97f9b879f21e22643e0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001220503e0aaee266bca85de09ce97b0091f24972d1ad1c8afe8609\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000010a614c60457f8d2ae2bb826d037f52113252888fadda8ed773c9c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000585a8b882ecff8aa8434feeac4ef199ca669bd81ed473e37f0bb4528\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000009504ffdb5fe82ad88218fb5e75a8bc185247e30e22d23b9fd9b7f282\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000ddec7d73bcd653168d82e34cf5746e006bccda8a9c031c3289b9568\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000cb6620ee4e8cb8b6b4d51251e5961f7ae2e83538ab3a4fef3bcc773\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000239224a0841738513c1eda712b73266ea958aa75f44a3985ebfab82\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002630c7c3586fcc19079300403c54dc293bcfdf8a9981f85a5c31bc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000028d8c34f44e51fd71f5401094a983f6566e6d08ce86ec5d1bd639c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000dca95f1828adc3c37b4625f60aeb35a6614a4358322b7a6bc2f7d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000d72ec84fda18959ddc474d1a31a3a13b1d94695136c4810af8c01a0b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000327c29604996eb7f0a208160969ee4408a1cad277a956334f94e0f35\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000e1bd41d009c1910fcfee7bf1cc1adb04b0b7a632ac36c1092f01bb7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000201a5afed48b9d095b949229e9882ef8bc96767be3097c87264dfb6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003f28e8f3f9c80b1269bb0aa3b57501c12458550ef04fd43aca6a33\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000029e09fc14e38a6a0103c8c67383f41af7d76998055682525f4ca89\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000285ce297602995582ba5d32d583d618a6a92643566e25dd36cf2b7ab\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000657045fa54fac52b8480dc84bd4c418940ba63679f4bd6add6a39962\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000017b7bb58be05a47ff7c4ead27db750813d6bcf3f99cbcc35324cf445\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003a310e39b6df17f17450496b4f5c1593399bfa1ab8b4d39bac9b25\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000bfbc5294f003548a9636ebbcea3ba42577821266317676fbc363c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002329351dd70c24da2eea5ac19f65b6053c4611aa4eb93bcc2783c57e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000004ce02f1005aa6fa4d158c6e4fce95ab053d88ae74881dd080c24e057\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000fdaaa54cdaade8cfb75245de0747c60c0307ad11be9fe154535565\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000003dc49f7472f960eedb4fb2d1ccc8b0530ca6c75ed2bba9718b6f297\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000014ca604d769d4b99fff03ae3ac84d1e8eb991c5dac7c3cd4d9e68ee\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000190ab8ecef3a3d5583563851672d81a4d4d952b8cf3bd503c655e5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001204d263b607987fab11e1c19c94b7e3e674cc73cc2fb7b05fbf07\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000141e8d7f7ac359a8ae58e35ce6010c25ddd6f1881f41c0b939332e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000946344dd06ef5ddd13fb74f20c475daf911ff4e3f1dcdf64c330e274\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000ec77a7892e48b85bcbaf404d16d7fc93747d7e9e3ba6195a9b6f1525\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000018a305c04dea8e93e423ce9569872e0ec5af49d23a0e3872b0ad6297\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000055e32c5f8a86c9a712eeb6440bbf9810ae6da12d0cea2493138a885\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001913fcbe67badbce4234e86e35a1ea867ecd69814b5f5ab039b7d4b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002c71fe4403aee704720ceafd21f9f8c9c97a8bfbd25bb46223aa40\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000343a42da0c811836d0785c272591facd816f0e7fdcfb1109d8f9a8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000309b182608b3eea7fafd0d72e3c79a0a3a9cda03cde3947e332e1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000204cc04e421c3958a64d7bc024a474ce792d42ab5b48a5a6f3927\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000005eaa010e7255bd37e0b00780575074a74d889e17c4dbc578f917348d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000a0d425f62d9196c069286dc6635ded9d027de40070d397e45bd63e0e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003355fd37068ce2d5d2a94ef964eeb9b687f21f4a00850a3e6cc4a71f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000ca9148dabe9424cd8c96860c90d836ab25970a3e91856764e2e640c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000bde23f829dde8edef35436be4b8978da21fd2c3a8100ef5334e3cc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000028bb26f1427fbfabeae65d55a9e59e18230713e40f0f7c9c2dee12\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002ac05422d254e597ee6b5e0f8be9b3e2f887486442d720c7766919\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000e36d0b6f187dd9601b1d1dcd987c3e0f6a081ffd039c7c5e32462\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000048d7b1f2a2a11fda34a5cfeea067ab03e482931e5a0f463f438ba\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000f780ab88c8a4f4247573a749fbb087a4e3fb6a7d29926de8a9ab3462\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000313bbe6a940e6a8c40ba091aa1ebbaad135bbbff3ed8ae07cf574d2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001d4ab29721aa2722482562670a0d71dc1eb73231c5dafb64756b04e8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000006588bcbdec38d19962b96cf0352cbf1b90f3379cc6787d018cdb96d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000022e79539a21ac24f9daa2cbddf2bb4a3125f88a5efc20d13ea856b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000dd284b7fee584cc578a10fbe57e8efe6bf6ebacb23c0ac5d46cdf7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001451143787f411c93d5506065c3fb597966f2fd7a4a5c078ee6aa2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000ca977394af1e414dc1f9d83efa007f7226e11d3a00f59a1fdfad1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000011f8caa80580e7a796bbce5b84e60731bf48e03c6ff5c6bba868e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001705beb1376af1af08b437acef6befbe7d3b60c5fbaf6bb7f38c9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000c838f1f45422d93ca9b5838368a37423efa8439ee24b2bf247a2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000111ad857d31d07fdc8b32d17af2522c18bdaccfef449b29d17362\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000312a7718fc616b0ecfdbf6066f71ec1a4a8c43f50f02f61cc398\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000007d232b217a59b804ef67091c5720a5460c2c16bf97b97a24801e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000177235c33695aced585685b4c500eb76e72caad02e17503900eb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000037f5c5890da7a8e2acd2b0669ad7db648ac43140c637a1c81637\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002123904063f223bc35135c426a4f9a0b74c1907e837b810f0321\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000961db809da357d91a9341170fafef9f24896d8730bd05cf3f96\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000d2e8fcd05eb874e98cfc3a6e239f6974950e6f50b0487513ecab760\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000017e362508c8db23fae0431eaed708d9db13e48fd5d318066bf6733f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000011b2bc4fe36f90b7ba5a62f974db250bfdc285b70c71148023c7e3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001be28570b378dd5dd2eb3aa495c229913b6757fe8900dfa3cce99\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000242bd0bb16d0a5324e0b4b5a83697dabb3b4a059084557478e50b9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d8ce69d18da32ed52e503d6b5ad48d970b90545f956b2d2af2edf6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000366655bf0cb3dd0cd7801e0adbd26b5b441b77a9e3642597effb00\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000dc7aa00d4607ca8374d40d1187f1c084b620edb45fc39bc8d2db8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000003baf60d9c6e70a765cf517f66a124509191188e9547ad09edf68b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000e0f476893b8fb4d37e855353075fde73dbc1fe181cc956349f19\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000032ed16b7de758abadf4a4fb2df7a101ff275c51f29e1555a89a5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000a564d03f0f2fe20f6fb5f038d931f732d817641cd7fff3b0acd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000011aa4d0fdcea8d4ca85cd5d548e322e2b6abd17f8444be855c5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000610588540267a0eb544531047d4c8af0f21fca7cd3d96205cfc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000002770dab5e14843149df8f76b8dc8458ed3ed2ed8a14a6e2e564\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000006b70ebc9f75bd32f466602cbd4b86c3c2d2379059542bb8bec6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000ef579af389fa7674f98a2371063fa8b218c5ca0ad94e21b896\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000021b6108dc988f9153383f9501ab9001109aa87902ddd4c8a4d1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000022c02ff22bc0af5201f0e1a14a75879c494731e4fbf999218c8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000032651c988edc1ccd08e82b888cbb8135e24a958ac0c0b640d5d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000015aefdfa0790bed326c38c358c07aac0674f5b2e771258b8df3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000822e1534c86afef911b67d3fa20cf2b12d93d20d64005f54d7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000338b871276768c923b1c603fd6150bd054c2287e532e61de7f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000002d0af52c0cae894bf836b61137ace2bd7500abd13a584c02741\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000006f8443a458f38d8731821c07a2fda0ecdbb1cf797f541844d468ce0c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000b6fbd8b4e227f5514979a61d8b0b918d2adc154e585ca926386704\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000f4f5e49b10278e27d9dee15b92f9d4a257138a206831e0c00188767\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002c7e9769bd8ae9906fc5682e937b5c31ab5b5b86e4d70af2c15a95c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f68a1db8cd387e0a2f93f45149fe1ee4a230bb386313bdd42058e8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f0f65c360c8f0f9853ad1142f16675dc1175d61afdbef977776b25\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000004f734e634156511cbef7dfefebdf317e7488aa6c2562572d7ecb7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002a46a7a16787e8317dc567ae26816324c2035be0186ba54d5cb8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001a593e6f01875b77e270163538d88452779bb557df7c2607c28e0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000004f24cfafa10bd50a452535f64be577a6161e51c7c71542f654c4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000597cce73e84b63f08cfcb9b01f5e7621752d8c8e08fabbd6ab5c0dd5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000007cad379df01247771fff471bc99faea1b86218602f45ab13efc5e9f6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000d6085aab25892be49c49d6c0a3949befdc3ddce2faa46b104e1e804\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002be5996786b42d6a229093896aea9966b1854ea261e01e84da1f420\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002684b72056e270b115d80b12b2f68eac7412355287226aecd9b5e0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000079ea27efb24366c87856a9e371c56fcbd59d09d3164a5c2fc15fcbca\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001694120525dba4548ca54087544da1fbefa51c38f0208d683418825d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000693e80d372938f3553151ab9d0a9a6922182591c701df739dc9a502\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002950d9cb23c8511937811910b712f73d448e6fdc2e39e029b86848b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000091c40056c6a48f33db17764af89c01f62ae653aa5e494146164cee\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001f373c47e1a39af4e1ebcd8c88411ec49d6bd520c2781564070971\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000809ca4b2170c57958709b867095b1972d80a2ee55359fbd0940fe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000038e7bd66fc3308447b1370dbdd0661c427c512bdbc641ff360fb2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000009a3325df76e2de1fc1970cc2f241fa8a41da9ad745a0d9666d9ff51d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000003176e92ff837bf43a48a995c1a321b166475f586ffb4b962e0254a4a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001ae3292e81ca3859b75bccd5bff825cd9f496efd085160c716ed05e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000033bdac4f0d36bb912fba28bb5caa54d1b611759a10f79ff3c969cf2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004c6db7fa0e2c9f08693abfeb128c5827b511a5c46c623a103b416b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003d87f48bb95e9431760d0c5f4f93c77d02fce9dd1673e9f5b01029\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000e214fc3d8b97571eb75d248ca29f8e25a584c33de8488ceee72b0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000133269b7159b828700d02de770a8cbd91f3d166e6bbc95d8e0dfc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000cc92e2dd933a08f7fd87f84451627982fb66583587858217c059\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000030708136c20c4c8216314005b3cb5c551ded33b26cf64d2ff47d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000c472a1341d479ed02f31b699e448c035049a7092670b38f4ec6121f0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000a358834d6eed41b9b7161a338aba53828111414cdea7552ed15548a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000e13e77372daea775c8358916e57ed11835899c14e5140ed9be11089\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000008252cd0931f94b2465bd4f93e4bfeec6697962c5b034cf3d12cf7c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000019812cd6cde3a43831234be71e68118be24a80161349b8b327acb5b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005865499f301adfb59f8380743e4c3b3ab220ca4eb97dc6628df626\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000015f77e1e61329560a4378eb401fa5bf0ef90b0a014a4d7857ca7a8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000e9cbcbb625e8a463ba8e7f14be46ba9538ffe93338784ccad3d992e8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000fb27169efcc2873cfaac223ebb91cc5e1e5ad7e9a312d42bedf7c42\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000c9c96d62ebfbf3fa4003f1d46d175140ab084dee17e8125fa40f24a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000311e3a766b1ab2064b68a344a561eb496d595126808ffb166c71cc1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000677568c82262ac3a4ca3f909bdfb0b35145ad490fa3fbdc719d06b91\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000ee77ba9ab657e51fd9140f5c9b46731d9341e98188f929c97d04746\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000008a67eb9c91a6d74168f3f385270fa942ea00bdd31924d1b6ea11148\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000017f93c9e0026e90d579e18c83b4a8557f0c00e9b85ab164cf4466c5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000994efa379235c03711a8e6b29895d928b5fde96cb01c02374c0602\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000b3be9f23c943d71d7c7dbdf6dd672d77a712f6c83e9796a85e4379f2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000713e1089b0b2bdcba462b740c9396f822f1c73e090713978a7f1314\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002fc44d358401a7ac9ce4ddcb17f3cbac08e40242e755e60ab2292ed\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000021ef2c04fd30be7049f73b9a8353ac96a467dd5f0b9c1457be1bc5e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000023b95b440ccbbdcb914172cf675cd15d6111bd7f5a436a4925d36e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001983521dbffd1b742a6d4b5dfda3f46579fbbdd83a2ebf9a039bec\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000044d53dbea312432e68fa90dc2148946f613216dbdeec86f6a67c1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000107667692f12d21a55a72ff1dce828f96872e36c35bfbae475a8d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000252d1d0c01744ec25af801ef7c57e2581c95295070b6a8a85bd5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001c1da54e16dc06158677024d9e74bff39bfaec83434ac33673fcc251\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000b4d0c6ae86bfdf7ba4c205fc3e6b3b6d63836b85e30e9d8bac922301\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002b16179cb022bf678bd847dd6fc1908d0df04abf0c7874981eb33ee7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000e6783554aae41856424d184dc4fa061f40676efd107e6f933a25641\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005ae4acbab519895b4b523d97a09e381c9e4b044e642f73b8c0f1b0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000010372b59c9595d947064804b75ab21868dd075a3842ab7d2df6181\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002f9f587ea304093be049d3142ac0c92f9c68928a4f82d12b929b69\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000005d4cae51b3c76dc3c61bed0c265c4f228c0c4d1d3d147146c34eb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001a5b6c0e0a0b485a490cb52ccdf9b22596656039b51545bb07be5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000d723d0976338edf55d08edab995dd6283cbb688855f0dca6e8f5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000bfebfae90208a82c7fa06c0f61674dbf1e4f9162e370656c38d611bb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000c91cd144b2a92ab5024c87f70cc1d76a4a7f26a82a98c5aaad62850\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000077c8114eb5cfb69c3924c699d0c70334360dd1daa95db0db4816953\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000348a6443e091db8f68e88a10afad7c6e3e5392247902c4b4feade43\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d63b70351e05829ad8a56336521b361b0d50eb7ea1f5b46c25b00a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004658603163f0ede572120a1bbfce8d313aa282ae54d2ffd9fe9079\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000048063b410c793db34856f23acfb19a0ce72f5997fa572773378c8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000228fb6e587fa593ff8b4764064bba8bfc2f43ba5b1f12af33d04a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000082e3ddb75c0ea2a98922b1556ce10346f9bb0cedd97ccb3fdf62\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000005571b54d4886b44b81c21dfbefa554cd7c23430e5aeff6b5ae2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000306a603ca1a0d961e08e103a9f13f3615163c3373d1bd2a67cadc2a7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000195d93ba7ae19832b622de86ebdadf3c78f1751ef2b2e9b0e3a530d8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000476d0d00cbc68bb20b4893f0e608b02a1e029b8c6c73e169c49e69\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000051348044bc10fc05960c244c3ccd3b3b6c145ffd9958a1c8bc0215\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001e4df369203badca9aedc28c240d592b12d284ce0b0463fc7537c09\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000091cc1ccd448b0ec9185618a84dea96f52477cfb9b9ca2b60cebe83\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000024a50299c0ef0c6dec9c64336b6cf5c1a1b0013e22fd4fcee1d7d1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000349248c1df06c3783d1270cd97ce7f605b9036fca0fdc2f0fbb96\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001afe6793e7427a3d780876d26eb7f2ded92563f991bf7302aea69\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000007148006e139e24d9fccc307661c9a0cbcd1af983487c2f0780c9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002734722a341984738177a3f6f264291424e4984f2128d921bf29\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000109b02caaa95e49a477757a41a42daed40e92f54fa09e63f5538cd2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000009a11c7ff8b8fa7fbff5a04c25906f701ab5bd67195736f9ccc839ab9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002b1d77f8e0cd60af1c62ef6d381e8905665b15a7fbc546d0c1a45e18\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002588cb017de9e2f23cea7edc5082f1b3faec890f9252d556efeac40\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000008b07f177adc24a4b1a64d2dbcfbcc903ba861d493e11d6b33af7dc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000bab8db5020aa8e052165275e8eb3e7c843533246bf6e4c8374757e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000138488fdca8bfc327e6dbd6c72c5f1dc5868d9c0ea886665b9b56b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000094021fc954efbf08be667fef1b817e8715d4093a561fc30264aa7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000e8183e64072db79adfc6c09b650c4178001be3fade4050b06005\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000004c93e8661c75974cd191c68dd66999da4f70d039c0ba4a12b970\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000021c675b3ec404bb996f5e68f9eeceeac6946e5a6822987824d33\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000ad85684d30f25d1ec34638f099df2f33b418a07307c68fe3c2d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000009c6add76ac42a1942c4ce74d25d1b8975d4e3ac8932185e785a44\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001e7d828d354716881683eb6fb5caec5d91afce298e4e3bcee9574924\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000a0e438ab203d8fd3e56100f2f14759f704bff6c699df0bb4e9aad64\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000b7d5c2895df8bc1fdf5d31e0f663564cb5cff3b18642c44a71b6248\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000193209ecd92fce00a75975446423d94a325ed525c15d5ab921da273\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000020835bdc30ac67efdbc785d15186914bc14e86387f97450df46418\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000c9078321f0030214c75e170b01ec664d39bab1b1e48460a54eb63\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000ac68b63d486ade190dc9108eb3730d25e7537649fe21c30e0121f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000002a94dfc5f4b677b251a7a7647dbb99c0803df8658222227fe3e3f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000b076bbef0e50593b1595ffb3d571e7ad95dbdf06dca8824ef7f3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000167075c8bcd24233d25cd268271c0e8fcb6f301ee1b6f6ff0341\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000013107aa587bcf12ac445330ff0325d73c5253f7e6a49ed8c50257bb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000090ff53d49c9ffd51511af8d5cba2038a8e25e3b17186b1bc941f43d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000d9e704d5607f77f8983cc56069571a3761d5bd5da55f05ec5d8e844\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002b2b4c0950fb6390f0ae860840e84eb0a82e5e8a9bc37c14bbf43b0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000be10137a2434dce1d97850b768ce878c1c80ec905f6e9f21e65fa7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005cd966f80183d4c048e63a5c14f649298dfd261d989d9e3c026bf4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000e8f30e55006a4082380c4b1a372b7ad919d3a9b0a52fe5ee881d3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000018c70a4c27bdba237ad19ebae5d3ca23f1394ccc746d73669a1c4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000022acc8432c883953227786f7a6560aeaf0176d232c8affa5b25b4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001854e95b28b4efcb2cfeb08c76d8cf1fb03f2055b3fb758f3a1c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000187080c2c39f5a3ea8be72ac4d3ec0d16b21cd34f1541bef23be\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001593766a3c63b524f658ec7690df467cc7bbcebbdb56385500d4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000012d6966dc51a41f2c617192169ec8418405e164ba83b9f7ecdfe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001d0c7d0a2605e127b00448b71e756ad96625116ab8ca18f74900\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000009cb439ea49282d257595ad1f7602856c16cc26fff423f7783c792\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000889282b98336c994d7420a639221e0484b511227fd616d78dbd028\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000071a4a2ad6767864bd21239c74c9912a40ca9fd3b209e21b66460d9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f3ed2c3c9a7c3a7291e859cecba8cf9243d23a4892e6be8ea9b70f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000006a4258ffdff8b7f6f4f685ce18c6eb1d7a1cf501ca9e02fcb7620a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004af78f1a109d1267a9c24d69c6a4b30fea49f0efa6c8834cf394f9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000193bf3efbb145747198470a81b2cd33c991057676742d5c22a64b2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006b436798c7e4a8c3bdbf054a66707feee5a18ce9ca57eb95bb48a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001db50c7caa3a02ea4f173343f958f334a8bf3f8638add9e69b34\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000003c621629cc0bcec5968d61d2e42c6673de4d46555118ad5001d8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001262bef2918265f6dd4534013a4650444054fb4f5e490c5ed57b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000120ceee972d70cc84430006645997c7337976c673bd75cbef2b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000ba16134dc0c418a116b97ad5deccd6bf6e3daa028a8a6a80d7823faf\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000a1a00d6d6fe0660e63402a5a7c7248589211594d37fd800456ce84b6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000394766cec78f962c29aaa715b66e3ad34e1f2323dba45e087cb3b395\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000008b15a3020676f5e084210ecc05f646885eca1cf6a10e9ae9e3995cc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000002cf7eb98abe784f6e516670a88b9028a6faabfd099a364c2dc5c42b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000054015fec337a9ee43eea501d2292f031f5bc1f09758d20f5cd3135\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000068d24d31a9f1192d848155a2f90939627bc456c9a337135a923fa\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006262bd09358258edcc455f9ba46b7f9d6e69d0f6b9da89488a4a5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000002327bf77ae67961463ea98a78dab06c24ac7d58b1727c5f856626\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000006672235c1606fbacd7861b16b267d203b4d687708eeb1fc25e6d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000ac0c9a39a47313a8715f125c46d6ea8be8741b99b1db4a8aae47\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000007e93f6578e7856aae0ecf6341e1312664d9e1d812ff254c37ae6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002a980acdb1443926875e7d4a57859b2b45ce3fa92c7716319f62\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000683bfd82c63514bc58a80daf699a6bcd040bb2a499540baf52463d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000373e6262928d7a6cac965b294aef35f90b72c85100ef91501775e06a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f7bc44061b65c62d4d7747138df127dd2a30f583c3ebb66a25c7a4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000212a71c38d0e13ab7c5646c949d4b7ca23afedbe351a43b7607043b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000a836e88f76ee5dcca1e884572f32f4460a3b024280738d76e98ced\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000413f6c1b1c9841961636bb3290f2410ba0731f3522c4ff3faa2e0e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000082336107412226110ab2a53016d4faad4deec048828507a300248\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000a91e7a3f35a23f01621dd051e314da617714991467131808d3bf\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000cd6576950f6f238227c3ba7f62405ed1bf3af4878c6dc1b04635\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000674099e9741e44da03e9531402a2607a19a65660b57470340828db\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000030c4744001ae85f9e6b46ed0664449927b86b8fbf25b22b851d23671\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002f5095ad1a12eb9eedf88ce1e7268368461b6b4e10051148f436cb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000057d3e2a77eadb8b9613cb839ab02a96094dd5d0a6d1f09026c3936\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004e0a28be887d6ed037cd9102cbbda7d6c9e584ba51f2c2dce96232\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000211346d8099f7ecea72481c4cd45591f5e0d7e347725ac2162f142\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000199ae9fc06c5acee766db6033b86f76c266cadefe1461c611c2198\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004c9e5748558d4f5a75bc824171e3b958152dfd6844330f1e907f8c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000137addf1521361dad1ee007eb9e6dd4eb8441492ebfaa3c240d556\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000054d4c77bb7964e5327c35760d87b890ea336aec5ecdeb783350738\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000006b7b06d04818e97a4df66164b471912f88d9cd02de4af6c8bbe74f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000380fa9858e3e90335c061a3776a26bee1e8b6851de33ec63670782\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000842598b03fb79ce7386e9f9181a02dcf1effc8f70d3ff7368ccd5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000003d3475edecd733fc7b82432882d9c9f1350a98ef8921b87db4dec\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000e330a8d57a38dbcc0b0a5dc7a4210f231b8082b9be5f9e4bce\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000218ff87fd50cfba2fd04203a78d2600cb2c4dcb039d803426e19\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000007c96e6e3ed3146260348ac79ea7dc2ec2ae6bf8dc203400a37721d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000005abaa10bf7260470c28ba32f1755b4cfd3734aad580681e39a9605a5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005e77c226e6fffccafa56055e68f0ea0a30101e6a243ab9b3e07db0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000e989fe27f85b89c1e852d7bc94b09033cc6c8b32fbbbd9383a9ae1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000091a1e962438583146293ef34156962445ffc5e81e4d0fe327d37ac\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000477978a6903217e2817d10e99bdfedb4f8bc396b96fd5b0b93b522\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000bfd9e5f13a9c03c48e8b58a937cf1ae2849160f1ca11f8fcced3c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000158dd3c31b6379887b4353ef2898c03b7ce55458fcd57cb6f0639\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000029d7009eb56b9d38366005576b82a9b59fc845522a34ad36a38a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000e6e207a82b8ad7136352204bb8e9ccfcd25885a715d3c65cbee997\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000fadc4429f50fc534ccac4db5e51a313df25034d6c5c25f7e83448c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000019c58defcfdab6c6ab9497685e61118effda4c2613bf44be19fcbd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006cf444d846093c5045d42ddc0986ca805f261476d0fd2eb474c39\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d0856a3d6a1e5b1ac7e388cc029bd8410b3b1489598974fe470568\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003d9aae63ed532b78082ca5386211e22410fd24ebd5318d1a4cd1da\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000345003879f86021a6d5e3fe93813246818c145947b7e225691177\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000175393730cde3e49de7af2b81ae736eee005a9f9c4a1e878c52ec\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000087a8c621c879aec2a897258632d6aa631b9a38ba4d564e08682a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002ea641b2975935bd9caf337b51ac9f9bb90a54f6ea6ee5d3112b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000c544f9b6a8cbab6d25caf949875622bf75139234850b10affe1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000f66fc4e37232a29f3389c493863a980d58a1d570eddd5268999\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001213fe2bbb8aacb1fc14983586e09db964151cb507956a81b35f25\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000ba82c2160602ddc1913bc4c133ad0af8848e014367c84110d00e05\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000b7a98b364b1cf9521275a915c7a1b3a0f0c052c7d8efb620ec0870\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000047dc62db23540ab4aee43e54812aedb623a2a158aa3244fc784722\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005291002da10e53c3855882251a6e5a425b5e639ef9be3bd05767ca\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000005ffbcbc0d9b380584bdc78050a6f0c3582b4c9c5103a150cbc71f5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000a7a69cc06b0a68b27a8fa5d29727ec3b6db8d32d61cf7489b5ff3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000007212eb8c49758d98cefaa6098da2b877a6055be341f5f7c0ad301\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000068d1099d8cf3f43f6d164f2925b1d52ede75640cc65ca020e1de1c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000008d5ddef4468a4414bd08184c2eba0ec536b85a743b1091828a6a884\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000acae40db93b589783b0cde70b98552955cb3c12f08de1b417d9008d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000066a51eaa3a54036f338719da3d5779180c0bc3787b533410de90e5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000008b521677a6e897950aac69640e52efb01b7af10bba3820ecd09a89\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001823f0e399311cab0fcf57403e094feebf99b22030bafd2004da87\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000bf821c2abf5bcd00ca96439ddf5b0b593be5601145fda5338efdc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000003f4fd19b2af0141289177014ecc6dce6ea8fb50bab93d4a291095\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000011842d892a02e55ca594caddc9f3cea1979ddffefc070eda8498\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000208aa0259d20f51c0e7b8895e18a93aea79af9b3832e710ef134\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000007218f849e72dee1f7fb6fcf36f3b6745c6468187ed2ed13287f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000f79f656cae641c2b74554c6ecd673c0c7550671c4c2af940661b3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000199b4d178c05fd1c3154c9a4632eadc7bfc734c4522176c977ce8a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000085d0682d481635cb2e6de2e4d9884589455a86194f0b222f9acb3c6\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000015972a5a6786a14b009bf582c4bbf7b9854591dd8d26f82b43ddaef\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000064bf72b7bdbfcbe96dbbd0efcaf7aa94c0f92cb4e6662819468fe4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003df36b7962bb4ad62266c462382eddc93f4bfeac464b95f7a89ee9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006516d3a9f424eb61db5dfb85aeee29708b78c65d24827bd926263\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001c1709fe1b294712638db356e89155650f6fbecde79ec47a92af7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000dfc23251344b593c16c28cd195abcb337519d7bc82175721a033\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000aae2dd2bf0b8581d137fcfa3d9c4cadbe3ef3834d7cae4268c0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000092a5baff3d9a5ae87689b2afe668e71bac3b342c7d383f0060f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000fa906eeff7d2e126698d88b8cda01d32ea2c039c26984daaa17a3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002d4315e5bdc2bcfdb245b914130764a50943a2b2e02ea3acf5c47b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000fc2bc9bb83e04cbe922d64719295bfef6320027725402306bcf1a0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000142690e7c334b97612746d6db208e6153bdfa8479d86d1b575feacd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000629a7820e8cdbbed18dcfe16c992152badc745ca73b9b34e53fb0d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000023c2e9dbf3fe03248e40f4ec3fb2dc81ac573d5a6a4f490c701877\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000013658a43b6d1c4be95fa36e32d3edf80716de3a8f7e98858016adb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000007c847295d8c4b6da9d8a64b57c3a2307e64387bf8882b9d35d6de\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000032bf90b823332af80bd2ea18f411f081c7dca8f2fe79d9215526b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000001bc0655da6f24c6952e811006897a0c6dd8b6bd94f178636c8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001e1d09b15393190cf686e25488db7fcbc2f1ebacc8165fe6e3a0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000cc79ae066badb4157def4067057cefd705bf87f1d832845a7ab36\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000014408398244b94b4eff6b54875802ede6df2d1d21915333a195719\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000114135a1bc757110c05162fa649b694db9569be117e34832c87257\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000009b15fb2bcee1af904989ba0761e4cddc6b3ee214c0bb07dac6211f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000012be506dde2c54adf355bdb41a457b0abec436202a3be73f0b052c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000963760ceb5fc65570650d494805e05c9d753f3ea6d44247ad3d08\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000bfec54977673f68b6fe5f088398e697d778fa7987f8bab6a70825\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000e7f428bb413c17032c0031af0d26133ba93f744a5a0c16cf7e1a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000036bc80378323c6eaff8ab350b6d89955f602960cb7c93d2feb4c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001ff8fd57798082ab5a7452ada211e1c3be38745155505601498829\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000020f960b535eac585e5810ad64f158c1142f0eecd925c8058172933\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000067bd89409368d221507a160e5c45972eeb01efe210054fe8e7d85\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003521f2d5ea3232d4835ca6c6bae083ba90458f67d4cd765ce93b09\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000005ab3ff3a0c484eff7b571fb78ce27d93f77a480074232e5ce0c1d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000001048c9eca7cc1cbb86946c04498052071f7e7c775bba565ada337c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000154caacde41be616f924d7d478812148242fba85605eefec9ac61\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000c34f75bd6f338c0206a31a8d5021cc2ded51e88a6ef4fe686d10\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001e0581d86c49a6ca14ba88639ef908abb09210b57989e06b1a1f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d0e6dc0bf830b50bde3e400e16ec4f772f92a55390e62d4aa73af3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000069c2501a2f32cc69af72a602ff674438ae04dd05516f72a71b9ab26\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000c926b38954550c9b8d363ff058c2eb135eebdb3e640cfa67df803d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000011e9ad9c18e9e2095c3662af5be1e918dff653758583aa45dc8197\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f311624ff4dcdf07400d0d2fec8b16b14c1c16babc377a2d85ad21\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002e455cabfdc2a8955e8ddfe717b12efe5b80937b0c0ad6ac977fc5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000fed8889a22339b340f599ac7908e790bfc3cfca9b78078a52d228\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000012ca4492956b3f859b00e5db14b54d422cd95c68c7150743db365\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000004c58e8f7bac59eb4a036764a4d8e0da51c0290858ab14fb72481\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002f60bc99563ff5b4b800c176fe8bde95e8f968fd6b53d74c9cef\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000bffd10a3fb0b5b86d8b2561f39d07f8a4c41dfa08e3e49b7db5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000006a296be9cd8fd4e3145c146863adbe08b71831abb8a869d032c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000c557f496e82891039ff22e277bd604be6e2e8b95e519bee91f9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000399b30d2111c4bf3051c1f7f2f35bba7ff290d92393341ae47df55\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001f88733439e4e8d3c474504aed62037faa16f3845b4c671f69732e26\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000018aa2f93d2ab76a7e2f1bf5b565b4a1b0ececb6ee46490984f6c0d4b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000005e22674fcf65ce7be896a0557205ab26d1f76d73a717f5f14a6d6ad\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000223d866b324c097973210f8fc715c9535908359d61d8e1ab2f0100\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000002b321fd6452ab43849bd7a781953ec4485554e0fdc579f2a52c90a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000173132748c51b5754b0341232325bd118455bf3c8d25164d3eb92a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000143158cdea5fbb9453bbe1a7a900e6feba1e2193e4f5c106d9fba\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000014677751456af5630025b3d9921a4eafb4d36a06498f0c6a84c56\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000243976cf2d30ecd3cb1fd0b805fba4da92d2758f78e1c6f8ae92\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001323db1ab3f247bcb1e92592004b43e4bed0966ed09f675cf269\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000017a410c22c4b6caf710f5ccf005d644caf276ea8626a538798d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000170b2b1374e3a0dfdce2fbc5e302e1e0e9fb419dc057c9959902d1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000015b4fad4d929630487680cda2d3aada138c58cc08241ef6dd4ab09\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000abebab869f1620843d413a3d9e06dc7d9f5201a414d547ace1f99\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000b0bdaf05c2fe8b12ebd2372f49d8eabcfbccdadd68b5e5b7c9565\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000ca1af42ee1be2c8895d94f39dab5fcdbe0b4b4065f4be534e7294\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000069d0cc8c0452bf86cff87db05232f801a162acab2d080d6e4e9ea9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000019c7f7685f5bdc3afbb5e978cb3f4f70fea7b2b410139741303b53\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000d3874ce21db78f4d1883ad9ae8b26c1d7c13f3d723ff85629d595\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000033f87c25275ff72b58630d8da90221f2c84bcbd77c8e615709f8b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000dc72adaaae6483eb6737de7d21b3a24b2426330e80b078ceaed1\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000002fb1337228db02ac464565271f22f045c1b6ee5e449f057a829\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000001902376ff640d3088899af0819dbd15f602156a13ac2fc8e94e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000007ee49761a1c8284a3b8acefa39e37e455be4773d648e2db794\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000005b4d495a77f57018dbc72bf47993d494349329a3c653f04ab93\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000009dcb3ae6d68828e2f5ccfd58780abb260354e74484106f81ce\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000a3ceb118021fb42d39be52db951c6f852bb9a241046e972706f7329a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000574e8e1c27fa54c77b4e7cd1b79de070f0d3ad5b383206ab9777d983\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000039d562f640c1743421d53e7e04c3e8ba222c339fff6f3d25b1d4a7fe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001cb1559d55c697871e18d5c26800f77fb11587241bfbec3b15e26\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006e01a93090319756c7ca826ef655feb0cc2ef9abcc59d67de5e5b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000a81aaf5a4c013032638a077af6aad8bc449d74daef8ad3a74419\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000087d0574963c1582f2161298e2de5e48f74566291ef9afc2be24a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000033251e71c347cd663945fb68efe82a8c6666c0b41e93f1c46658d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000f592857e6f0e4711b5b93fdf95f2b21a5963bde15be750a07908\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000004353c8426e18b942a5012934ddac8322b86d6ab98ed7c0ee86ed\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004f027845b699f42e7d0d30c530e99524c5f97186ce6a250a5fac42\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002fc6407edc060df90785082834867331e6746a43ed34a26fbdc5df64\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000048733007c91ea3665bd4e1653b10799e3f43abee0fe830ffbb3ad\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000025a9b1c5afceba0c78c4b0320797acdc1ad50b4e040f148fbff7f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000007ca6d026d27387edc1c5570de41c61bacbcb1dad2c0f300b49e637\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000258f683a77ad509da82a4fab24188fdb4b4690e212c50794a9abb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000015111bce7b6ac13c930484e14e31e13e43355cb4d63c8f1782440\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001ca074fdecac7749d95f28f10c83a7e13787fd865bfbe505382bc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001c11a6505dd44ab405fdc07ddfc015f3c1166a5d9352ab58b52c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000c83f7f8e1cab4efa08d6c68c4555fb6ab542e01b87edd8f56ac\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000009561d0ceba15388573d2a994aff24512ec3ed7d7881aa0997dd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000007dc7cfbbb94db1fbc076a70a1252fd595686b4d75b2ea77ed6ee9e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000251feb68a8c90852f73aeb29ebda191038737b7edd37c9475f4ac\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000013f9a97045ea9047654e514951288911b2c3986787c27bab49106\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000006e8c37735c61f22bec69f4cb7eba03172349e7012b7704652f3e83a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001f341add5657043d8e50e53ba079fe24966a2668f904be5579c84b9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000029a6275cd477d77939424bd183c2f1308a9912f45aa7cc9ed13b56\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000a0336239e5e1faedf5bd2eedf38c9a5ba34a832356aea70aeb102\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000003c1a2b25093a64eb624055f6a3a26e18b8e7ea2d9382ec7a3609a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001bd89bf7e8740ce22adfa6e8793bd1716a647e558ed1742ee8329\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001320421f1bb2c94000e11a621f581fc277c0e2911c3b89f680bd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000054ce90a949f5ae2d43c4ace599668c6ccbc50620f6d5705922ea7c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000200d16fea4857e6b73169cc593421a57971acdbcaf87a31d7d8d72c8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000e75602181c88f713b91c49de291ed878be305d25b75c0ec5fbe942\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000081f8169c3c3665f20351dc0fe499612ae232ec0b55858a8e5dc6e9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d7ad232e7593fb435d125343b8113bbdb3705ab58ac0e18c26cc79\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000076df615d887e33193ca2dc0f2fc0e70744512c95da6242e9b1a81\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000084a62093d1929843e74456686429b698a7ea9b1901c1565779f58\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000251d1da01e9de9fcaf3ca3a64bff78a5faf51a8e697dfab6b5e4b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000609a8798996b1f1fe0b66060a628eadc380d0d369a2318c2d0ec\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000014770aeab044a022e86d888a6ede75b6474022c71aead3a1db74\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000004101d04ebc90ade5d4b911aa13c038ecf25e9887d877203ddb8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000007c700410b61eb7ff1aaccbfc3a79e4e4484ad7a2b0eda4d91dc4b613\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000055ff438a031413ee042fd3c0a2b69be98690542806ff123b7988024\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000002eca5f9f2c3b656d2550662fdee4c95da133eade51a5cae653bc69fe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000c679b76ccf0c5b943095fdee8fa466311edbea2c4a05f9430ffef3f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000007c6f494e32d5d9de58fa008a770fdc0a7b4a141be5b7c2de3ab970\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000d5dcd5a26c8ad29c1293e70401e2f90d8288469df3816b8cc6d4aa\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000d754d94f36cacbfb620710672afb1558499cabe17ca62c54a7d3a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000004096bb78fba714b130f7f1f929e2803c75a7a85619f7a2b86567f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000020e686c38d44c35896df35f9f1b7723a82a826a5e2393c25ef68c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000504f9af6885c0cb6484109ea205a956c8efae9557a1f5b9233da\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000e8746e52e4320ec17e66434a3936a3825f7046fe874e92275fb\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000f48d818a9a026270c9f733f629959bea25192596d59874b1ce2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000eaa9214cb05b241828a1cfb0c4209fb7ea64429815d61f7c1d98939e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001f7f915a6002cce4edd5cba392307f3a199a520ee8937327a9135162\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000009674ee0c606d687bdcddf8e023462927e2902b3381bc4bb862a7397\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000001f3f3528c083a4b11eb2f04d8bbeca92b57f05d8282909bde78bc77\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000131917ac459aefb91774dbb42caeca497afc0cfd1766e0338cc7f88\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000027634444081e1289354cb50034a506bb306a2ac1d8280683771c5c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000017a852acff78fbee573329d45bb8b121e9f6fc1e4f687bb3778ada\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000006789e1a00eca982fb2827f680b254c4a0ecb005af4464f3585a02\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000015d2e9f54b1e9419d6b32ce68ae626cdd7f2a1954f22ca39ae0fa\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002f7893bc169165ed9fefb434b6201103f23cc84a747a68ff8797\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000008471ccf356a18dd48aa12506ef0b6162cb8f98a8d8bb0465902\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000596f00b9db53c4111bcde16f3781471c5307af1a996e34ec20a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000007b5d2406f64f5f5833c063a6906552e815e603140c00bca951\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000093ca5d935740a1b25f10ce092fd777c2bb521f3156619389ae78931e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000292f3a48559527341f72400a0f8a783aebcaae5bfa0e390dfaa5286b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001e852ed7ddf0108d1fce0f4f686f43c8c1b85bcb12c43e564dc7630e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000c4bea8fb1e7f3a1f3e6c6b3f71388c0ec7eef3de381853767e89f87\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000029ef31a21711b55c4300efa38ace0b706091e373f48285286f2c578\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000979060786bb008f193d3917e28667bb1b28329f3adadc172e4cce7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000019030ceb98013b1627517b45b04ee055ef445813bbebaa25fa1ed3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000adf202247bb794fc9a3c82cd8767143f1e6ed5f60940ee18b09a8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000b19061e2481d8be6183b3d881b0d58601072d2a32729435f6af3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000007a6d34f59b29e8d4da53e51e3414acd18527466d064945fe19fc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000002e66ca213a2c3e9eb5fa62de29feb83880a0bd29f90fca8ad199\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000b4ca10aa100728d0928f37db5296303db1b74ffe29e4a17b6cd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000143309f6b19567955743775f61f8dc6932c0b46cf5fb11c6c72\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000b04d5409b3ac60cc18c0b9a3d58b303594635a8f75a9d2abd5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000040a2699f62a552703a278608248c2ce823f4cd8845376e9a371\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000005cfcb850db7e83d4963994f958bae9b1de1483f5aeb3d449925\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000190f80220e70c1481153671a7c90fd856988c183ab0e3d9313df8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000009374563a06178641d06776f66554c2a094b5319f0801fe35cef72ccf\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000003e4e6e5e8e4a89e7de50eed104d4a49d2992ff101b6740beec7cb5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000618cd377d14aaa441cbdb92527894f98da316eca81664f8ab5488d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000d977ab2897885fee712f58612fce8c10ffbe9400326fe3429b77b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000c3575b487dd0f938c5bc744fa65ca4ca3a9c981b8bda903ec110b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000247ac689595ed8d62678bfe53e5af13c0f5455e558f5e6bb375c16\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000093d175376aa621176511f335a48f824b66d998e8082f85134a48b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000c0c0448fe922f2c737946297d35f2c25ad7cc223e11bbe58e1f8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000016abe4e7c10ddb658bb089b2ef3b1de3f3329097cf679eedf2b5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000242757cea5b68c52b83dd8c2eb9257492074fc69dfa30bd4cbf4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000006813f3dd7726a509fbe3101835db155dfd35d44aeae6aedb316\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000053cc4f39cff1c8cee1aff7e289a85dee84164d2d981afc8f17a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000000789724805cf1d37ef689acf52c47a460507f540d5e5ca79bfa\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000003d71618bb8952887f65540270a5e54d6246b9419e08831b5e4e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000251a513a33eadfad67c015f6e3b291dfd0ae1cc4bb3a43006dc\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000968009e3f8d6e6071e7def68298307717a9af6c2d44986deaae297d5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000062bcacb734df83bbfa3e1b9a8dfa570ecffb6c29eaaf8e9498cccd30\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000001d4618c0931bd3c25ee592c35341f30ff3b549a671f637b3c26ef414\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000418b329df96a004f1b652ad06a7ca295f9f2e711c412d00493f5a86\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000302bfb88e9027237d023c4b969e106c9a7a23a84103776de7880836\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000069b9f7d9134fd93c8b7e3af8b26bbcbb5553af02fb6ed644d7fca5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000411ec444240ee91e2777ad18b80dee854e3e838e32209e84774fa\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000007c73f322eba4dee5463305227c7e1a8139f1b7b296444f265052\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000129adf0f9c0242aedbb9d87935d67ee4ddea758c00344d4b6a29e\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000343594e671158b6e1b4b6499f6ad66e2aeabf1f6d295d3dba850\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000320f0d5c22ba22b588b97a0e02273034bcd53669b1c8c4eeda1b\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001e8cdb2d98471a5c60bdbddbe644b9ad08e17a97b3a7dce1e332\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000026c9994ccdd027e86f51a2e36812f754bd855a7f9b1ca56511\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000002746a820a2c08b35b8d0493c4b5d468fcc971b9c88003e84849\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000002949f844e92645df73ce9c093e5aac0d962a0fa13eb076eec835c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000156fbda67468ae2863993b98a41227c420246e4bc4e68c84df0e8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000003b43c6c807122c8dd10e2a0cffbf72946f41c97c1ab82d416f74d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000004e0635c2438b1b649007e5d424b3de846299a8db53049ebf4da0c\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000258e4b79e3cca2ab7d12b35ba77fc491572f2e794f0a10f5236d9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000f5816875d9fece105e499b0467b8fb23ea973c48d828a235acdebd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001353bbaec810af7a4c74b4964ae072361c0889ed6d59cf16db6fd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000b354d8c389473670ca6bed7e3dffa069f270d35ec9dad810af141\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000002fa1f39e7cd8730fa08085ba2b532146ad1ef3b400a13e835ca36\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000d2c7943eee59652a9783bff27e474a92ec206c5c6e3cdd58d0d7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000036034181b4d9a84a97490b49fbee4262b9cfb25a7bfc9c0eec9f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000007deb59381cce692f152fc902732d96a7e7d463bc83915b37c0a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000ea7d32833462c0f72ade0cae4766e6065caa4e510331929c56d16632\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000068fce0ddd370d4c8f9129a7bc7843e75fc57666202d3b90239e269\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000026b4a2212c9c9493f8bd9d5331cab6d8eda8ee017410e58a783ca069\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000009535ea2dc7e83c31cd17f1db1bb78b0a678fc0610844273de143bf5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000008607cbd5baca91d5b8b82ee965aace335744a3e21578af22bee8ba\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000030dcedae0f5e98c4e176f9569ce76c4d4135bb028fc3144ef381d9\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000297c3f0e3fa85731222ba934a955bf513247a72a33c74c498cadbe\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000020a0d4a1e8120cbdb486e758b58919c9df12e0edc8ca1f2795e94\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000078773afc9023182bfb6534a60158672e6bc6e8aa5052854da80\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000102ecdd67800807d9e137357805b9bbf8a439ed86bde5b19fbeb7\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000005c3d2e3c7ee737c67ab465533acb233e0df902c1525fc11c3a55\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000001a77771650cdbbceff87caa4461391ba6a4ddc9815b5b0ab47b0\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000071ec390bbd28fa2a84e52ab5b32901d0723d22646b04ae01dc3\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000005c3ec3194f710c6f26ee736d59cc935ddfa574440f39846433a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000001cc3df6924591939269d61ead563b9eb68402a2ca01d7ff99e2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000008c778b3554ceaf3a13a856acbfe46b5750fb86fd92ba30651c2852f4\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000107ca31f75f8ea76073dda3c33330b2706c1ec20c3ec240e853b65c5\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000006ba99b08e7f2869ce113e2ad7464891de7b4cfa96f330d706a2da46\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000f31036bd51b2818f6dfb90ada9be5019abf55fb15694b181e269865\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000004fcc101bc47eb7a379b9f608d5c00ac04d2d0ea165ae2937070796\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000044d5ca3eda838edef0df7e69e1934047f8482822ce58ff7a18466d\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000029bdfb157be6d400c4dd3370d98afdd8cd3db6f1ada8c19bbf4650\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000005e9699ad8035caa4f73af781ac2040c87b8aa77459b3607209aa8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000001c0ba033f7d85beeaa167c9bde0e192240653a7ff6d9b81679842\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000000e0176111f29e800b49c7b8c7226dbbf4df715f1a4f06bcaaa49\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000ac3bb2cf42192e9053f5384355228a2b3d70b4ece4d45773a5d5ddd2\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000f29f7b60842b1044b2db7998e9bcbd92f8ec6fe8d159c6d582f1f1a\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000352f86bc5f9760961a25de009940508bb2cd0b37f378fbc87dc97eef\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000e9b3086008679ed57f59857f64c3954368ba1088117dbf88d5839cd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000015324bd8fed0e61b62bd1d6c663b862cb98ea03c494a92e4a8d0af\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000020475a181b7a084b341860a72fc0c1fdfcc13a85adeb0471444b0f\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"0000000000031905c508a975707b74f24e733880382775ee0e6250666473e1d8\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000000ca38b15d2ea33a6eef505a9c661540a18882f79ba9a3c575a9bd\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"000000000002739979a7a89fa279303b6606885e750b19e91ed637d7f222b392\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000091e935fc266facc2c92759d5468a39aee5be6b76b519a9bc7567\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000006e339938254208203b67c3c400f703fc29535fc646699e36e58\",\n        [\n            0,\n            0\n        ]\n    ],\n    [\n        \"00000000000008f6f1d1150d77f93a7f1baa24b65ceb471b1825b2e92ca6997c\",\n        [\n            0,\n            0\n        ]\n    ]\n]"
  },
  {
    "path": "lib/coinchooser.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 kyuupichan@gmail\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom collections import defaultdict, namedtuple\nfrom math import floor, log10\n\nfrom .bitcoin import sha256, COIN, TYPE_ADDRESS\nfrom .transaction import Transaction\nfrom .util import NotEnoughFunds, PrintError\n\n\n# A simple deterministic PRNG.  Used to deterministically shuffle a\n# set of coins - the same set of coins should produce the same output.\n# Although choosing UTXOs \"randomly\" we want it to be deterministic,\n# so if sending twice from the same UTXO set we choose the same UTXOs\n# to spend.  This prevents attacks on users by malicious or stale\n# servers.\nclass PRNG:\n    def __init__(self, seed):\n        self.sha = sha256(seed)\n        self.pool = bytearray()\n\n    def get_bytes(self, n):\n        while len(self.pool) < n:\n            self.pool.extend(self.sha)\n            self.sha = sha256(self.sha)\n        result, self.pool = self.pool[:n], self.pool[n:]\n        return result\n\n    def randint(self, start, end):\n        # Returns random integer in [start, end)\n        n = end - start\n        r = 0\n        p = 1\n        while p < n:\n            r = self.get_bytes(1)[0] + (r << 8)\n            p = p << 8\n        return start + (r % n)\n\n    def choice(self, seq):\n        return seq[self.randint(0, len(seq))]\n\n    def shuffle(self, x):\n        for i in reversed(range(1, len(x))):\n            # pick an element in x[:i+1] with which to exchange x[i]\n            j = self.randint(0, i+1)\n            x[i], x[j] = x[j], x[i]\n\n\nBucket = namedtuple('Bucket',\n                    ['desc',\n                     'weight',      # as in BIP-141\n                     'value',       # in satoshis\n                     'coins',       # UTXOs\n                     'min_height',  # min block height where a coin was confirmed\n                     'witness'])    # whether any coin uses segwit\n\ndef strip_unneeded(bkts, sufficient_funds):\n    '''Remove buckets that are unnecessary in achieving the spend amount'''\n    bkts = sorted(bkts, key = lambda bkt: bkt.value)\n    for i in range(len(bkts)):\n        if not sufficient_funds(bkts[i + 1:]):\n            return bkts[i:]\n    # Shouldn't get here\n    return bkts\n\nclass CoinChooserBase(PrintError):\n\n    def keys(self, coins):\n        raise NotImplementedError\n\n    def bucketize_coins(self, coins):\n        keys = self.keys(coins)\n        buckets = defaultdict(list)\n        for key, coin in zip(keys, coins):\n            buckets[key].append(coin)\n\n        def make_Bucket(desc, coins):\n            witness = any(Transaction.is_segwit_input(coin) for coin in coins)\n            # note that we're guessing whether the tx uses segwit based\n            # on this single bucket\n            weight = sum(Transaction.estimated_input_weight(coin, witness)\n                         for coin in coins)\n            value = sum(coin['value'] for coin in coins)\n            min_height = min(coin['height'] for coin in coins)\n            return Bucket(desc, weight, value, coins, min_height, witness)\n\n        return list(map(make_Bucket, buckets.keys(), buckets.values()))\n\n    def penalty_func(self, tx):\n        def penalty(candidate):\n            return 0\n        return penalty\n\n    def change_amounts(self, tx, count, fee_estimator, dust_threshold):\n        # Break change up if bigger than max_change\n        output_amounts = [o[2] for o in tx.outputs()]\n        # Don't split change of less than 0.02 BTC\n        max_change = max(max(output_amounts) * 1.25, 0.02 * COIN)\n\n        # Use N change outputs\n        for n in range(1, count + 1):\n            # How much is left if we add this many change outputs?\n            change_amount = max(0, tx.get_fee() - fee_estimator(n))\n            if change_amount // n <= max_change:\n                break\n\n        # Get a handle on the precision of the output amounts; round our\n        # change to look similar\n        def trailing_zeroes(val):\n            s = str(val)\n            return len(s) - len(s.rstrip('0'))\n\n        zeroes = [trailing_zeroes(i) for i in output_amounts]\n        min_zeroes = min(zeroes)\n        max_zeroes = max(zeroes)\n        zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1)\n\n        # Calculate change; randomize it a bit if using more than 1 output\n        remaining = change_amount\n        amounts = []\n        while n > 1:\n            average = remaining / n\n            amount = self.p.randint(int(average * 0.7), int(average * 1.3))\n            precision = min(self.p.choice(zeroes), int(floor(log10(amount))))\n            amount = int(round(amount, -precision))\n            amounts.append(amount)\n            remaining -= amount\n            n -= 1\n\n        # Last change output.  Round down to maximum precision but lose\n        # no more than 100 satoshis to fees (2dp)\n        N = pow(10, min(2, zeroes[0]))\n        amount = (remaining // N) * N\n        amounts.append(amount)\n\n        assert sum(amounts) <= change_amount\n\n        return amounts\n\n    def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):\n        amounts = self.change_amounts(tx, len(change_addrs), fee_estimator,\n                                      dust_threshold)\n        assert min(amounts) >= 0\n        assert len(change_addrs) >= len(amounts)\n        # If change is above dust threshold after accounting for the\n        # size of the change output, add it to the transaction.\n        dust = sum(amount for amount in amounts if amount < dust_threshold)\n        amounts = [amount for amount in amounts if amount >= dust_threshold]\n        change = [(TYPE_ADDRESS, addr, amount)\n                  for addr, amount in zip(change_addrs, amounts)]\n        self.print_error('change:', change)\n        if dust:\n            self.print_error('not keeping dust', dust)\n        return change\n\n    def make_tx(self, coins, outputs, change_addrs, fee_estimator,\n                dust_threshold):\n        \"\"\"Select unspent coins to spend to pay outputs.  If the change is\n        greater than dust_threshold (after adding the change output to\n        the transaction) it is kept, otherwise none is sent and it is\n        added to the transaction fee.\n\n        Note: fee_estimator expects virtual bytes\n        \"\"\"\n\n        # Deterministic randomness from coins\n        utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins]\n        self.p = PRNG(''.join(sorted(utxos)))\n\n        # Copy the ouputs so when adding change we don't modify \"outputs\"\n        tx = Transaction.from_io([], outputs[:])\n        # Weight of the transaction with no inputs and no change\n        # Note: this will use legacy tx serialization as the need for \"segwit\"\n        # would be detected from inputs. The only side effect should be that the\n        # marker and flag are excluded, which is compensated in get_tx_weight()\n        base_weight = tx.estimated_weight()\n        spent_amount = tx.output_value()\n\n        def fee_estimator_w(weight):\n            return fee_estimator(Transaction.virtual_size_from_weight(weight))\n\n        def get_tx_weight(buckets):\n            total_weight = base_weight + sum(bucket.weight for bucket in buckets)\n            is_segwit_tx = any(bucket.witness for bucket in buckets)\n            if is_segwit_tx:\n                total_weight += 2  # marker and flag\n                # non-segwit inputs were previously assumed to have\n                # a witness of '' instead of '00' (hex)\n                # note that mixed legacy/segwit buckets are already ok\n                num_legacy_inputs = sum((not bucket.witness) * len(bucket.coins)\n                                        for bucket in buckets)\n                total_weight += num_legacy_inputs\n\n            return total_weight\n\n        def sufficient_funds(buckets):\n            '''Given a list of buckets, return True if it has enough\n            value to pay for the transaction'''\n            total_input = sum(bucket.value for bucket in buckets)\n            total_weight = get_tx_weight(buckets)\n            return total_input >= spent_amount + fee_estimator_w(total_weight)\n\n        # Collect the coins into buckets, choose a subset of the buckets\n        buckets = self.bucketize_coins(coins)\n        buckets = self.choose_buckets(buckets, sufficient_funds,\n                                      self.penalty_func(tx))\n\n        tx.add_inputs([coin for b in buckets for coin in b.coins])\n        tx_weight = get_tx_weight(buckets)\n\n        # This takes a count of change outputs and returns a tx fee\n        output_weight = 4 * Transaction.estimated_output_size(change_addrs[0])\n        fee = lambda count: fee_estimator_w(tx_weight + count * output_weight)\n        change = self.change_outputs(tx, change_addrs, fee, dust_threshold)\n        tx.add_outputs(change)\n\n        self.print_error(\"using %d inputs\" % len(tx.inputs()))\n        self.print_error(\"using buckets:\", [bucket.desc for bucket in buckets])\n\n        return tx\n\n    def choose_buckets(self, buckets, sufficient_funds, penalty_func):\n        raise NotImplemented('To be subclassed')\n\n\nclass CoinChooserRandom(CoinChooserBase):\n\n    def bucket_candidates_any(self, buckets, sufficient_funds):\n        '''Returns a list of bucket sets.'''\n        if not buckets:\n            raise NotEnoughFunds()\n\n        candidates = set()\n\n        # Add all singletons\n        for n, bucket in enumerate(buckets):\n            if sufficient_funds([bucket]):\n                candidates.add((n, ))\n\n        # And now some random ones\n        attempts = min(100, (len(buckets) - 1) * 10 + 1)\n        permutation = list(range(len(buckets)))\n        for i in range(attempts):\n            # Get a random permutation of the buckets, and\n            # incrementally combine buckets until sufficient\n            self.p.shuffle(permutation)\n            bkts = []\n            for count, index in enumerate(permutation):\n                bkts.append(buckets[index])\n                if sufficient_funds(bkts):\n                    candidates.add(tuple(sorted(permutation[:count + 1])))\n                    break\n            else:\n                raise NotEnoughFunds()\n\n        candidates = [[buckets[n] for n in c] for c in candidates]\n        return [strip_unneeded(c, sufficient_funds) for c in candidates]\n\n    def bucket_candidates_prefer_confirmed(self, buckets, sufficient_funds):\n        \"\"\"Returns a list of bucket sets preferring confirmed coins.\n\n        Any bucket can be:\n        1. \"confirmed\" if it only contains confirmed coins; else\n        2. \"unconfirmed\" if it does not contain coins with unconfirmed parents\n        3. \"unconfirmed parent\" otherwise\n\n        This method tries to only use buckets of type 1, and if the coins there\n        are not enough, tries to use the next type but while also selecting\n        all buckets of all previous types.\n        \"\"\"\n        conf_buckets = [bkt for bkt in buckets if bkt.min_height > 0]\n        unconf_buckets = [bkt for bkt in buckets if bkt.min_height == 0]\n        unconf_par_buckets = [bkt for bkt in buckets if bkt.min_height == -1]\n\n        bucket_sets = [conf_buckets, unconf_buckets, unconf_par_buckets]\n        already_selected_buckets = []\n\n        for bkts_choose_from in bucket_sets:\n            try:\n                def sfunds(bkts):\n                    return sufficient_funds(already_selected_buckets + bkts)\n\n                candidates = self.bucket_candidates_any(bkts_choose_from, sfunds)\n                break\n            except NotEnoughFunds:\n                already_selected_buckets += bkts_choose_from\n        else:\n            raise NotEnoughFunds()\n\n        candidates = [(already_selected_buckets + c) for c in candidates]\n        return [strip_unneeded(c, sufficient_funds) for c in candidates]\n\n    def choose_buckets(self, buckets, sufficient_funds, penalty_func):\n        candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds)\n        penalties = [penalty_func(cand) for cand in candidates]\n        winner = candidates[penalties.index(min(penalties))]\n        self.print_error(\"Bucket sets:\", len(buckets))\n        self.print_error(\"Winning penalty:\", min(penalties))\n        return winner\n\nclass CoinChooserPrivacy(CoinChooserRandom):\n    \"\"\"Attempts to better preserve user privacy.\n    First, if any coin is spent from a user address, all coins are.\n    Compared to spending from other addresses to make up an amount, this reduces\n    information leakage about sender holdings.  It also helps to\n    reduce blockchain UTXO bloat, and reduce future privacy loss that\n    would come from reusing that address' remaining UTXOs.\n    Second, it penalizes change that is quite different to the sent amount.\n    Third, it penalizes change that is too big.\n    \"\"\"\n\n    def keys(self, coins):\n        return [coin['address'] for coin in coins]\n\n    def penalty_func(self, tx):\n        min_change = min(o[2] for o in tx.outputs()) * 0.75\n        max_change = max(o[2] for o in tx.outputs()) * 1.33\n        spent_amount = sum(o[2] for o in tx.outputs())\n\n        def penalty(buckets):\n            badness = len(buckets) - 1\n            total_input = sum(bucket.value for bucket in buckets)\n            # FIXME \"change\" here also includes fees\n            change = float(total_input - spent_amount)\n            # Penalize change not roughly in output range\n            if change < min_change:\n                badness += (min_change - change) / (min_change + 10000)\n            elif change > max_change:\n                badness += (change - max_change) / (max_change + 10000)\n                # Penalize large change; 5 BTC excess ~= using 1 more input\n                badness += change / (COIN * 5)\n            return badness\n\n        return penalty\n\n\nCOIN_CHOOSERS = {\n    'Privacy': CoinChooserPrivacy,\n}\n\ndef get_name(config):\n    kind = config.get('coin_chooser')\n    if not kind in COIN_CHOOSERS:\n        kind = 'Privacy'\n    return kind\n\ndef get_coin_chooser(config):\n    klass = COIN_CHOOSERS[get_name(config)]\n    return klass()\n"
  },
  {
    "path": "lib/commands.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport sys\nimport datetime\nimport copy\nimport argparse\nimport json\nimport ast\nimport base64\nfrom functools import wraps\nfrom decimal import Decimal\n\nfrom .import util\nfrom .util import bfh, bh2u, format_satoshis\nfrom .import bitcoin\nfrom .bitcoin import is_address,  hash_160, COIN, TYPE_ADDRESS\nfrom .i18n import _\nfrom .transaction import Transaction, multisig_script\nfrom .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED\nfrom .plugins import run_hook\n\nknown_commands = {}\n\n\ndef satoshis(amount):\n    # satoshi conversion must not be performed by the parser\n    return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount\n\n\nclass Command:\n    def __init__(self, func, s):\n        self.name = func.__name__\n        self.requires_network = 'n' in s\n        self.requires_wallet = 'w' in s\n        self.requires_password = 'p' in s\n        self.description = func.__doc__\n        self.help = self.description.split('.')[0] if self.description else None\n        varnames = func.__code__.co_varnames[1:func.__code__.co_argcount]\n        self.defaults = func.__defaults__\n        if self.defaults:\n            n = len(self.defaults)\n            self.params = list(varnames[:-n])\n            self.options = list(varnames[-n:])\n        else:\n            self.params = list(varnames)\n            self.options = []\n            self.defaults = []\n\n\ndef command(s):\n    def decorator(func):\n        global known_commands\n        name = func.__name__\n        known_commands[name] = Command(func, s)\n        @wraps(func)\n        def func_wrapper(*args, **kwargs):\n            c = known_commands[func.__name__]\n            wallet = args[0].wallet\n            password = kwargs.get('password')\n            if c.requires_wallet and wallet is None:\n                raise BaseException(\"wallet not loaded. Use 'electrum daemon load_wallet'\")\n            if c.requires_password and password is None and wallet.storage.get('use_encryption'):\n                return {'error': 'Password required' }\n            return func(*args, **kwargs)\n        return func_wrapper\n    return decorator\n\n\nclass Commands:\n\n    def __init__(self, config, wallet, network, callback = None):\n        self.config = config\n        self.wallet = wallet\n        self.network = network\n        self._callback = callback\n\n    def _run(self, method, args, password_getter):\n        # this wrapper is called from the python console\n        cmd = known_commands[method]\n        if cmd.requires_password and self.wallet.has_password():\n            password = password_getter()\n            if password is None:\n                return\n        else:\n            password = None\n\n        f = getattr(self, method)\n        if cmd.requires_password:\n            result = f(*args, **{'password':password})\n        else:\n            result = f(*args)\n\n        if self._callback:\n            self._callback()\n        return result\n\n    @command('')\n    def commands(self):\n        \"\"\"List of commands\"\"\"\n        return ' '.join(sorted(known_commands.keys()))\n\n    @command('')\n    def create(self, segwit=False):\n        \"\"\"Create a new wallet\"\"\"\n        raise BaseException('Not a JSON-RPC command')\n\n    @command('wn')\n    def restore(self, text):\n        \"\"\"Restore a wallet from text. Text can be a seed phrase, a master\n        public key, a master private key, a list of bitcoin addresses\n        or bitcoin private keys. If you want to be prompted for your\n        seed, type '?' or ':' (concealed) \"\"\"\n        raise BaseException('Not a JSON-RPC command')\n\n    @command('wp')\n    def password(self, password=None, new_password=None):\n        \"\"\"Change wallet password. \"\"\"\n        b = self.wallet.storage.is_encrypted()\n        self.wallet.update_password(password, new_password, b)\n        self.wallet.storage.write()\n        return {'password':self.wallet.has_password()}\n\n    @command('')\n    def getconfig(self, key):\n        \"\"\"Return a configuration variable. \"\"\"\n        return self.config.get(key)\n\n    @command('')\n    def setconfig(self, key, value):\n        \"\"\"Set a configuration variable. 'value' may be a string or a Python expression.\"\"\"\n        try:\n            value = ast.literal_eval(value)\n        except:\n            pass\n        self.config.set_key(key, value)\n        return True\n\n    @command('')\n    def make_seed(self, nbits=132, entropy=1, language=None, segwit=False):\n        \"\"\"Create a seed\"\"\"\n        from .mnemonic import Mnemonic\n        t = 'segwit' if segwit else 'standard'\n        s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy)\n        return s\n\n    @command('')\n    def check_seed(self, seed, entropy=1, language=None):\n        \"\"\"Check that a seed was generated with given entropy\"\"\"\n        from .mnemonic import Mnemonic\n        return Mnemonic(language).check_seed(seed, entropy)\n\n    @command('n')\n    def getaddresshistory(self, address):\n        \"\"\"Return the transaction history of any address. Note: This is a\n        walletless server query, results are not checked by SPV.\n        \"\"\"\n        return self.network.synchronous_get(('blockchain.address.get_history', [address]))\n\n    @command('w')\n    def listunspent(self):\n        \"\"\"List unspent outputs. Returns the list of unspent transaction\n        outputs in your wallet.\"\"\"\n        l = copy.deepcopy(self.wallet.get_utxos(exclude_frozen=False))\n        for i in l:\n            v = i[\"value\"]\n            i[\"value\"] = str(Decimal(v)/COIN) if v is not None else None\n        return l\n\n    @command('n')\n    def getaddressunspent(self, address):\n        \"\"\"Returns the UTXO list of any address. Note: This\n        is a walletless server query, results are not checked by SPV.\n        \"\"\"\n        return self.network.synchronous_get(('blockchain.address.listunspent', [address]))\n\n    @command('')\n    def serialize(self, jsontx):\n        \"\"\"Create a transaction from json inputs.\n        Inputs must have a redeemPubkey.\n        Outputs must be a list of {'address':address, 'value':satoshi_amount}.\n        \"\"\"\n        keypairs = {}\n        inputs = jsontx.get('inputs')\n        outputs = jsontx.get('outputs')\n        locktime = jsontx.get('locktime', 0)\n        for txin in inputs:\n            if txin.get('output'):\n                prevout_hash, prevout_n = txin['output'].split(':')\n                txin['prevout_n'] = int(prevout_n)\n                txin['prevout_hash'] = prevout_hash\n            sec = txin.get('privkey')\n            if sec:\n                txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)\n                pubkey = bitcoin.public_key_from_private_key(privkey, compressed)\n                keypairs[pubkey] = privkey, compressed\n                txin['type'] = txin_type\n                txin['x_pubkeys'] = [pubkey]\n                txin['signatures'] = [None]\n                txin['num_sig'] = 1\n\n        outputs = [(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]\n        tx = Transaction.from_io(inputs, outputs, locktime=locktime)\n        tx.sign(keypairs)\n        return tx.as_dict()\n\n    @command('wp')\n    def signtransaction(self, tx, privkey=None, password=None):\n        \"\"\"Sign a transaction. The wallet keys will be used unless a private key is provided.\"\"\"\n        tx = Transaction(tx)\n        if privkey:\n            txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)\n            pubkey = bitcoin.public_key_from_private_key(privkey2, compressed)\n            h160 = bitcoin.hash_160(bfh(pubkey))\n            x_pubkey = 'fd' + bh2u(b'\\x00' + h160)\n            tx.sign({x_pubkey:(privkey2, compressed)})\n        else:\n            self.wallet.sign_transaction(tx, password)\n        return tx.as_dict()\n\n    @command('')\n    def deserialize(self, tx):\n        \"\"\"Deserialize a serialized transaction\"\"\"\n        tx = Transaction(tx)\n        return tx.deserialize()\n\n    @command('n')\n    def broadcast(self, tx, timeout=30):\n        \"\"\"Broadcast a transaction to the network. \"\"\"\n        tx = Transaction(tx)\n        return self.network.broadcast(tx, timeout)\n\n    @command('')\n    def createmultisig(self, num, pubkeys):\n        \"\"\"Create multisig address\"\"\"\n        assert isinstance(pubkeys, list), (type(num), type(pubkeys))\n        redeem_script = multisig_script(pubkeys, num)\n        address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))\n        return {'address':address, 'redeemScript':redeem_script}\n\n    @command('w')\n    def freeze(self, address):\n        \"\"\"Freeze address. Freeze the funds at one of your wallet\\'s addresses\"\"\"\n        return self.wallet.set_frozen_state([address], True)\n\n    @command('w')\n    def unfreeze(self, address):\n        \"\"\"Unfreeze address. Unfreeze the funds at one of your wallet\\'s address\"\"\"\n        return self.wallet.set_frozen_state([address], False)\n\n    @command('wp')\n    def getprivatekeys(self, address, password=None):\n        \"\"\"Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses.\"\"\"\n        if isinstance(address, str):\n            address = address.strip()\n        if is_address(address):\n            return self.wallet.export_private_key(address, password)[0]\n        domain = address\n        return [self.wallet.export_private_key(address, password)[0] for address in domain]\n\n    @command('w')\n    def ismine(self, address):\n        \"\"\"Check if address is in wallet. Return true if and only address is in wallet\"\"\"\n        return self.wallet.is_mine(address)\n\n    @command('')\n    def dumpprivkeys(self):\n        \"\"\"Deprecated.\"\"\"\n        return \"This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '\"\n\n    @command('')\n    def validateaddress(self, address):\n        \"\"\"Check that an address is valid. \"\"\"\n        return is_address(address)\n\n    @command('w')\n    def getpubkeys(self, address):\n        \"\"\"Return the public keys for a wallet address. \"\"\"\n        return self.wallet.get_public_keys(address)\n\n    @command('w')\n    def getbalance(self):\n        \"\"\"Return the balance of your wallet. \"\"\"\n        c, u, x = self.wallet.get_balance()\n        out = {\"confirmed\": str(Decimal(c)/COIN)}\n        if u:\n            out[\"unconfirmed\"] = str(Decimal(u)/COIN)\n        if x:\n            out[\"unmatured\"] = str(Decimal(x)/COIN)\n        return out\n\n    @command('n')\n    def getaddressbalance(self, address):\n        \"\"\"Return the balance of any address. Note: This is a walletless\n        server query, results are not checked by SPV.\n        \"\"\"\n        out = self.network.synchronous_get(('blockchain.address.get_balance', [address]))\n        out[\"confirmed\"] =  str(Decimal(out[\"confirmed\"])/COIN)\n        out[\"unconfirmed\"] =  str(Decimal(out[\"unconfirmed\"])/COIN)\n        return out\n\n    @command('n')\n    def getproof(self, address):\n        \"\"\"Get Merkle branch of an address in the UTXO set\"\"\"\n        p = self.network.synchronous_get(('blockchain.address.get_proof', [address]))\n        out = []\n        for i,s in p:\n            out.append(i)\n        return out\n\n    @command('n')\n    def getmerkle(self, txid, height):\n        \"\"\"Get Merkle branch of a transaction included in a block. Electrum\n        uses this to verify transactions (Simple Payment Verification).\"\"\"\n        return self.network.synchronous_get(('blockchain.transaction.get_merkle', [txid, int(height)]))\n\n    @command('n')\n    def getservers(self):\n        \"\"\"Return the list of available servers\"\"\"\n        return self.network.get_servers()\n\n    @command('')\n    def version(self):\n        \"\"\"Return the version of electrum.\"\"\"\n        from .version import ELECTRUM_VERSION\n        return ELECTRUM_VERSION\n\n    @command('w')\n    def getmpk(self):\n        \"\"\"Get master public key. Return your wallet\\'s master public key\"\"\"\n        return self.wallet.get_master_public_key()\n\n    @command('wp')\n    def getmasterprivate(self, password=None):\n        \"\"\"Get master private key. Return your wallet\\'s master private key\"\"\"\n        return str(self.wallet.keystore.get_master_private_key(password))\n\n    @command('wp')\n    def getseed(self, password=None):\n        \"\"\"Get seed phrase. Print the generation seed of your wallet.\"\"\"\n        s = self.wallet.get_seed(password)\n        return s\n\n    @command('wp')\n    def importprivkey(self, privkey, password=None):\n        \"\"\"Import a private key.\"\"\"\n        if not self.wallet.can_import_privkey():\n            return \"Error: This type of wallet cannot import private keys. Try to create a new wallet with that key.\"\n        try:\n            addr = self.wallet.import_private_key(privkey, password)\n            out = \"Keypair imported: \" + addr\n        except BaseException as e:\n            out = \"Error: \" + str(e)\n        return out\n\n    def _resolver(self, x):\n        if x is None:\n            return None\n        out = self.wallet.contacts.resolve(x)\n        if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:\n            raise BaseException('cannot verify alias', x)\n        return out['address']\n\n    @command('n')\n    def sweep(self, privkey, destination, fee=None, nocheck=False, imax=100):\n        \"\"\"Sweep private keys. Returns a transaction that spends UTXOs from\n        privkey to a destination address. The transaction is not\n        broadcasted.\"\"\"\n        from .wallet import sweep\n        tx_fee = satoshis(fee)\n        privkeys = privkey.split()\n        self.nocheck = nocheck\n        #dest = self._resolver(destination)\n        tx = sweep(privkeys, self.network, self.config, destination, tx_fee, imax)\n        return tx.as_dict() if tx else None\n\n    @command('wp')\n    def signmessage(self, address, message, password=None):\n        \"\"\"Sign a message with a key. Use quotes if your message contains\n        whitespaces\"\"\"\n        sig = self.wallet.sign_message(address, message, password)\n        return base64.b64encode(sig).decode('ascii')\n\n    @command('')\n    def verifymessage(self, address, signature, message):\n        \"\"\"Verify a signature.\"\"\"\n        sig = base64.b64decode(signature)\n        message = util.to_bytes(message)\n        return bitcoin.verify_message(address, sig, message)\n\n    def _mktx(self, outputs, fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime=None):\n        self.nocheck = nocheck\n        change_addr = self._resolver(change_addr)\n        domain = None if domain is None else map(self._resolver, domain)\n        final_outputs = []\n        for address, amount in outputs:\n            address = self._resolver(address)\n            amount = satoshis(amount)\n            final_outputs.append((TYPE_ADDRESS, address, amount))\n\n        coins = self.wallet.get_spendable_coins(domain, self.config)\n        tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)\n        if locktime != None: \n            tx.locktime = locktime\n        if rbf:\n            tx.set_rbf(True)\n        if not unsigned:\n            run_hook('sign_tx', self.wallet, tx)\n            self.wallet.sign_transaction(tx, password)\n        return tx\n\n    @command('wp')\n    def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None):\n        \"\"\"Create a transaction. \"\"\"\n        tx_fee = satoshis(fee)\n        domain = from_addr.split(',') if from_addr else None\n        tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)\n        return tx.as_dict()\n\n    @command('wp')\n    def paytomany(self, outputs, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, rbf=False, password=None, locktime=None):\n        \"\"\"Create a multi-output transaction. \"\"\"\n        tx_fee = satoshis(fee)\n        domain = from_addr.split(',') if from_addr else None\n        tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned, rbf, password, locktime)\n        return tx.as_dict()\n\n    @command('w')\n    def history(self):\n        \"\"\"Wallet history. Returns the transaction history of your wallet.\"\"\"\n        balance = 0\n        out = []\n        for item in self.wallet.get_history():\n            tx_hash, height, conf, timestamp, value, balance = item\n            if timestamp:\n                date = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]\n            else:\n                date = \"----\"\n            label = self.wallet.get_label(tx_hash)\n            tx = self.wallet.transactions.get(tx_hash)\n            tx.deserialize()\n            input_addresses = []\n            output_addresses = []\n            for x in tx.inputs():\n                if x['type'] == 'coinbase': continue\n                addr = x.get('address')\n                if addr == None: continue\n                if addr == \"(pubkey)\":\n                    prevout_hash = x.get('prevout_hash')\n                    prevout_n = x.get('prevout_n')\n                    _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n)\n                    if _addr:\n                        addr = _addr\n                input_addresses.append(addr)\n            for addr, v in tx.get_outputs():\n                output_addresses.append(addr)\n            out.append({\n                'txid': tx_hash,\n                'timestamp': timestamp,\n                'date': date,\n                'input_addresses': input_addresses,\n                'output_addresses': output_addresses,\n                'label': label,\n                'value': str(Decimal(value)/COIN) if value is not None else None,\n                'height': height,\n                'confirmations': conf\n            })\n        return out\n\n    @command('w')\n    def setlabel(self, key, label):\n        \"\"\"Assign a label to an item. Item may be a bitcoin address or a\n        transaction ID\"\"\"\n        self.wallet.set_label(key, label)\n\n    @command('w')\n    def listcontacts(self):\n        \"\"\"Show your list of contacts\"\"\"\n        return self.wallet.contacts\n\n    @command('w')\n    def getalias(self, key):\n        \"\"\"Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record.\"\"\"\n        return self.wallet.contacts.resolve(key)\n\n    @command('w')\n    def searchcontacts(self, query):\n        \"\"\"Search through contacts, return matching entries. \"\"\"\n        results = {}\n        for key, value in self.wallet.contacts.items():\n            if query.lower() in key.lower():\n                results[key] = value\n        return results\n\n    @command('w')\n    def listaddresses(self, receiving=False, change=False, labels=False, frozen=False, unused=False, funded=False, balance=False):\n        \"\"\"List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results.\"\"\"\n        out = []\n        for addr in self.wallet.get_addresses():\n            if frozen and not self.wallet.is_frozen(addr):\n                continue\n            if receiving and self.wallet.is_change(addr):\n                continue\n            if change and not self.wallet.is_change(addr):\n                continue\n            if unused and self.wallet.is_used(addr):\n                continue\n            if funded and self.wallet.is_empty(addr):\n                continue\n            item = addr\n            if labels or balance:\n                item = (item,)\n            if balance:\n                item += (format_satoshis(sum(self.wallet.get_addr_balance(addr))),)\n            if labels:\n                item += (repr(self.wallet.labels.get(addr, '')),)\n            out.append(item)\n        return out\n\n    @command('n')\n    def gettransaction(self, txid):\n        \"\"\"Retrieve a transaction. \"\"\"\n        if self.wallet and txid in self.wallet.transactions:\n            tx = self.wallet.transactions[txid]\n        else:\n            raw = self.network.synchronous_get(('blockchain.transaction.get', [txid]))\n            if raw:\n                tx = Transaction(raw)\n            else:\n                raise BaseException(\"Unknown transaction\")\n        return tx.as_dict()\n\n    @command('')\n    def encrypt(self, pubkey, message):\n        \"\"\"Encrypt a message with a public key. Use quotes if the message contains whitespaces.\"\"\"\n        return bitcoin.encrypt_message(message, pubkey)\n\n    @command('wp')\n    def decrypt(self, pubkey, encrypted, password=None):\n        \"\"\"Decrypt a message encrypted with a public key.\"\"\"\n        return self.wallet.decrypt_message(pubkey, encrypted, password)\n\n    def _format_request(self, out):\n        pr_str = {\n            PR_UNKNOWN: 'Unknown',\n            PR_UNPAID: 'Pending',\n            PR_PAID: 'Paid',\n            PR_EXPIRED: 'Expired',\n        }\n        out['amount (BTCP)'] = format_satoshis(out.get('amount'))\n        out['status'] = pr_str[out.get('status', PR_UNKNOWN)]\n        return out\n\n    @command('w')\n    def getrequest(self, key):\n        \"\"\"Return a payment request\"\"\"\n        r = self.wallet.get_payment_request(key, self.config)\n        if not r:\n            raise BaseException(\"Request not found\")\n        return self._format_request(r)\n\n    #@command('w')\n    #def ackrequest(self, serialized):\n    #    \"\"\"<Not implemented>\"\"\"\n    #    pass\n\n    @command('w')\n    def listrequests(self, pending=False, expired=False, paid=False):\n        \"\"\"List the payment requests you made.\"\"\"\n        out = self.wallet.get_sorted_requests(self.config)\n        if pending:\n            f = PR_UNPAID\n        elif expired:\n            f = PR_EXPIRED\n        elif paid:\n            f = PR_PAID\n        else:\n            f = None\n        if f is not None:\n            out = list(filter(lambda x: x.get('status')==f, out))\n        return list(map(self._format_request, out))\n\n    @command('w')\n    def createnewaddress(self):\n        \"\"\"Create a new receiving address, beyond the gap limit of the wallet\"\"\"\n        return self.wallet.create_new_address(False)\n\n    @command('w')\n    def getunusedaddress(self):\n        \"\"\"Returns the first unused address of the wallet, or None if all addresses are used.\n        An address is considered as used if it has received a transaction, or if it is used in a payment request.\"\"\"\n        return self.wallet.get_unused_address()\n\n    @command('w')\n    def addrequest(self, amount, memo='', expiration=None, force=False):\n        \"\"\"Create a payment request, using the first unused address of the wallet.\n        The address will be condidered as used after this operation.\n        If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet.\"\"\"\n        addr = self.wallet.get_unused_address()\n        if addr is None:\n            if force:\n                addr = self.wallet.create_new_address(False)\n            else:\n                return False\n        amount = satoshis(amount)\n        expiration = int(expiration) if expiration else None\n        req = self.wallet.make_payment_request(addr, amount, memo, expiration)\n        self.wallet.add_payment_request(req, self.config)\n        out = self.wallet.get_payment_request(addr, self.config)\n        return self._format_request(out)\n\n    @command('wp')\n    def signrequest(self, address, password=None):\n        \"Sign payment request with an OpenAlias\"\n        alias = self.config.get('alias')\n        if not alias:\n            raise BaseException('No alias in your configuration')\n        alias_addr = self.wallet.contacts.resolve(alias)['address']\n        self.wallet.sign_payment_request(address, alias, alias_addr, password)\n\n    @command('w')\n    def rmrequest(self, address):\n        \"\"\"Remove a payment request\"\"\"\n        return self.wallet.remove_payment_request(address, self.config)\n\n    @command('w')\n    def clearrequests(self):\n        \"\"\"Remove all payment requests\"\"\"\n        for k in list(self.wallet.receive_requests.keys()):\n            self.wallet.remove_payment_request(k, self.config)\n\n    @command('n')\n    def notify(self, address, URL):\n        \"\"\"Watch an address. Everytime the address changes, a http POST is sent to the URL.\"\"\"\n        def callback(x):\n            import urllib.request\n            headers = {'content-type':'application/json'}\n            data = {'address':address, 'status':x.get('result')}\n            try:\n                req = urllib.request.Request(URL, json.dumps(data), headers)\n                response_stream = urllib.request.urlopen(req, timeout=5)\n                util.print_error('Got Response for %s' % address)\n            except BaseException as e:\n                util.print_error(str(e))\n        self.network.send([('blockchain.address.subscribe', [address])], callback)\n        return True\n\n    @command('wn')\n    def is_synchronized(self):\n        \"\"\" return wallet synchronization status \"\"\"\n        return self.wallet.is_up_to_date()\n\n    @command('')\n    def help(self):\n        # for the python console\n        return sorted(known_commands.keys())\n\nparam_descriptions = {\n    'privkey': 'Private key. Type \\'?\\' to get a prompt.',\n    'destination': 'BTCP address, contact or alias',\n    'address': 'BTCP address',\n    'seed': 'Seed phrase',\n    'txid': 'Transaction ID',\n    'pos': 'Position',\n    'height': 'Block height',\n    'tx': 'Serialized transaction (hexadecimal)',\n    'key': 'Variable name',\n    'pubkey': 'Public key',\n    'message': 'Clear text message. Use quotes if it contains spaces.',\n    'encrypted': 'Encrypted message',\n    'amount': 'Amount to be sent (in BTCP). Type \\'!\\' to send the maximum available.',\n    'requested_amount': 'Requested amount (in BTCP).',\n    'outputs': 'list of [\"address\", amount]',\n    'redeem_script': 'redeem script (hexadecimal)',\n}\n\ncommand_options = {\n    'password':    (\"-W\", \"Password\"),\n    'new_password':(None, \"New Password\"),\n    'receiving':   (None, \"Show only receiving addresses\"),\n    'change':      (None, \"Show only change addresses\"),\n    'frozen':      (None, \"Show only frozen addresses\"),\n    'unused':      (None, \"Show only unused addresses\"),\n    'funded':      (None, \"Show only funded addresses\"),\n    'balance':     (\"-b\", \"Show the balances of listed addresses\"),\n    'labels':      (\"-l\", \"Show the labels of listed addresses\"),\n    'nocheck':     (None, \"Do not verify aliases\"),\n    'imax':        (None, \"Maximum number of inputs\"),\n    'fee':         (\"-f\", \"Transaction fee (in BTCP)\"),\n    'from_addr':   (\"-F\", \"Source address (must be a wallet address; use sweep to spend from non-wallet address).\"),\n    'change_addr': (\"-c\", \"Change address. Default is a spare address, or the source address if it's not in the wallet\"),\n    'nbits':       (None, \"Number of bits of entropy\"),\n    'entropy':     (None, \"Custom entropy\"),\n    'segwit':      (None, \"Create segwit seed\"),\n    'language':    (\"-L\", \"Default language for wordlist\"),\n    'privkey':     (None, \"Private key. Set to '?' to get a prompt.\"),\n    'unsigned':    (\"-u\", \"Do not sign transaction\"),\n    'rbf':         (None, \"Replace-by-fee transaction\"),\n    'locktime':    (None, \"Set locktime block number\"),\n    'domain':      (\"-D\", \"List of addresses\"),\n    'memo':        (\"-m\", \"Description of the request\"),\n    'expiration':  (None, \"Time in seconds\"),\n    'timeout':     (None, \"Timeout in seconds\"),\n    'force':       (None, \"Create new address beyond gap limit, if no more addresses are available.\"),\n    'pending':     (None, \"Show only pending requests.\"),\n    'expired':     (None, \"Show only expired requests.\"),\n    'paid':        (None, \"Show only paid requests.\"),\n}\n\n\n# don't use floats because of rounding errors\nfrom .transaction import tx_from_str\njson_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))\narg_types = {\n    'num': int,\n    'nbits': int,\n    'imax': int,\n    'entropy': int,\n    'tx': tx_from_str,\n    'pubkeys': json_loads,\n    'jsontx': json_loads,\n    'inputs': json_loads,\n    'outputs': json_loads,\n    'fee': lambda x: str(Decimal(x)) if x is not None else None,\n    'amount': lambda x: str(Decimal(x)) if x != '!' else '!',\n    'locktime': int,\n}\n\nconfig_variables = {\n\n    'addrequest': {\n        'requests_dir': 'directory where a bip70 file will be written.',\n        'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',\n        'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',\n        'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \\\"(\\'file:///var/www/\\',\\'https://electrum.org/\\')\\\"',\n    },\n    'listrequests':{\n        'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \\\"(\\'file:///var/www/\\',\\'https://electrum.org/\\')\\\"',\n    }\n}\n\ndef set_default_subparser(self, name, args=None):\n    \"\"\"see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand\"\"\"\n    subparser_found = False\n    for arg in sys.argv[1:]:\n        if arg in ['-h', '--help']:  # global help if no subparser\n            break\n    else:\n        for x in self._subparsers._actions:\n            if not isinstance(x, argparse._SubParsersAction):\n                continue\n            for sp_name in x._name_parser_map.keys():\n                if sp_name in sys.argv[1:]:\n                    subparser_found = True\n        if not subparser_found:\n            # insert default in first position, this implies no\n            # global options without a sub_parsers specified\n            if args is None:\n                sys.argv.insert(1, name)\n            else:\n                args.insert(0, name)\n\nargparse.ArgumentParser.set_default_subparser = set_default_subparser\n\n\n# workaround https://bugs.python.org/issue23058\n# see https://github.com/nickstenning/honcho/pull/121\n\ndef subparser_call(self, parser, namespace, values, option_string=None):\n    from argparse import ArgumentError, SUPPRESS, _UNRECOGNIZED_ARGS_ATTR\n    parser_name = values[0]\n    arg_strings = values[1:]\n    # set the parser name if requested\n    if self.dest is not SUPPRESS:\n        setattr(namespace, self.dest, parser_name)\n    # select the parser\n    try:\n        parser = self._name_parser_map[parser_name]\n    except KeyError:\n        tup = parser_name, ', '.join(self._name_parser_map)\n        msg = _('unknown parser %r (choices: %s)') % tup\n        raise ArgumentError(self, msg)\n    # parse all the remaining options into the namespace\n    # store any unrecognized options on the object, so that the top\n    # level parser can decide what to do with them\n    namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)\n    if arg_strings:\n        vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])\n        getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)\n\nargparse._SubParsersAction.__call__ = subparser_call\n\n\ndef add_network_options(parser):\n    parser.add_argument(\"-1\", \"--oneserver\", action=\"store_true\", dest=\"oneserver\", default=False, help=\"connect to one server only\")\n    parser.add_argument(\"-s\", \"--server\", dest=\"server\", default=None, help=\"set server host:port:protocol, where protocol is either t (tcp) or s (ssl)\")\n    parser.add_argument(\"-p\", \"--proxy\", dest=\"proxy\", default=None, help=\"set proxy [type:]host[:port], where type is socks4,socks5 or http\")\n\ndef add_global_options(parser):\n    group = parser.add_argument_group('global options')\n    group.add_argument(\"-v\", \"--verbose\", action=\"store_true\", dest=\"verbose\", default=False, help=\"Show debugging information\")\n    group.add_argument(\"-D\", \"--dir\", dest=\"electrum_path\", help=\"electrum directory\")\n    group.add_argument(\"-P\", \"--portable\", action=\"store_true\", dest=\"portable\", default=False, help=\"Use local 'electrum_data' directory\")\n    group.add_argument(\"-w\", \"--wallet\", dest=\"wallet_path\", help=\"wallet path\")\n    group.add_argument(\"--testnet\", action=\"store_true\", dest=\"testnet\", default=False, help=\"Use Testnet\")\n    group.add_argument(\"--nossl\", action=\"store_true\", dest=\"nossl\", default=False, help=\"Disable SSL\")\n\ndef get_parser():\n    # create main parser\n    parser = argparse.ArgumentParser(\n        epilog=\"Run 'electrum help <command>' to see the help for a command\")\n    add_global_options(parser)\n    subparsers = parser.add_subparsers(dest='cmd', metavar='<command>')\n    # gui\n    parser_gui = subparsers.add_parser('gui', description=\"Run Electrum's Graphical User Interface.\", help=\"Run GUI (default)\")\n    parser_gui.add_argument(\"url\", nargs='?', default=None, help=\"bitcoin URI (or bip70 file)\")\n    parser_gui.add_argument(\"-g\", \"--gui\", dest=\"gui\", help=\"select graphical user interface\", choices=['qt', 'kivy', 'text', 'stdio'])\n    parser_gui.add_argument(\"-o\", \"--offline\", action=\"store_true\", dest=\"offline\", default=False, help=\"Run offline\")\n    parser_gui.add_argument(\"-m\", action=\"store_true\", dest=\"hide_gui\", default=False, help=\"hide GUI on startup\")\n    parser_gui.add_argument(\"-L\", \"--lang\", dest=\"language\", default=None, help=\"default language used in GUI\")\n    add_network_options(parser_gui)\n    add_global_options(parser_gui)\n    # daemon\n    parser_daemon = subparsers.add_parser('daemon', help=\"Run Daemon\")\n    parser_daemon.add_argument(\"subcommand\", choices=['start', 'status', 'stop', 'load_wallet', 'close_wallet'], nargs='?')\n    #parser_daemon.set_defaults(func=run_daemon)\n    add_network_options(parser_daemon)\n    add_global_options(parser_daemon)\n    # commands\n    for cmdname in sorted(known_commands.keys()):\n        cmd = known_commands[cmdname]\n        p = subparsers.add_parser(cmdname, help=cmd.help, description=cmd.description)\n        add_global_options(p)\n        if cmdname == 'restore':\n            p.add_argument(\"-o\", \"--offline\", action=\"store_true\", dest=\"offline\", default=False, help=\"Run offline\")\n        for optname, default in zip(cmd.options, cmd.defaults):\n            a, help = command_options[optname]\n            b = '--' + optname\n            action = \"store_true\" if type(default) is bool else 'store'\n            args = (a, b) if a else (b,)\n            if action == 'store':\n                _type = arg_types.get(optname, str)\n                p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type)\n            else:\n                p.add_argument(*args, dest=optname, action=action, default=default, help=help)\n\n        for param in cmd.params:\n            h = param_descriptions.get(param, '')\n            _type = arg_types.get(param, str)\n            p.add_argument(param, help=h, type=_type)\n\n        cvh = config_variables.get(cmdname)\n        if cvh:\n            group = p.add_argument_group('configuration variables', '(set with setconfig/getconfig)')\n            for k, v in cvh.items():\n                group.add_argument(k, nargs='?', help=v)\n\n    # 'gui' is the default command\n    parser.set_default_subparser('gui')\n    return parser\n"
  },
  {
    "path": "lib/contacts.py",
    "content": "# Electrum - Lightweight Bitcoin Client\n# Copyright (c) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport re\nimport dns\nimport json\n\nfrom . import bitcoin\nfrom . import dnssec\n\n\nclass Contacts(dict):\n\n    def __init__(self, storage):\n        self.storage = storage\n        d = self.storage.get('contacts', {})\n        try:\n            self.update(d)\n        except:\n            return\n        # backward compatibility\n        for k, v in self.items():\n            _type, n = v\n            if _type == 'address' and bitcoin.is_address(n):\n                self.pop(k)\n                self[n] = ('address', k)\n\n    def save(self):\n        self.storage.put('contacts', dict(self))\n\n    def import_file(self, path):\n        try:\n            with open(path, 'r') as f:\n                d = self._validate(json.loads(f.read()))\n        except:\n            return\n        self.update(d)\n        self.save()\n\n    def __setitem__(self, key, value):\n        dict.__setitem__(self, key, value)\n        self.save()\n\n    def pop(self, key):\n        if key in self.keys():\n            dict.pop(self, key)\n            self.save()\n\n    def resolve(self, k):\n        if bitcoin.is_address(k):\n            return {\n                'address': k,\n                'type': 'address'\n            }\n        if k in self.keys():\n            _type, addr = self[k]\n            if _type == 'address':\n                return {\n                    'address': addr,\n                    'type': 'contact'\n                }\n        out = self.resolve_openalias(k)\n        if out:\n            address, name, validated = out\n            return {\n                'address': address,\n                'name': name,\n                'type': 'openalias',\n                'validated': validated\n            }\n        raise Exception(\"Invalid BTCP address or alias\", k)\n\n    def resolve_openalias(self, url):\n        # support email-style addresses, per the OA standard\n        url = url.replace('@', '.')\n        records, validated = dnssec.query(url, dns.rdatatype.TXT)\n        prefix = 'btc'\n        for record in records:\n            string = record.strings[0]\n            if string.startswith('oa1:' + prefix):\n                address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')\n                name = self.find_regex(string, r'recipient_name=([^;]+)')\n                if not name:\n                    name = address\n                if not address:\n                    continue\n                return address, name, validated\n\n    def find_regex(self, haystack, needle):\n        regex = re.compile(needle)\n        try:\n            return regex.search(haystack).groups()[0]\n        except AttributeError:\n            return None\n            \n    def _validate(self, data):\n        for k,v in list(data.items()):\n            if k == 'contacts':\n                return self._validate(v)\n            if not bitcoin.is_address(k):\n                data.pop(k)\n            else:\n                _type,_ = v\n                if _type != 'address':\n                    data.pop(k)\n        return data\n\n"
  },
  {
    "path": "lib/currencies.json",
    "content": "{\n    \"CoinMarketCap\": [\n        \"USD\",\n        \"EUR\",\n        \"CNY\"\n    ]\n}\n"
  },
  {
    "path": "lib/daemon.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport ast\nimport os\nimport time\n\n# from jsonrpc import JSONRPCResponseManager\nimport jsonrpclib\nfrom .jsonrpc import VerifyingJSONRPCServer\n\nfrom .version import ELECTRUM_VERSION\nfrom .network import Network\nfrom .util import json_decode, DaemonThread\nfrom .util import print_error, to_string\nfrom .wallet import Wallet\nfrom .storage import WalletStorage\nfrom .commands import known_commands, Commands\nfrom .simple_config import SimpleConfig\nfrom .exchange_rate import FxThread\n\n\ndef get_lockfile(config):\n    return os.path.join(config.path, 'daemon')\n\n\ndef remove_lockfile(lockfile):\n    os.unlink(lockfile)\n\n\ndef get_fd_or_server(config):\n    '''Tries to create the lockfile, using O_EXCL to\n    prevent races.  If it succeeds it returns the FD.\n    Otherwise try and connect to the server specified in the lockfile.\n    If this succeeds, the server is returned.  Otherwise remove the\n    lockfile and try again.'''\n    lockfile = get_lockfile(config)\n    while True:\n        try:\n            return os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644), None\n        except OSError:\n            pass\n        server = get_server(config)\n        if server is not None:\n            return None, server\n        # Couldn't connect; remove lockfile and try again.\n        remove_lockfile(lockfile)\n\n\ndef get_server(config):\n    lockfile = get_lockfile(config)\n    while True:\n        create_time = None\n        try:\n            with open(lockfile) as f:\n                (host, port), create_time = ast.literal_eval(f.read())\n                rpc_user, rpc_password = get_rpc_credentials(config)\n                if rpc_password == '':\n                    # authentication disabled\n                    server_url = 'http://%s:%d' % (host, port)\n                else:\n                    server_url = 'http://%s:%s@%s:%d' % (\n                        rpc_user, rpc_password, host, port)\n                server = jsonrpclib.Server(server_url)\n            # Test daemon is running\n            server.ping()\n            return server\n        except Exception as e:\n            print_error(\"[get_server]\", e)\n        if not create_time or create_time < time.time() - 1.0:\n            return None\n        # Sleep a bit and try again; it might have just been started\n        time.sleep(1.0)\n\n\ndef get_rpc_credentials(config):\n    rpc_user = config.get('rpcuser', None)\n    rpc_password = config.get('rpcpassword', None)\n    if rpc_user is None or rpc_password is None:\n        rpc_user = 'user'\n        import ecdsa, base64\n        bits = 128\n        nbytes = bits // 8 + (bits % 8 > 0)\n        pw_int = ecdsa.util.randrange(pow(2, bits))\n        pw_b64 = base64.b64encode(\n            pw_int.to_bytes(nbytes, 'big'), b'-_')\n        rpc_password = to_string(pw_b64, 'ascii')\n        config.set_key('rpcuser', rpc_user)\n        config.set_key('rpcpassword', rpc_password, save=True)\n    elif rpc_password == '':\n        from .util import print_stderr\n        print_stderr('WARNING: RPC authentication is disabled.')\n    return rpc_user, rpc_password\n\n\nclass Daemon(DaemonThread):\n\n    def __init__(self, config, fd, is_gui):\n        DaemonThread.__init__(self)\n        self.config = config\n        if config.get('offline'):\n            self.network = None\n            self.fx = None\n        else:\n            self.network = Network(config)\n            self.network.start()\n            self.fx = FxThread(config, self.network)\n            self.network.add_jobs([self.fx])\n\n        self.gui = None\n        self.wallets = {}\n        # Setup JSONRPC server\n        self.init_server(config, fd, is_gui)\n\n    def init_server(self, config, fd, is_gui):\n        host = config.get('rpchost', '127.0.0.1')\n        port = config.get('rpcport', 0)\n\n        rpc_user, rpc_password = get_rpc_credentials(config)\n        try:\n            server = VerifyingJSONRPCServer((host, port), logRequests=False,\n                                            rpc_user=rpc_user, rpc_password=rpc_password)\n        except Exception as e:\n            self.print_error('Warning: cannot initialize RPC server on host', host, e)\n            self.server = None\n            os.close(fd)\n            return\n        os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8'))\n        os.close(fd)\n        self.server = server\n        server.timeout = 0.1\n        server.register_function(self.ping, 'ping')\n        if is_gui:\n            server.register_function(self.run_gui, 'gui')\n        else:\n            server.register_function(self.run_daemon, 'daemon')\n            self.cmd_runner = Commands(self.config, None, self.network)\n            for cmdname in known_commands:\n                server.register_function(getattr(self.cmd_runner, cmdname), cmdname)\n            server.register_function(self.run_cmdline, 'run_cmdline')\n\n    def ping(self):\n        return True\n\n    def run_daemon(self, config_options):\n        config = SimpleConfig(config_options)\n        sub = config.get('subcommand')\n        assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']\n        if sub in [None, 'start']:\n            response = \"Daemon already running\"\n        elif sub == 'load_wallet':\n            path = config.get_wallet_path()\n            wallet = self.load_wallet(path, config.get('password'))\n            self.cmd_runner.wallet = wallet\n            response = True\n        elif sub == 'close_wallet':\n            path = config.get_wallet_path()\n            if path in self.wallets:\n                self.stop_wallet(path)\n                response = True\n            else:\n                response = False\n        elif sub == 'status':\n            if self.network:\n                p = self.network.get_parameters()\n                response = {\n                    'path': self.network.config.path,\n                    'server': p[0],\n                    'blockchain_height': self.network.get_local_height(),\n                    'server_height': self.network.get_server_height(),\n                    'spv_nodes': len(self.network.get_interfaces()),\n                    'connected': self.network.is_connected(),\n                    'auto_connect': p[4],\n                    'version': ELECTRUM_VERSION,\n                    'wallets': {k: w.is_up_to_date()\n                                for k, w in self.wallets.items()},\n                    'fee_per_kb': self.config.fee_per_kb(),\n                }\n            else:\n                response = \"Daemon offline\"\n        elif sub == 'stop':\n            self.stop()\n            response = \"Daemon stopped\"\n        return response\n\n    def run_gui(self, config_options):\n        config = SimpleConfig(config_options)\n        if self.gui:\n            #if hasattr(self.gui, 'new_window'):\n            #    path = config.get_wallet_path()\n            #    self.gui.new_window(path, config.get('url'))\n            #    response = \"ok\"\n            #else:\n            #    response = \"error: current GUI does not support multiple windows\"\n            response = \"error: Electrum GUI already running\"\n        else:\n            response = \"Error: Electrum is running in daemon mode. Please stop the daemon first.\"\n        return response\n\n    def load_wallet(self, path, password):\n        # wizard will be launched if we return\n        if path in self.wallets:\n            wallet = self.wallets[path]\n            return wallet\n        storage = WalletStorage(path, manual_upgrades=True)\n        if not storage.file_exists():\n            return\n        if storage.is_encrypted():\n            if not password:\n                return\n            storage.decrypt(password)\n        if storage.requires_split():\n            return\n        if storage.requires_upgrade():\n            return\n        if storage.get_action():\n            return\n        wallet = Wallet(storage)\n        wallet.start_threads(self.network)\n        self.wallets[path] = wallet\n        return wallet\n\n    def add_wallet(self, wallet):\n        path = wallet.storage.path\n        self.wallets[path] = wallet\n\n    def get_wallet(self, path):\n        return self.wallets.get(path)\n\n    def stop_wallet(self, path):\n        wallet = self.wallets.pop(path)\n        wallet.stop_threads()\n\n    def run_cmdline(self, config_options):\n        password = config_options.get('password')\n        new_password = config_options.get('new_password')\n        config = SimpleConfig(config_options)\n        config.fee_estimates = self.network.config.fee_estimates.copy()\n        cmdname = config.get('cmd')\n        cmd = known_commands[cmdname]\n        if cmd.requires_wallet:\n            path = config.get_wallet_path()\n            wallet = self.wallets.get(path)\n            if wallet is None:\n                return {'error': 'Wallet \"%s\" is not loaded. Use \"electrum daemon load_wallet\"'%os.path.basename(path) }\n        else:\n            wallet = None\n        # arguments passed to function\n        args = map(lambda x: config.get(x), cmd.params)\n        # decode json arguments\n        args = [json_decode(i) for i in args]\n        # options\n        kwargs = {}\n        for x in cmd.options:\n            kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x))\n        cmd_runner = Commands(config, wallet, self.network)\n        func = getattr(cmd_runner, cmd.name)\n        result = func(*args, **kwargs)\n        return result\n\n    def run(self):\n        while self.is_running():\n            self.server.handle_request() if self.server else time.sleep(0.1)\n        for k, wallet in self.wallets.items():\n            wallet.stop_threads()\n        if self.network:\n            self.print_error(\"shutting down network\")\n            self.network.stop()\n            self.network.join()\n        self.on_stop()\n\n    def stop(self):\n        self.print_error(\"stopping, removing lockfile\")\n        remove_lockfile(get_lockfile(self.config))\n        DaemonThread.stop(self)\n\n    def init_gui(self, config, plugins):\n        gui_name = config.get('gui', 'qt')\n        if gui_name in ['lite', 'classic']:\n            gui_name = 'qt'\n        gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])\n        self.gui = gui.ElectrumGui(config, self, plugins)\n        self.gui.main()\n"
  },
  {
    "path": "lib/dnssec.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n# Check DNSSEC trust chain.\n# Todo: verify expiration dates\n#\n# Based on\n#  http://backreference.org/2010/11/17/dnssec-verification-with-dig/\n#  https://github.com/rthalley/dnspython/blob/master/tests/test_dnssec.py\n\n\n# import traceback\n# import sys\nimport time\nimport struct\n\n\nimport dns.name\nimport dns.query\nimport dns.dnssec\nimport dns.message\nimport dns.resolver\nimport dns.rdatatype\nimport dns.rdtypes.ANY.NS\nimport dns.rdtypes.ANY.CNAME\nimport dns.rdtypes.ANY.DLV\nimport dns.rdtypes.ANY.DNSKEY\nimport dns.rdtypes.ANY.DS\nimport dns.rdtypes.ANY.NSEC\nimport dns.rdtypes.ANY.NSEC3\nimport dns.rdtypes.ANY.NSEC3PARAM\nimport dns.rdtypes.ANY.RRSIG\nimport dns.rdtypes.ANY.SOA\nimport dns.rdtypes.ANY.TXT\nimport dns.rdtypes.IN.A\nimport dns.rdtypes.IN.AAAA\n\n\n# Pure-Python version of dns.dnssec._validate_rsig\nimport ecdsa\nfrom . import rsakey\n\n\ndef python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None):\n    from dns.dnssec import ValidationFailure, ECDSAP256SHA256, ECDSAP384SHA384\n    from dns.dnssec import _find_candidate_keys, _make_hash, _is_ecdsa, _is_rsa, _to_rdata, _make_algorithm_id\n\n    if isinstance(origin, str):\n        origin = dns.name.from_text(origin, dns.name.root)\n\n    for candidate_key in _find_candidate_keys(keys, rrsig):\n        if not candidate_key:\n            raise ValidationFailure('unknown key')\n\n        # For convenience, allow the rrset to be specified as a (name, rdataset)\n        # tuple as well as a proper rrset\n        if isinstance(rrset, tuple):\n            rrname = rrset[0]\n            rdataset = rrset[1]\n        else:\n            rrname = rrset.name\n            rdataset = rrset\n\n        if now is None:\n            now = time.time()\n        if rrsig.expiration < now:\n            raise ValidationFailure('expired')\n        if rrsig.inception > now:\n            raise ValidationFailure('not yet valid')\n\n        hash = _make_hash(rrsig.algorithm)\n\n        if _is_rsa(rrsig.algorithm):\n            keyptr = candidate_key.key\n            (bytes,) = struct.unpack('!B', keyptr[0:1])\n            keyptr = keyptr[1:]\n            if bytes == 0:\n                (bytes,) = struct.unpack('!H', keyptr[0:2])\n                keyptr = keyptr[2:]\n            rsa_e = keyptr[0:bytes]\n            rsa_n = keyptr[bytes:]\n            n = ecdsa.util.string_to_number(rsa_n)\n            e = ecdsa.util.string_to_number(rsa_e)\n            pubkey = rsakey.RSAKey(n, e)\n            sig = rrsig.signature\n\n        elif _is_ecdsa(rrsig.algorithm):\n            if rrsig.algorithm == ECDSAP256SHA256:\n                curve = ecdsa.curves.NIST256p\n                key_len = 32\n                digest_len = 32\n            elif rrsig.algorithm == ECDSAP384SHA384:\n                curve = ecdsa.curves.NIST384p\n                key_len = 48\n                digest_len = 48\n            else:\n                # shouldn't happen\n                raise ValidationFailure('unknown ECDSA curve')\n            keyptr = candidate_key.key\n            x = ecdsa.util.string_to_number(keyptr[0:key_len])\n            y = ecdsa.util.string_to_number(keyptr[key_len:key_len * 2])\n            assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)\n            point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)\n            verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, curve)\n            r = rrsig.signature[:key_len]\n            s = rrsig.signature[key_len:]\n            sig = ecdsa.ecdsa.Signature(ecdsa.util.string_to_number(r),\n                                        ecdsa.util.string_to_number(s))\n\n        else:\n            raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)\n\n        hash.update(_to_rdata(rrsig, origin)[:18])\n        hash.update(rrsig.signer.to_digestable(origin))\n\n        if rrsig.labels < len(rrname) - 1:\n            suffix = rrname.split(rrsig.labels + 1)[1]\n            rrname = dns.name.from_text('*', suffix)\n        rrnamebuf = rrname.to_digestable(origin)\n        rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,\n                              rrsig.original_ttl)\n        rrlist = sorted(rdataset);\n        for rr in rrlist:\n            hash.update(rrnamebuf)\n            hash.update(rrfixed)\n            rrdata = rr.to_digestable(origin)\n            rrlen = struct.pack('!H', len(rrdata))\n            hash.update(rrlen)\n            hash.update(rrdata)\n\n        digest = hash.digest()\n\n        if _is_rsa(rrsig.algorithm):\n            digest = _make_algorithm_id(rrsig.algorithm) + digest\n            if pubkey.verify(bytearray(sig), bytearray(digest)):\n                return\n\n        elif _is_ecdsa(rrsig.algorithm):\n            diglong = ecdsa.util.string_to_number(digest)\n            if verifying_key.pubkey.verifies(diglong, sig):\n                return\n\n        else:\n            raise ValidationFailure('unknown algorithm %s' % rrsig.algorithm)\n\n    raise ValidationFailure('verify failure')\n\n\n# replace validate_rrsig\ndns.dnssec._validate_rrsig = python_validate_rrsig\ndns.dnssec.validate_rrsig = python_validate_rrsig\ndns.dnssec.validate = dns.dnssec._validate\n\n\n\nfrom .util import print_error\n\n\n# hard-coded trust anchors (root KSKs)\ntrust_anchors = [\n    # KSK-2017:\n    dns.rrset.from_text('.', 1    , 'IN', 'DNSKEY', '257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU='),\n    # KSK-2010:\n    dns.rrset.from_text('.', 15202, 'IN', 'DNSKEY', '257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0='),\n]\n\n\ndef check_query(ns, sub, _type, keys):\n    q = dns.message.make_query(sub, _type, want_dnssec=True)\n    response = dns.query.tcp(q, ns, timeout=5)\n    assert response.rcode() == 0, 'No answer'\n    answer = response.answer\n    assert len(answer) != 0, ('No DNS record found', sub, _type)\n    assert len(answer) != 1, ('No DNSSEC record found', sub, _type)\n    if answer[0].rdtype == dns.rdatatype.RRSIG:\n        rrsig, rrset = answer\n    elif answer[1].rdtype == dns.rdatatype.RRSIG:\n        rrset, rrsig = answer\n    else:\n        raise BaseException('No signature set in record')\n    if keys is None:\n        keys = {dns.name.from_text(sub):rrset}\n    dns.dnssec.validate(rrset, rrsig, keys)\n    return rrset\n\n\ndef get_and_validate(ns, url, _type):\n    # get trusted root key\n    root_rrset = None\n    for dnskey_rr in trust_anchors:\n        try:\n            # Check if there is a valid signature for the root dnskey\n            root_rrset = check_query(ns, '', dns.rdatatype.DNSKEY, {dns.name.root: dnskey_rr})\n            break\n        except dns.dnssec.ValidationFailure:\n            # It's OK as long as one key validates\n            continue\n    if not root_rrset:\n        raise dns.dnssec.ValidationFailure('None of the trust anchors found in DNS')\n    keys = {dns.name.root: root_rrset}\n    # top-down verification\n    parts = url.split('.')\n    for i in range(len(parts), 0, -1):\n        sub = '.'.join(parts[i-1:])\n        name = dns.name.from_text(sub)\n        # If server is authoritative, don't fetch DNSKEY\n        query = dns.message.make_query(sub, dns.rdatatype.NS)\n        response = dns.query.udp(query, ns, 3)\n        assert response.rcode() == dns.rcode.NOERROR, \"query error\"\n        rrset = response.authority[0] if len(response.authority) > 0 else response.answer[0]\n        rr = rrset[0]\n        if rr.rdtype == dns.rdatatype.SOA:\n            continue\n        # get DNSKEY (self-signed)\n        rrset = check_query(ns, sub, dns.rdatatype.DNSKEY, None)\n        # get DS (signed by parent)\n        ds_rrset = check_query(ns, sub, dns.rdatatype.DS, keys)\n        # verify that a signed DS validates DNSKEY\n        for ds in ds_rrset:\n            for dnskey in rrset:\n                htype = 'SHA256' if ds.digest_type == 2 else 'SHA1'\n                good_ds = dns.dnssec.make_ds(name, dnskey, htype)\n                if ds == good_ds:\n                    break\n            else:\n                continue\n            break\n        else:\n            raise BaseException(\"DS does not match DNSKEY\")\n        # set key for next iteration\n        keys = {name: rrset}\n    # get TXT record (signed by zone)\n    rrset = check_query(ns, url, _type, keys)\n    return rrset\n\n\ndef query(url, rtype):\n    # 8.8.8.8 is Google's public DNS server\n    nameservers = ['8.8.8.8']\n    ns = nameservers[0]\n    try:\n        out = get_and_validate(ns, url, rtype)\n        validated = True\n    except BaseException as e:\n        #traceback.print_exc(file=sys.stderr)\n        print_error(\"DNSSEC error:\", str(e))\n        resolver = dns.resolver.get_default_resolver()\n        out = resolver.query(url, rtype)\n        validated = False\n    return out, validated\n"
  },
  {
    "path": "lib/equihash.py",
    "content": "# ZCASH implementation: https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/equihash.py\nfrom pyblake2 import blake2b\nfrom operator import itemgetter\nimport struct\nfrom functools import reduce\n\nDEBUG = False\nVERBOSE = False\n\n\nword_size = 32\nword_mask = (1<<word_size)-1\n\n\ndef expand_array(inp, out_len, bit_len, byte_pad=0):\n    assert bit_len >= 8 and word_size >= 7+bit_len\n    bit_len_mask = (1<<bit_len)-1\n\n    out_width = (bit_len+7)//8 + byte_pad\n    assert out_len == 8*out_width*len(inp)//bit_len\n    out = bytearray(out_len)\n\n    bit_len_mask = (1 << bit_len) - 1\n\n    # The acc_bits least-significant bits of acc_value represent a bit sequence\n    # in big-endian order.\n    acc_bits = 0\n    acc_value = 0\n\n    j = 0\n    for i in range(len(inp)):\n        acc_value = ((acc_value << 8) & word_mask) | inp[i]\n        acc_bits += 8\n\n        # When we have bit_len or more bits in the accumulator, write the next\n        # output element.\n        if acc_bits >= bit_len:\n            acc_bits -= bit_len\n            for x in range(byte_pad, out_width):\n                out[j+x] = (\n                    # Big-endian\n                    acc_value >> (acc_bits+(8*(out_width-x-1)))\n                ) & (\n                    # Apply bit_len_mask across byte boundaries\n                    (bit_len_mask >> (8*(out_width-x-1))) & 0xFF\n                )\n            j += out_width\n\n    return out\n\n\ndef compress_array(inp, out_len, bit_len, byte_pad=0):\n    assert bit_len >= 8 and word_size >= 7+bit_len\n\n    in_width = (bit_len+7)//8 + byte_pad\n    assert out_len == bit_len*len(inp)//(8*in_width)\n    out = bytearray(out_len)\n\n    bit_len_mask = (1 << bit_len) - 1\n\n    # The acc_bits least-significant bits of acc_value represent a bit sequence\n    # in big-endian order.\n    acc_bits = 0\n    acc_value = 0\n\n    j = 0\n    for i in range(out_len):\n        # When we have fewer than 8 bits left in the accumulator, read the next\n        # input element.\n        if acc_bits < 8:\n            acc_value = ((acc_value << bit_len) & word_mask) | inp[j]\n            for x in range(byte_pad, in_width):\n                acc_value = acc_value | (\n                    (\n                        # Apply bit_len_mask across byte boundaries\n                        inp[j+x] & ((bit_len_mask >> (8*(in_width-x-1))) & 0xFF)\n                    ) << (8*(in_width-x-1))); # Big-endian\n            j += in_width\n            acc_bits += bit_len\n\n        acc_bits -= 8\n        out[i] = (acc_value >> acc_bits) & 0xFF\n\n    return out\n\n\ndef get_indices_from_minimal(minimal, bit_len):\n    eh_index_size = 4\n    assert (bit_len+7)//8 <= eh_index_size\n    len_indices = 8*eh_index_size*len(minimal)//bit_len\n    byte_pad = eh_index_size - (bit_len+7)//8\n    expanded = expand_array(minimal, len_indices, bit_len, byte_pad)\n    return [struct.unpack('>I', expanded[i:i+4])[0] for i in range(0, len_indices, eh_index_size)]\n\n\ndef get_minimal_from_indices(indices, bit_len):\n    eh_index_size = 4\n    assert (bit_len+7)//8 <= eh_index_size\n    len_indices = len(indices)*eh_index_size\n    min_len = bit_len*len_indices//(8*eh_index_size)\n    byte_pad = eh_index_size - (bit_len+7)//8\n    byte_indices = bytearray(b''.join([struct.pack('>I', i) for i in indices]))\n    return compress_array(byte_indices, min_len, bit_len, byte_pad)\n\n\ndef hash_nonce(digest, nonce):\n    for i in range(8):\n        digest.update(struct.pack('<I', nonce >> (32*i) & 0xffffffff))\n\ndef hash_xi(digest, xi):\n    digest.update(struct.pack('<I', xi))\n    return digest # For chaining\n\n\ndef count_zeroes(h):\n    # Convert to binary string\n    if type(h) == bytearray:\n        h = ''.join('{0:08b}'.format(x, 'b') for x in h)\n    else:\n        h = ''.join('{0:08b}'.format(ord(x), 'b') for x in h)\n    # Count leading zeroes\n    return (h+'1').index('1')\n\n\ndef has_collision(ha, hb, i, l):\n    res = [ha[j] == hb[j] for j in range((i-1)*l//8, i*l//8)]\n    return reduce(lambda x, y: x and y, res)\n\n\ndef distinct_indices(a, b):\n    for i in a:\n        for j in b:\n            if i == j:\n                return False\n    return True\n\n\ndef xor(ha, hb):\n    return bytearray(a^b for a,b in zip(ha,hb))\n\n\ndef gbp_basic(digest, n, k):\n    '''Implementation of Basic Wagner's algorithm for the GBP.'''\n    validate_params(n, k)\n    collision_length = n/(k+1)\n    hash_length = (k+1)*((collision_length+7)//8)\n    indices_per_hash_output = 512/n\n\n    # 1) Generate first list\n    if DEBUG: print('Generating first list')\n    X = []\n    tmp_hash = ''\n    for i in range(0, 2**(collision_length+1)):\n        r = i % indices_per_hash_output\n        if r == 0:\n            # X_i = H(I||V||x_i)\n            curr_digest = digest.copy()\n            hash_xi(curr_digest, i/indices_per_hash_output)\n            tmp_hash = curr_digest.digest()\n        X.append((\n            expand_array(bytearray(tmp_hash[r*n/8:(r+1)*n/8]),\n                         hash_length, collision_length),\n            (i,)\n        ))\n\n    # 3) Repeat step 2 until 2n/(k+1) bits remain\n    for i in range(1, k):\n        if DEBUG: print('Round %d:' % i)\n\n        # 2a) Sort the list\n        if DEBUG: print('- Sorting list')\n        X.sort(key=itemgetter(0))\n        if DEBUG and VERBOSE:\n            for Xi in X[-32:]:\n                print('%s %s' % (print_hash(Xi[0]), Xi[1]))\n\n        if DEBUG: print('- Finding collisions')\n        Xc = []\n        while len(X) > 0:\n            # 2b) Find next set of unordered pairs with collisions on first n/(k+1) bits\n            j = 1\n            while j < len(X):\n                if not has_collision(X[-1][0], X[-1-j][0], i, collision_length):\n                    break\n                j += 1\n\n            # 2c) Store tuples (X_i ^ X_j, (i, j)) on the table\n            for l in range(0, j-1):\n                for m in range(l+1, j):\n                    # Check that there are no duplicate indices in tuples i and j\n                    if distinct_indices(X[-1-l][1], X[-1-m][1]):\n                        if X[-1-l][1][0] < X[-1-m][1][0]:\n                            concat = X[-1-l][1] + X[-1-m][1]\n                        else:\n                            concat = X[-1-m][1] + X[-1-l][1]\n                        Xc.append((xor(X[-1-l][0], X[-1-m][0]), concat))\n\n            # 2d) Drop this set\n            while j > 0:\n                X.pop(-1)\n                j -= 1\n        # 2e) Replace previous list with new list\n        X = Xc\n\n    # k+1) Find a collision on last 2n(k+1) bits\n    if DEBUG:\n        print('Final round:')\n        print('- Sorting list')\n    X.sort(key=itemgetter(0))\n    if DEBUG and VERBOSE:\n        for Xi in X[-32:]:\n            print('%s %s' % (print_hash(Xi[0]), Xi[1]))\n    if DEBUG: print('- Finding collisions')\n    solns = []\n    while len(X) > 0:\n        j = 1\n        while j < len(X):\n            if not (has_collision(X[-1][0], X[-1-j][0], k, collision_length) and\n                    has_collision(X[-1][0], X[-1-j][0], k+1, collision_length)):\n                break\n            j += 1\n\n        for l in range(0, j-1):\n            for m in range(l+1, j):\n                res = xor(X[-1-l][0], X[-1-m][0])\n                if count_zeroes(res) == 8*hash_length and distinct_indices(X[-1-l][1], X[-1-m][1]):\n                    if DEBUG and VERBOSE:\n                        print('Found solution:')\n                        print('- %s %s' % (print_hash(X[-1-l][0]), X[-1-l][1]))\n                        print('- %s %s' % (print_hash(X[-1-m][0]), X[-1-m][1]))\n                    if X[-1-l][1][0] < X[-1-m][1][0]:\n                        solns.append(list(X[-1-l][1] + X[-1-m][1]))\n                    else:\n                        solns.append(list(X[-1-m][1] + X[-1-l][1]))\n\n        # 2d) Drop this set\n        while j > 0:\n            X.pop(-1)\n            j -= 1\n    return [get_minimal_from_indices(soln, collision_length+1) for soln in solns]\n\n\ndef gbp_validate(digest, minimal, n, k):\n    validate_params(n, k)\n    collision_length = n//(k+1)\n    hash_length = (k+1)*((collision_length+7)//8)\n    indices_per_hash_output = 512//n\n    solution_width = (1 << k)*(collision_length+1)//8\n\n    if len(minimal) != solution_width:\n        print('Invalid solution length: %d (expected %d)' % \\\n            (len(minimal), solution_width))\n        return False\n\n    X = []\n    for i in get_indices_from_minimal(minimal, collision_length+1):\n        r = i % indices_per_hash_output\n        # X_i = H(I||V||x_i)\n        curr_digest = digest.copy()\n        hash_xi(curr_digest, i//indices_per_hash_output)\n        tmp_hash = curr_digest.digest()\n        X.append((\n            expand_array(bytearray(tmp_hash[r*n//8:(r+1)*n//8]),\n                         hash_length, collision_length),\n            (i,)\n        ))\n\n    for r in range(1, k+1):\n        Xc = []\n        for i in range(0, len(X), 2):\n            if not has_collision(X[i][0], X[i+1][0], r, collision_length):\n                print('Invalid solution: invalid collision length between StepRows')\n                return False\n            if X[i+1][1][0] < X[i][1][0]:\n                print('Invalid solution: Index tree incorrectly ordered')\n                return False\n            if not distinct_indices(X[i][1], X[i+1][1]):\n                print('Invalid solution: duplicate indices')\n                return False\n            Xc.append((xor(X[i][0], X[i+1][0]), X[i][1] + X[i+1][1]))\n        X = Xc\n\n    if len(X) != 1:\n        print('Invalid solution: incorrect length after end of rounds: %d' % len(X))\n        return False\n\n    if count_zeroes(X[0][0]) != 8*hash_length:\n        print('Invalid solution: incorrect number of zeroes: %d' % count_zeroes(X[0][0]))\n        return False\n\n    return True\n\n\ndef zcash_person(n, k):\n    return b'ZcashPoW' + struct.pack('<II', n, k)\n\n\ndef print_hash(h):\n    if type(h) == bytearray:\n        return ''.join('{0:02x}'.format(x, 'x') for x in h)\n    else:\n        return ''.join('{0:02x}'.format(ord(x), 'x') for x in h)\n\n\ndef validate_params(n, k):\n    if (k >= n):\n        raise ValueError('n must be larger than k')\n    if (((n/(k+1))+1) >= 32):\n        raise ValueError('Parameters must satisfy n/(k+1)+1 < 32')\n\n\n# a bit different from https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/mininode.py#L747\n# since electrum is a SPV oriented and not a node\ndef is_gbp_valid(header, nNonce, nSolution, n=48, k=5):\n    # H(I||...\n    digest = blake2b(digest_size=(512//n)*n//8, person=zcash_person(n, k))\n    digest.update(header[:108])\n    hash_nonce(digest, nNonce)\n    return gbp_validate(digest, nSolution, n, k)\n\n"
  },
  {
    "path": "lib/exchange_rate.py",
    "content": "from datetime import datetime\nimport inspect\nimport requests\nimport sys\nfrom threading import Thread\nimport time\nimport csv\nfrom decimal import Decimal\n\nfrom .bitcoin import COIN\nfrom .i18n import _\nfrom .util import PrintError, ThreadJob\n\n\n# See https://en.wikipedia.org/wiki/ISO_4217\nCCY_PRECISIONS = {}\n'''\n{'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,\n'CVE': 0, 'DJF': 0, 'GNF': 0, 'IQD': 3, 'ISK': 0,\n'JOD': 3, 'JPY': 0, 'KMF': 0, 'KRW': 0, 'KWD': 3,\n'LYD': 3, 'MGA': 1, 'MRO': 1, 'OMR': 3, 'PYG': 0,\n'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0,\n'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}\n'''\n\nclass ExchangeBase(PrintError):\n\n    def __init__(self, on_quotes, on_history):\n        self.history = {}\n        self.quotes = {}\n        self.on_quotes = on_quotes\n        self.on_history = on_history\n\n    def get_json(self, site, get_string):\n        # APIs must have https\n        url = ''.join(['https://', site, get_string])\n        response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})\n        return response.json()\n\n    def get_csv(self, site, get_string):\n        url = ''.join(['https://', site, get_string])\n        response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'})\n        reader = csv.DictReader(response.content.decode().split('\\n'))\n        return list(reader)\n\n    def name(self):\n        return self.__class__.__name__\n\n    def update_safe(self, ccy):\n        try:\n            self.print_error(\"getting fx quotes for\", ccy)\n            self.quotes = self.get_rates(ccy)\n            self.print_error(\"received fx quotes\")\n        except BaseException as e:\n            self.print_error(\"failed fx quotes:\", e)\n        self.on_quotes()\n\n    def update(self, ccy):\n        t = Thread(target=self.update_safe, args=(ccy,))\n        t.setDaemon(True)\n        t.start()\n\n    def get_historical_rates_safe(self, ccy):\n        try:\n            self.print_error(\"requesting fx history for\", ccy)\n            self.history[ccy] = self.historical_rates(ccy)\n            self.print_error(\"received fx history for\", ccy)\n            self.on_history()\n        except BaseException as e:\n            self.print_error(\"failed fx history:\", e)\n\n    def get_historical_rates(self, ccy):\n        result = self.history.get(ccy)\n        if not result and ccy in self.history_ccys():\n            t = Thread(target=self.get_historical_rates_safe, args=(ccy,))\n            t.setDaemon(True)\n            t.start()\n        return result\n\n    def history_ccys(self):\n        return []\n\n    def historical_rate(self, ccy, d_t):\n        return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'))\n\n    def get_currencies(self):\n        rates = self.get_rates('')\n        return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a) in [3,4]])\n\n\nclass CoinMarketCap(ExchangeBase):\n    def get_rates(self, ccy):\n        json = self.get_json('api.coinmarketcap.com',\n                             \"/v1/ticker/bitcoin-private?convert=%s\")\n        return {'USD': Decimal(json[0]['price_usd'])}\n\n\ndef dictinvert(d):\n    inv = {}\n    for k, vlist in d.items():\n        for v in vlist:\n            keys = inv.setdefault(v, [])\n            keys.append(k)\n    return inv\n\ndef get_exchanges_and_currencies():\n    import os, json\n    path = os.path.join(os.path.dirname(__file__), 'currencies.json')\n    try:\n        with open(path, 'r') as f:\n            return json.loads(f.read())\n    except:\n        pass\n    d = {}\n    is_exchange = lambda obj: (inspect.isclass(obj)\n                               and issubclass(obj, ExchangeBase)\n                               and obj != ExchangeBase)\n    exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange))\n    for name, klass in exchanges.items():\n        exchange = klass(None, None)\n        try:\n            d[name] = exchange.get_currencies()\n        except:\n            continue\n    with open(path, 'w') as f:\n        f.write(json.dumps(d, indent=4, sort_keys=True))\n    return d\n\n\nCURRENCIES = get_exchanges_and_currencies()\n\n\ndef get_exchanges_by_ccy(history=True):\n    if not history:\n        return dictinvert(CURRENCIES)\n    d = {}\n    exchanges = CURRENCIES.keys()\n    for name in exchanges:\n        klass = globals()[name]\n        exchange = klass(None, None)\n        d[name] = exchange.history_ccys()\n    return dictinvert(d)\n\n\nclass FxThread(ThreadJob):\n\n    def __init__(self, config, network):\n        self.config = config\n        self.network = network\n        self.ccy = self.get_currency()\n        self.history_used_spot = False\n        self.ccy_combo = None\n        self.hist_checkbox = None\n        self.set_exchange(self.config_exchange())\n\n    def get_currencies(self, h):\n        d = get_exchanges_by_ccy(h)\n        return sorted(d.keys())\n\n    def get_exchanges_by_ccy(self, ccy, h):\n        d = get_exchanges_by_ccy(h)\n        return d.get(ccy, [])\n\n    def ccy_amount_str(self, amount, commas):\n        prec = CCY_PRECISIONS.get(self.ccy, 2)\n        fmt_str = \"{:%s.%df}\" % (\",\" if commas else \"\", max(0, prec))\n        return fmt_str.format(round(amount, prec))\n\n    def run(self):\n        # This runs from the plugins thread which catches exceptions\n        if self.is_enabled():\n            if self.timeout ==0 and self.show_history():\n                self.exchange.get_historical_rates(self.ccy)\n            if self.timeout <= time.time():\n                self.timeout = time.time() + 150\n                self.exchange.update(self.ccy)\n\n    def is_enabled(self):\n        return bool(self.config.get('use_exchange_rate'))\n\n    def set_enabled(self, b):\n        return self.config.set_key('use_exchange_rate', bool(b))\n\n    def get_history_config(self):\n        return bool(self.config.get('history_rates'))\n\n    def set_history_config(self, b):\n        self.config.set_key('history_rates', bool(b))\n\n    def get_fiat_address_config(self):\n        return bool(self.config.get('fiat_address'))\n\n    def set_fiat_address_config(self, b):\n        self.config.set_key('fiat_address', bool(b))\n\n    def get_currency(self):\n        # Use when dynamic fetching is needed\n        return self.config.get('currency', 'USD')\n\n    def config_exchange(self):\n        return self.config.get('use_exchange', 'CoinMarketCap')\n\n    def show_history(self):\n        return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()\n\n    def set_currency(self, ccy):\n        self.ccy = ccy\n        self.config.set_key('currency', ccy, True)\n        self.timeout = 0 # Because self.ccy changes\n        self.on_quotes()\n\n    def set_exchange(self, name):\n        class_ = globals().get(name, CoinMarketCap)\n        self.print_error(\"using exchange\", name)\n        if self.config_exchange() != name:\n            self.config.set_key('use_exchange', name, True)\n        self.exchange = class_(self.on_quotes, self.on_history)\n        # A new exchange means new fx quotes, initially empty.  Force\n        # a quote refresh\n        self.timeout = 0\n\n    def on_quotes(self):\n        self.network.trigger_callback('on_quotes')\n\n    def on_history(self):\n        self.network.trigger_callback('on_history')\n\n    def exchange_rate(self):\n        '''Returns None, or the exchange rate as a Decimal'''\n        rate = self.exchange.quotes.get(self.ccy)\n        if rate:\n            return Decimal(rate)\n\n    def format_amount_and_units(self, btc_balance):\n        rate = self.exchange_rate()\n        return '' if rate is None else \"%s %s\" % (self.value_str(btc_balance, rate), self.ccy)\n\n    def get_fiat_status_text(self, btc_balance, base_unit, decimal_point):\n        rate = self.exchange_rate()\n        return _(\"  (No exchange rate available)\") if rate is None else \" 1 %s=%s %s\" % (base_unit,\n            self.value_str(COIN / (10**(8 - decimal_point)), rate), self.ccy)\n\n    def value_str(self, satoshis, rate):\n        if satoshis is None:  # Can happen with incomplete history\n            return _(\"Unknown\")\n        if rate:\n            value = Decimal(satoshis) / COIN * Decimal(rate)\n            return \"%s\" % (self.ccy_amount_str(value, True))\n        return _(\"No data\")\n\n    def history_rate(self, d_t):\n        rate = self.exchange.historical_rate(self.ccy, d_t)\n        # Frequently there is no rate for today, until tomorrow :)\n        # Use spot quotes in that case\n        if rate is None and (datetime.today().date() - d_t.date()).days <= 2:\n            rate = self.exchange.quotes.get(self.ccy)\n            self.history_used_spot = True\n        return rate\n\n    def historical_value_str(self, satoshis, d_t):\n        rate = self.history_rate(d_t)\n        return self.value_str(satoshis, rate)\n"
  },
  {
    "path": "lib/i18n.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2012 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport gettext, os\n\nLOCALE_DIR = os.path.join(os.path.dirname(__file__), 'locale')\nlanguage = gettext.translation('electrum', LOCALE_DIR, fallback = True)\n\ndef _(x):\n    global language\n    return language.gettext(x)\n\ndef set_language(x):\n    global language\n    if x: language = gettext.translation('electrum', LOCALE_DIR, fallback = True, languages=[x])\n\n\nlanguages = {\n    '':_('Default'),\n    'ar_SA':_('Arabic'),\n    'cs_CZ':_('Czech'),\n    'da_DK':_('Danish'),\n    'de_DE':_('German'),\n    'eo_UY':_('Esperanto'),\n    'el_GR':_('Greek'),\n    'en_UK':_('English'),\n    'es_ES':_('Spanish'),\n    'fr_FR':_('French'),\n    'hu_HU':_('Hungarian'),\n    'hy_AM':_('Armenian'),\n    'id_ID':_('Indonesian'),\n    'it_IT':_('Italian'),\n    'ja_JP':_('Japanese'),\n    'ky_KG':_('Kyrgyz'),\n    'lv_LV':_('Latvian'),\n    'nl_NL':_('Dutch'),\n    'no_NO':_('Norwegian'),\n    'pl_PL':_('Polish'),\n    'pt_BR':_('Brasilian'),\n    'pt_PT':_('Portuguese'),\n    'ro_RO':_('Romanian'),\n    'ru_RU':_('Russian'),\n    'sk_SK':_('Slovak'),\n    'sl_SI':_('Slovenian'),\n    'ta_IN':_('Tamil'),\n    'th_TH':_('Thai'),\n    'vi_VN':_('Vietnamese'),\n    'zh_CN':_('Chinese')\n    }\n"
  },
  {
    "path": "lib/interface.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport os\nimport re\nimport socket\nimport ssl\nimport sys\nimport threading\nimport time\nimport traceback\n\nfrom .util import print_error, get_cert_path\n\nca_path = get_cert_path()\n\nfrom . import util\nfrom . import x509\nfrom . import pem\n\n\ndef Connection(server, queue, config_path):\n    \"\"\"Makes asynchronous connections to a remote electrum server.\n    Returns the running thread that is making the connection.\n\n    Once the thread has connected, it finishes, placing a tuple on the\n    queue of the form (server, socket), where socket is None if\n    connection failed.\n    \"\"\"\n    host, port, protocol = server.rsplit(':', 2)\n    if not protocol in 'st':\n        raise Exception('Unknown protocol: %s' % protocol)\n    c = TcpConnection(server, queue, config_path)\n    c.start()\n    return c\n\n\nclass TcpConnection(threading.Thread, util.PrintError):\n\n    def __init__(self, server, queue, config_path):\n        threading.Thread.__init__(self)\n        self.config_path = config_path\n        self.queue = queue\n        self.server = server\n        self.host, self.port, self.protocol = self.server.rsplit(':', 2)\n        self.host = str(self.host)\n        self.port = int(self.port)\n        self.use_ssl = (self.protocol == 's')\n        self.daemon = True\n\n    def diagnostic_name(self):\n        return self.host\n\n    def check_host_name(self, peercert, name):\n        \"\"\"Simple certificate/host name checker.  Returns True if the\n        certificate matches, False otherwise.  Does not support\n        wildcards.\"\"\"\n        # Check that the peer has supplied a certificate.\n        # None/{} is not acceptable.\n        if not peercert:\n            return False\n        if 'subjectAltName' in peercert:\n            for typ, val in peercert[\"subjectAltName\"]:\n                if typ == \"DNS\": # and val == name:\n                    return True\n        else:\n            # Only check the subject DN if there is no subject alternative\n            # name.\n            cn = None\n            for attr, val in peercert[\"subject\"]:\n                # Use most-specific (last) commonName attribute.\n                if attr == \"commonName\":\n                    cn = val\n            if cn is not None:\n                return cn == name\n        return False\n\n    def get_simple_socket(self):\n        try:\n            l = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)\n        except socket.gaierror:\n            self.print_error(\"cannot resolve hostname\")\n            return\n        e = None\n        for res in l:\n            try:\n                s = socket.socket(res[0], socket.SOCK_STREAM)\n                s.settimeout(10)\n                s.connect(res[4])\n                s.settimeout(2)\n                s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n                return s\n            except BaseException as _e:\n                e = _e\n                continue\n        else:\n            self.print_error(\"failed to connect\", str(e))\n\n    @staticmethod\n    def get_ssl_context(cert_reqs, ca_certs):\n        context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_certs)\n        context.check_hostname = False\n        context.verify_mode = cert_reqs\n\n        context.options |= ssl.OP_NO_SSLv2\n        context.options |= ssl.OP_NO_SSLv3\n        context.options |= ssl.OP_NO_TLSv1\n\n        return context\n\n    def get_socket(self):\n        if self.use_ssl:\n            cert_path = os.path.join(self.config_path, 'certs', self.host)\n            if not os.path.exists(cert_path):\n                is_new = True\n                s = self.get_simple_socket()\n                if s is None:\n                    return\n                # try with CA first\n                try:\n                    context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_path)\n                    s = context.wrap_socket(s, do_handshake_on_connect=True)\n                except ssl.SSLError as e:\n                    print_error(e)\n                    s = None\n                except:\n                    return\n\n                if s and self.check_host_name(s.getpeercert(), self.host):\n                    self.print_error(\"SSL certificate signed by CA\")\n                    return s\n                # get server certificate.\n                # Do not use ssl.get_server_certificate because it does not work with proxy\n                s = self.get_simple_socket()\n                if s is None:\n                    return\n                try:\n                    context = self.get_ssl_context(cert_reqs=ssl.CERT_NONE, ca_certs=None)\n                    s = context.wrap_socket(s)\n                except ssl.SSLError as e:\n                    self.print_error(\"SSL error retrieving SSL certificate:\", e)\n                    return\n                except:\n                    return\n\n                dercert = s.getpeercert(True)\n                s.close()\n                cert = ssl.DER_cert_to_PEM_cert(dercert)\n                # workaround android bug\n                cert = re.sub(\"([^\\n])-----END CERTIFICATE-----\",\"\\\\1\\n-----END CERTIFICATE-----\",cert)\n                temporary_path = cert_path + '.temp'\n                with open(temporary_path,\"w\") as f:\n                    f.write(cert)\n            else:\n                is_new = False\n\n        s = self.get_simple_socket()\n        if s is None:\n            return\n\n        if self.use_ssl:\n            try:\n                context = self.get_ssl_context(cert_reqs=ssl.CERT_REQUIRED,\n                                               ca_certs=(temporary_path if is_new else cert_path))\n                s = context.wrap_socket(s, do_handshake_on_connect=True)\n            except socket.timeout:\n                self.print_error('timeout')\n                return\n            except ssl.SSLError as e:\n                self.print_error(\"SSL error:\", e)\n                if e.errno != 1:\n                    return\n                if is_new:\n                    rej = cert_path + '.rej'\n                    if os.path.exists(rej):\n                        os.unlink(rej)\n                    os.rename(temporary_path, rej)\n                else:\n                    with open(cert_path) as f:\n                        cert = f.read()\n                    try:\n                        b = pem.dePem(cert, 'CERTIFICATE')\n                        x = x509.X509(b)\n                    except:\n                        traceback.print_exc(file=sys.stderr)\n                        self.print_error(\"wrong certificate\")\n                        return\n                    try:\n                        x.check_date()\n                    except:\n                        self.print_error(\"certificate has expired:\", cert_path)\n                        os.unlink(cert_path)\n                        return\n                    self.print_error(\"wrong certificate\")\n                if e.errno == 104:\n                    return\n                return\n            except BaseException as e:\n                self.print_error(e)\n                traceback.print_exc(file=sys.stderr)\n                return\n\n            if is_new:\n                self.print_error(\"saving certificate\")\n                os.rename(temporary_path, cert_path)\n\n        return s\n\n    def run(self):\n        socket = self.get_socket()\n        if socket:\n            self.print_error(\"connected\")\n        self.queue.put((self.server, socket))\n\n\nclass Interface(util.PrintError):\n    \"\"\"The Interface class handles a socket connected to a single remote\n    electrum server.  It's exposed API is:\n\n    - Member functions close(), fileno(), get_responses(), has_timed_out(),\n      ping_required(), queue_request(), send_requests()\n    - Member variable server.\n    \"\"\"\n\n    def __init__(self, server, socket):\n        self.server = server\n        self.host, _, _ = server.rsplit(':', 2)\n        self.socket = socket\n\n        self.pipe = util.SocketPipe(socket)\n        self.pipe.set_timeout(0.0)  # Don't wait for data\n        # Dump network messages.  Set at runtime from the console.\n        self.debug = False\n        self.unsent_requests = []\n        self.unanswered_requests = {}\n        # Set last ping to zero to ensure immediate ping\n        self.last_request = time.time()\n        self.last_ping = 0\n        self.closed_remotely = False\n\n    def diagnostic_name(self):\n        return self.host\n\n    def fileno(self):\n        # Needed for select\n        return self.socket.fileno()\n\n    def close(self):\n        if not self.closed_remotely:\n            try:\n                self.socket.shutdown(socket.SHUT_RDWR)\n            except socket.error:\n                pass\n        self.socket.close()\n\n    def queue_request(self, *args):  # method, params, _id\n        '''Queue a request, later to be send with send_requests when the\n        socket is available for writing.\n        '''\n        self.request_time = time.time()\n        self.unsent_requests.append(args)\n\n    def num_requests(self):\n        '''Keep unanswered requests below 100'''\n        n = 100 - len(self.unanswered_requests)\n        return min(n, len(self.unsent_requests))\n\n    def send_requests(self):\n        '''Sends queued requests.  Returns False on failure.'''\n        make_dict = lambda m, p, i: {'method': m, 'params': p, 'id': i}\n        n = self.num_requests()\n        wire_requests = self.unsent_requests[0:n]\n        try:\n            self.pipe.send_all([make_dict(*r) for r in wire_requests])\n        except socket.error as e:\n            self.print_error(\"socket error:\", e)\n            return False\n        self.unsent_requests = self.unsent_requests[n:]\n        for request in wire_requests:\n            if self.debug:\n                self.print_error(\"-->\", request)\n            self.unanswered_requests[request[2]] = request\n        return True\n\n    def ping_required(self):\n        '''Maintains time since last ping.  Returns True if a ping should\n        be sent.\n        '''\n        now = time.time()\n        if now - self.last_ping > 60:\n            self.last_ping = now\n            return True\n        return False\n\n    def has_timed_out(self):\n        '''Returns True if the interface has timed out.'''\n        if (self.unanswered_requests and time.time() - self.request_time > 10\n            and self.pipe.idle_time() > 10):\n            self.print_error(\"timeout\", len(self.unanswered_requests))\n            return True\n\n        return False\n\n    def get_responses(self):\n        '''Call if there is data available on the socket.  Returns a list of\n        (request, response) pairs.  Notifications are singleton\n        unsolicited responses presumably as a result of prior\n        subscriptions, so request is None and there is no 'id' member.\n        Otherwise it is a response, which has an 'id' member and a\n        corresponding request.  If the connection was closed remotely\n        or the remote server is misbehaving, a (None, None) will appear.\n        '''\n        responses = []\n        while True:\n            try:\n                response = self.pipe.get()\n            except util.timeout:\n                break\n            if not type(response) is dict:\n                responses.append((None, None))\n                if response is None:\n                    self.closed_remotely = True\n                    self.print_error(\"connection closed remotely\")\n                break\n            if self.debug:\n                self.print_error(\"<--\", response)\n            wire_id = response.get('id', None)\n            if wire_id is None:  # Notification\n                responses.append((None, response))\n            else:\n                request = self.unanswered_requests.pop(wire_id, None)\n                if request:\n                    responses.append((request, response))\n                else:\n                    self.print_error(\"unknown wire ID\", wire_id)\n                    responses.append((None, None)) # Signal\n                    break\n\n        return responses\n\n\ndef check_cert(host, cert):\n    try:\n        b = pem.dePem(cert, 'CERTIFICATE')\n        x = x509.X509(b)\n    except:\n        traceback.print_exc(file=sys.stdout)\n        return\n\n    try:\n        x.check_date()\n        expired = False\n    except:\n        expired = True\n\n    m = \"host: %s\\n\"%host\n    m += \"has_expired: %s\\n\"% expired\n    util.print_msg(m)\n\n\n# Used by tests\ndef _match_hostname(name, val):\n    if val == name:\n        return True\n\n    return val.startswith('*.') and name.endswith(val[1:])\n\n\ndef test_certificates():\n    from .simple_config import SimpleConfig\n    config = SimpleConfig()\n    mydir = os.path.join(config.path, \"certs\")\n    certs = os.listdir(mydir)\n    for c in certs:\n        p = os.path.join(mydir,c)\n        with open(p) as f:\n            cert = f.read()\n        check_cert(c, cert)\n\nif __name__ == \"__main__\":\n    test_certificates()\n"
  },
  {
    "path": "lib/jsonrpc.py",
    "content": "#!/usr/bin/env python3\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2018 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler\nfrom base64 import b64decode\nimport time\n\nfrom . import util\n\n\nclass RPCAuthCredentialsInvalid(Exception):\n    def __str__(self):\n        return 'Authentication failed (bad credentials)'\n\n\nclass RPCAuthCredentialsMissing(Exception):\n    def __str__(self):\n        return 'Authentication failed (missing credentials)'\n\n\nclass RPCAuthUnsupportedType(Exception):\n    def __str__(self):\n        return 'Authentication failed (only basic auth is supported)'\n\n\n# based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke\nclass VerifyingJSONRPCServer(SimpleJSONRPCServer):\n\n    def __init__(self, *args, rpc_user, rpc_password, **kargs):\n\n        self.rpc_user = rpc_user\n        self.rpc_password = rpc_password\n\n        class VerifyingRequestHandler(SimpleJSONRPCRequestHandler):\n            def parse_request(myself):\n                # first, call the original implementation which returns\n                # True if all OK so far\n                if SimpleJSONRPCRequestHandler.parse_request(myself):\n                    try:\n                        self.authenticate(myself.headers)\n                        return True\n                    except (RPCAuthCredentialsInvalid, RPCAuthCredentialsMissing,\n                            RPCAuthUnsupportedType) as e:\n                        myself.send_error(401, str(e))\n                    except BaseException as e:\n                        import traceback, sys\n                        traceback.print_exc(file=sys.stderr)\n                        myself.send_error(500, str(e))\n                return False\n\n        SimpleJSONRPCServer.__init__(\n            self, requestHandler=VerifyingRequestHandler, *args, **kargs)\n\n    def authenticate(self, headers):\n        if self.rpc_password == '':\n            # RPC authentication is disabled\n            return\n\n        auth_string = headers.get('Authorization', None)\n        if auth_string is None:\n            raise RPCAuthCredentialsMissing()\n\n        (basic, _, encoded) = auth_string.partition(' ')\n        if basic != 'Basic':\n            raise RPCAuthUnsupportedType()\n\n        encoded = util.to_bytes(encoded, 'utf8')\n        credentials = util.to_string(b64decode(encoded), 'utf8')\n        (username, _, password) = credentials.partition(':')\n        if not (util.constant_time_compare(username, self.rpc_user)\n                and util.constant_time_compare(password, self.rpc_password)):\n            time.sleep(0.050)\n            raise RPCAuthCredentialsInvalid()\n"
  },
  {
    "path": "lib/keystore.py",
    "content": "#!/usr/bin/env python2\n# -*- mode: python -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2016  The Electrum developers\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom unicodedata import normalize\n\nfrom . import bitcoin\nfrom .bitcoin import *\n\nfrom .util import PrintError, InvalidPassword, hfu\nfrom .mnemonic import Mnemonic, load_wordlist\nfrom .plugins import run_hook\n\n\nclass KeyStore(PrintError):\n\n    def has_seed(self):\n        return False\n\n    def is_watching_only(self):\n        return False\n\n    def can_import(self):\n        return False\n\n    def get_tx_derivations(self, tx):\n        keypairs = {}\n        for txin in tx.inputs():\n            num_sig = txin.get('num_sig')\n            if num_sig is None:\n                continue\n            x_signatures = txin['signatures']\n            signatures = [sig for sig in x_signatures if sig]\n            if len(signatures) == num_sig:\n                # input is complete\n                continue\n            for k, x_pubkey in enumerate(txin['x_pubkeys']):\n                if x_signatures[k] is not None:\n                    # this pubkey already signed\n                    continue\n                derivation = self.get_pubkey_derivation(x_pubkey)\n                if not derivation:\n                    continue\n                keypairs[x_pubkey] = derivation\n        return keypairs\n\n    def can_sign(self, tx):\n        if self.is_watching_only():\n            return False\n        return bool(self.get_tx_derivations(tx))\n\n\n\nclass Software_KeyStore(KeyStore):\n\n    def __init__(self):\n        KeyStore.__init__(self)\n\n    def may_have_password(self):\n        return not self.is_watching_only()\n\n    def sign_message(self, sequence, message, password):\n        privkey, compressed = self.get_private_key(sequence, password)\n        key = regenerate_key(privkey)\n        return key.sign_message(message, compressed)\n\n    def decrypt_message(self, sequence, message, password):\n        privkey, compressed = self.get_private_key(sequence, password)\n        ec = regenerate_key(privkey)\n        decrypted = ec.decrypt_message(message)\n        return decrypted\n\n    def sign_transaction(self, tx, password):\n        if self.is_watching_only():\n            return\n        # Raise if password is not correct.\n        self.check_password(password)\n        # Add private keys\n        keypairs = self.get_tx_derivations(tx)\n        for k, v in keypairs.items():\n            keypairs[k] = self.get_private_key(v, password)\n        # Sign\n        if keypairs:\n            tx.sign(keypairs)\n\n\nclass Imported_KeyStore(Software_KeyStore):\n    # keystore for imported private keys\n\n    def __init__(self, d):\n        Software_KeyStore.__init__(self)\n        self.keypairs = d.get('keypairs', {})\n\n    def is_deterministic(self):\n        return False\n\n    def can_change_password(self):\n        return True\n\n    def get_master_public_key(self):\n        return None\n\n    def dump(self):\n        return {\n            'type': 'imported',\n            'keypairs': self.keypairs,\n        }\n\n    def can_import(self):\n        return True\n\n    def check_password(self, password):\n        pubkey = list(self.keypairs.keys())[0]\n        self.get_private_key(pubkey, password)\n\n    def import_privkey(self, sec, password):\n        txin_type, privkey, compressed = deserialize_privkey(sec)\n        pubkey = public_key_from_private_key(privkey, compressed)\n        self.keypairs[pubkey] = pw_encode(sec, password)\n        return txin_type, pubkey\n\n    def delete_imported_key(self, key):\n        self.keypairs.pop(key)\n\n    def get_private_key(self, pubkey, password):\n        sec = pw_decode(self.keypairs[pubkey], password)\n        txin_type, privkey, compressed = deserialize_privkey(sec)\n        # this checks the password\n        if pubkey != public_key_from_private_key(privkey, compressed):\n            raise InvalidPassword()\n        return privkey, compressed\n\n    def get_pubkey_derivation(self, x_pubkey):\n        if x_pubkey[0:2] in ['02', '03', '04']:\n            if x_pubkey in self.keypairs.keys():\n                return x_pubkey\n        elif x_pubkey[0:2] == 'fd':\n            addr = bitcoin.script_to_address(x_pubkey[2:])\n            if addr in self.addresses:\n                return self.addresses[addr].get('pubkey')\n\n    def update_password(self, old_password, new_password):\n        self.check_password(old_password)\n        if new_password == '':\n            new_password = None\n        for k, v in self.keypairs.items():\n            b = pw_decode(v, old_password)\n            c = pw_encode(b, new_password)\n            self.keypairs[k] = c\n\n\n\nclass Deterministic_KeyStore(Software_KeyStore):\n\n    def __init__(self, d):\n        Software_KeyStore.__init__(self)\n        self.seed = d.get('seed', '')\n        self.passphrase = d.get('passphrase', '')\n\n    def is_deterministic(self):\n        return True\n\n    def dump(self):\n        d = {}\n        if self.seed:\n            d['seed'] = self.seed\n        if self.passphrase:\n            d['passphrase'] = self.passphrase\n        return d\n\n    def has_seed(self):\n        return bool(self.seed)\n\n    def is_watching_only(self):\n        return not self.has_seed()\n\n    def can_change_password(self):\n        return not self.is_watching_only()\n\n    def add_seed(self, seed):\n        if self.seed:\n            raise Exception(\"a seed exists\")\n        self.seed = self.format_seed(seed)\n\n    def get_seed(self, password):\n        return pw_decode(self.seed, password)\n\n    def get_passphrase(self, password):\n        return pw_decode(self.passphrase, password) if self.passphrase else ''\n\n\nclass Xpub:\n\n    def __init__(self):\n        self.xpub = None\n        self.xpub_receive = None\n        self.xpub_change = None\n\n    def get_master_public_key(self):\n        return self.xpub\n\n    def derive_pubkey(self, for_change, n):\n        xpub = self.xpub_change if for_change else self.xpub_receive\n        if xpub is None:\n            xpub = bip32_public_derivation(self.xpub, \"\", \"/%d\"%for_change)\n            if for_change:\n                self.xpub_change = xpub\n            else:\n                self.xpub_receive = xpub\n        return self.get_pubkey_from_xpub(xpub, (n,))\n\n    @classmethod\n    def get_pubkey_from_xpub(self, xpub, sequence):\n        _, _, _, _, c, cK = deserialize_xpub(xpub)\n        for i in sequence:\n            cK, c = CKD_pub(cK, c, i)\n        return bh2u(cK)\n\n    def get_xpubkey(self, c, i):\n        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))\n        return 'ff' + bh2u(bitcoin.DecodeBase58Check(self.xpub)) + s\n\n    @classmethod\n    def parse_xpubkey(self, pubkey):\n        assert pubkey[0:2] == 'ff'\n        pk = bfh(pubkey)\n        pk = pk[1:]\n        xkey = bitcoin.EncodeBase58Check(pk[0:78])\n        dd = pk[78:]\n        s = []\n        while dd:\n            n = int(bitcoin.rev_hex(bh2u(dd[0:2])), 16)\n            dd = dd[2:]\n            s.append(n)\n        assert len(s) == 2\n        return xkey, s\n\n    def get_pubkey_derivation(self, x_pubkey):\n        if x_pubkey[0:2] != 'ff':\n            return\n        xpub, derivation = self.parse_xpubkey(x_pubkey)\n        if self.xpub != xpub:\n            return\n        return derivation\n\n\nclass BIP32_KeyStore(Deterministic_KeyStore, Xpub):\n\n    def __init__(self, d):\n        Xpub.__init__(self)\n        Deterministic_KeyStore.__init__(self, d)\n        self.xpub = d.get('xpub')\n        self.xprv = d.get('xprv')\n\n    def format_seed(self, seed):\n        return ' '.join(seed.split())\n\n    def dump(self):\n        d = Deterministic_KeyStore.dump(self)\n        d['type'] = 'bip32'\n        d['xpub'] = self.xpub\n        d['xprv'] = self.xprv\n        return d\n\n    def get_master_private_key(self, password):\n        return pw_decode(self.xprv, password)\n\n    def check_password(self, password):\n        xprv = pw_decode(self.xprv, password)\n        if deserialize_xprv(xprv)[4] != deserialize_xpub(self.xpub)[4]:\n            raise InvalidPassword()\n\n    def update_password(self, old_password, new_password):\n        self.check_password(old_password)\n        if new_password == '':\n            new_password = None\n        if self.has_seed():\n            decoded = self.get_seed(old_password)\n            self.seed = pw_encode(decoded, new_password)\n        if self.passphrase:\n            decoded = self.get_passphrase(old_password)\n            self.passphrase = pw_encode(decoded, new_password)\n        if self.xprv is not None:\n            b = pw_decode(self.xprv, old_password)\n            self.xprv = pw_encode(b, new_password)\n\n    def is_watching_only(self):\n        return self.xprv is None\n\n    def add_xprv(self, xprv):\n        self.xprv = xprv\n        self.xpub = bitcoin.xpub_from_xprv(xprv)\n\n    def add_xprv_from_seed(self, bip32_seed, xtype, derivation):\n        xprv, xpub = bip32_root(bip32_seed, xtype)\n        xprv, xpub = bip32_private_derivation(xprv, \"m/\", derivation)\n        self.add_xprv(xprv)\n\n    def get_private_key(self, sequence, password):\n        xprv = self.get_master_private_key(password)\n        _, _, _, _, c, k = deserialize_xprv(xprv)\n        pk = bip32_private_key(sequence, k, c)\n        return pk, True\n\n\n\nclass Old_KeyStore(Deterministic_KeyStore):\n\n    def __init__(self, d):\n        Deterministic_KeyStore.__init__(self, d)\n        self.mpk = d.get('mpk')\n\n    def get_hex_seed(self, password):\n        return pw_decode(self.seed, password).encode('utf8')\n\n    def dump(self):\n        d = Deterministic_KeyStore.dump(self)\n        d['mpk'] = self.mpk\n        d['type'] = 'old'\n        return d\n\n    def add_seed(self, seedphrase):\n        Deterministic_KeyStore.add_seed(self, seedphrase)\n        s = self.get_hex_seed(None)\n        self.mpk = self.mpk_from_seed(s)\n\n    def add_master_public_key(self, mpk):\n        self.mpk = mpk\n\n    def format_seed(self, seed):\n        from . import old_mnemonic, mnemonic\n        seed = mnemonic.normalize_text(seed)\n        # see if seed was entered as hex\n        if seed:\n            try:\n                bfh(seed)\n                return str(seed)\n            except Exception:\n                pass\n        words = seed.split()\n        seed = old_mnemonic.mn_decode(words)\n        if not seed:\n            raise Exception(\"Invalid seed\")\n        return seed\n\n    def get_seed(self, password):\n        from . import old_mnemonic\n        s = self.get_hex_seed(password)\n        return ' '.join(old_mnemonic.mn_encode(s))\n\n    @classmethod\n    def mpk_from_seed(klass, seed):\n        secexp = klass.stretch_key(seed)\n        master_private_key = ecdsa.SigningKey.from_secret_exponent(secexp, curve = SECP256k1)\n        master_public_key = master_private_key.get_verifying_key().to_string()\n        return bh2u(master_public_key)\n\n    @classmethod\n    def stretch_key(self, seed):\n        x = seed\n        for i in range(100000):\n            x = hashlib.sha256(x + seed).digest()\n        return string_to_number(x)\n\n    @classmethod\n    def get_sequence(self, mpk, for_change, n):\n        return string_to_number(Hash((\"%d:%d:\"%(n, for_change)).encode('ascii') + bfh(mpk)))\n\n    @classmethod\n    def get_pubkey_from_mpk(self, mpk, for_change, n):\n        z = self.get_sequence(mpk, for_change, n)\n        master_public_key = ecdsa.VerifyingKey.from_string(bfh(mpk), curve = SECP256k1)\n        pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator\n        public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1)\n        return '04' + bh2u(public_key2.to_string())\n\n    def derive_pubkey(self, for_change, n):\n        return self.get_pubkey_from_mpk(self.mpk, for_change, n)\n\n    def get_private_key_from_stretched_exponent(self, for_change, n, secexp):\n        order = generator_secp256k1.order()\n        secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order\n        pk = number_to_string(secexp, generator_secp256k1.order())\n        return pk\n\n    def get_private_key(self, sequence, password):\n        seed = self.get_hex_seed(password)\n        self.check_seed(seed)\n        for_change, n = sequence\n        secexp = self.stretch_key(seed)\n        pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)\n        return pk, False\n\n    def check_seed(self, seed):\n        secexp = self.stretch_key(seed)\n        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )\n        master_public_key = master_private_key.get_verifying_key().to_string()\n        if master_public_key != bfh(self.mpk):\n            print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key))\n            raise InvalidPassword()\n\n    def check_password(self, password):\n        seed = self.get_hex_seed(password)\n        self.check_seed(seed)\n\n    def get_master_public_key(self):\n        return self.mpk\n\n    def get_xpubkey(self, for_change, n):\n        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))\n        return 'fe' + self.mpk + s\n\n    @classmethod\n    def parse_xpubkey(self, x_pubkey):\n        assert x_pubkey[0:2] == 'fe'\n        pk = x_pubkey[2:]\n        mpk = pk[0:128]\n        dd = pk[128:]\n        s = []\n        while dd:\n            n = int(bitcoin.rev_hex(dd[0:4]), 16)\n            dd = dd[4:]\n            s.append(n)\n        assert len(s) == 2\n        return mpk, s\n\n    def get_pubkey_derivation(self, x_pubkey):\n        if x_pubkey[0:2] != 'fe':\n            return\n        mpk, derivation = self.parse_xpubkey(x_pubkey)\n        if self.mpk != mpk:\n            return\n        return derivation\n\n    def update_password(self, old_password, new_password):\n        self.check_password(old_password)\n        if new_password == '':\n            new_password = None\n        if self.has_seed():\n            decoded = pw_decode(self.seed, old_password)\n            self.seed = pw_encode(decoded, new_password)\n\n\n\nclass Hardware_KeyStore(KeyStore, Xpub):\n    # Derived classes must set:\n    #   - device\n    #   - DEVICE_IDS\n    #   - wallet_type\n\n    #restore_wallet_class = BIP32_RD_Wallet\n    max_change_outputs = 1\n\n    def __init__(self, d):\n        Xpub.__init__(self)\n        KeyStore.__init__(self)\n        # Errors and other user interaction is done through the wallet's\n        # handler.  The handler is per-window and preserved across\n        # device reconnects\n        self.xpub = d.get('xpub')\n        self.label = d.get('label')\n        self.derivation = d.get('derivation')\n        self.handler = None\n        run_hook('init_keystore', self)\n\n    def set_label(self, label):\n        self.label = label\n\n    def may_have_password(self):\n        return False\n\n    def is_deterministic(self):\n        return True\n\n    def dump(self):\n        return {\n            'type': 'hardware',\n            'hw_type': self.hw_type,\n            'xpub': self.xpub,\n            'derivation':self.derivation,\n            'label':self.label,\n        }\n\n    def unpaired(self):\n        '''A device paired with the wallet was diconnected.  This can be\n        called in any thread context.'''\n        self.print_error(\"unpaired\")\n\n    def paired(self):\n        '''A device paired with the wallet was (re-)connected.  This can be\n        called in any thread context.'''\n        self.print_error(\"paired\")\n\n    def can_export(self):\n        return False\n\n    def is_watching_only(self):\n        '''The wallet is not watching-only; the user will be prompted for\n        pin and passphrase as appropriate when needed.'''\n        assert not self.has_seed()\n        return False\n\n    def can_change_password(self):\n        return False\n\n\n\ndef bip39_normalize_passphrase(passphrase):\n    return normalize('NFKD', passphrase or '')\n\ndef bip39_to_seed(mnemonic, passphrase):\n    import pbkdf2, hashlib, hmac\n    PBKDF2_ROUNDS = 2048\n    mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))\n    passphrase = bip39_normalize_passphrase(passphrase)\n    return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase,\n                         iterations = PBKDF2_ROUNDS, macmodule = hmac,\n                         digestmodule = hashlib.sha512).read(64)\n\n# returns tuple (is_checksum_valid, is_wordlist_valid)\ndef bip39_is_checksum_valid(mnemonic):\n    words = [ normalize('NFKD', word) for word in mnemonic.split() ]\n    words_len = len(words)\n    wordlist = load_wordlist(\"english.txt\")\n    n = len(wordlist)\n    checksum_length = 11*words_len//33\n    entropy_length = 32*checksum_length\n    i = 0\n    words.reverse()\n    while words:\n        w = words.pop()\n        try:\n            k = wordlist.index(w)\n        except ValueError:\n            return False, False\n        i = i*n + k\n    if words_len not in [12, 15, 18, 21, 24]:\n        return False, True\n    entropy = i >> checksum_length\n    checksum = i % 2**checksum_length\n    h = '{:x}'.format(entropy)\n    while len(h) < entropy_length/4:\n        h = '0'+h\n    b = bytearray.fromhex(h)\n    hashed = int(hfu(hashlib.sha256(b).digest()), 16)\n    calculated_checksum = hashed >> (256 - checksum_length)\n    return checksum == calculated_checksum, True\n\ndef from_bip39_seed(seed, passphrase, derivation):\n    k = BIP32_KeyStore({})\n    bip32_seed = bip39_to_seed(seed, passphrase)\n    xtype = xtype_from_derivation(derivation)\n    k.add_xprv_from_seed(bip32_seed, xtype, derivation)\n    return k\n\n\ndef xtype_from_derivation(derivation):\n    \"\"\"Returns the script type to be used for this derivation.\"\"\"\n    if derivation.startswith(\"m/84'\"):\n        return 'p2wpkh'\n    elif derivation.startswith(\"m/49'\"):\n        return 'p2wpkh-p2sh'\n    else:\n        return 'standard'\n\n\n# extended pubkeys\n\ndef is_xpubkey(x_pubkey):\n    return x_pubkey[0:2] == 'ff'\n\n\ndef parse_xpubkey(x_pubkey):\n    assert x_pubkey[0:2] == 'ff'\n    return BIP32_KeyStore.parse_xpubkey(x_pubkey)\n\n\ndef xpubkey_to_address(x_pubkey):\n    if x_pubkey[0:2] == 'fd':\n        address = bitcoin.script_to_address(x_pubkey[2:])\n        return x_pubkey, address\n    if x_pubkey[0:2] in ['02', '03', '04']:\n        pubkey = x_pubkey\n    elif x_pubkey[0:2] == 'ff':\n        xpub, s = BIP32_KeyStore.parse_xpubkey(x_pubkey)\n        pubkey = BIP32_KeyStore.get_pubkey_from_xpub(xpub, s)\n    elif x_pubkey[0:2] == 'fe':\n        mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)\n        pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])\n    else:\n        raise BaseException(\"Cannot parse pubkey\")\n    if pubkey:\n        address = public_key_to_p2pkh(bfh(pubkey))\n    return pubkey, address\n\ndef xpubkey_to_pubkey(x_pubkey):\n    pubkey, address = xpubkey_to_address(x_pubkey)\n    return pubkey\n\nhw_keystores = {}\n\ndef register_keystore(hw_type, constructor):\n    hw_keystores[hw_type] = constructor\n\ndef hardware_keystore(d):\n    hw_type = d['hw_type']\n    if hw_type in hw_keystores:\n        constructor = hw_keystores[hw_type]\n        return constructor(d)\n    raise BaseException('unknown hardware type', hw_type)\n\ndef load_keystore(storage, name):\n    w = storage.get('wallet_type', 'standard')\n    d = storage.get(name, {})\n    t = d.get('type')\n    if not t:\n        raise BaseException('wallet format requires update')\n    if t == 'old':\n        k = Old_KeyStore(d)\n    elif t == 'imported':\n        k = Imported_KeyStore(d)\n    elif t == 'bip32':\n        k = BIP32_KeyStore(d)\n    elif t == 'hardware':\n        k = hardware_keystore(d)\n    else:\n        raise BaseException('unknown wallet type', t)\n    return k\n\n\ndef is_old_mpk(mpk):\n    try:\n        int(mpk, 16)\n    except:\n        return False\n    return len(mpk) == 128\n\n\ndef is_address_list(text):\n    parts = text.split()\n    return bool(parts) and all(bitcoin.is_address(x) for x in parts)\n\n\ndef get_private_keys(text):\n    parts = text.split('\\n')\n    parts = map(lambda x: ''.join(x.split()), parts)\n    parts = list(filter(bool, parts))\n    if bool(parts) and all(bitcoin.is_private_key(x) for x in parts):\n        return parts\n\n\ndef is_private_key_list(text):\n    return bool(get_private_keys(text))\n\n\nis_mpk = lambda x: is_old_mpk(x) or is_xpub(x)\nis_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)\nis_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x)\nis_private_key = lambda x: is_xprv(x) or is_private_key_list(x)\nis_bip32_key = lambda x: is_xprv(x) or is_xpub(x)\n\n\ndef bip44_derivation(account_id, bip43_purpose=44):\n    coin = 1 if bitcoin.NetworkConstants.TESTNET else 183 # BTCP - SLIP0044\n    return \"m/%d'/%d'/%d'\" % (bip43_purpose, coin, int(account_id))\n\ndef from_seed(seed, passphrase, is_p2sh):\n    t = seed_type(seed)\n    if t == 'old':\n        keystore = Old_KeyStore({})\n        keystore.add_seed(seed)\n    elif t in ['standard', 'segwit']:\n        keystore = BIP32_KeyStore({})\n        keystore.add_seed(seed)\n        keystore.passphrase = passphrase\n        bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)\n        if t == 'standard':\n            der = \"m/\"\n            xtype = 'standard'\n        else:\n            der = \"m/1'/\" if is_p2sh else \"m/0'/\"\n            xtype = 'p2wsh' if is_p2sh else 'p2wpkh'\n        keystore.add_xprv_from_seed(bip32_seed, xtype, der)\n    else:\n        raise BaseException(t)\n    return keystore\n\ndef from_private_key_list(text):\n    keystore = Imported_KeyStore({})\n    for x in get_private_keys(text):\n        keystore.import_key(x, None)\n    return keystore\n\ndef from_old_mpk(mpk):\n    keystore = Old_KeyStore({})\n    keystore.add_master_public_key(mpk)\n    return keystore\n\ndef from_xpub(xpub):\n    k = BIP32_KeyStore({})\n    k.xpub = xpub\n    return k\n\ndef from_xprv(xprv):\n    xpub = bitcoin.xpub_from_xprv(xprv)\n    k = BIP32_KeyStore({})\n    k.xprv = xprv\n    k.xpub = xpub\n    return k\n\ndef from_master_key(text):\n    if is_xprv(text):\n        k = from_xprv(text)\n    elif is_old_mpk(text):\n        k = from_old_mpk(text)\n    elif is_xpub(text):\n        k = from_xpub(text)\n    else:\n        raise BaseException('Invalid key')\n    return k\n"
  },
  {
    "path": "lib/mnemonic.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport os\nimport hmac\nimport math\nimport hashlib\nimport unicodedata\nimport string\n\nimport ecdsa\nimport pbkdf2\n\nfrom .util import print_error\nfrom .bitcoin import is_old_seed, is_new_seed\nfrom . import version\n\n# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html\nCJK_INTERVALS = [\n    (0x4E00, 0x9FFF, 'CJK Unified Ideographs'),\n    (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'),\n    (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'),\n    (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'),\n    (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'),\n    (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'),\n    (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'),\n    (0x3190, 0x319F , 'Kanbun'),\n    (0x2E80, 0x2EFF, 'CJK Radicals Supplement'),\n    (0x2F00, 0x2FDF, 'CJK Radicals'),\n    (0x31C0, 0x31EF, 'CJK Strokes'),\n    (0x2FF0, 0x2FFF, 'Ideographic Description Characters'),\n    (0xE0100, 0xE01EF, 'Variation Selectors Supplement'),\n    (0x3100, 0x312F, 'Bopomofo'),\n    (0x31A0, 0x31BF, 'Bopomofo Extended'),\n    (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'),\n    (0x3040, 0x309F, 'Hiragana'),\n    (0x30A0, 0x30FF, 'Katakana'),\n    (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'),\n    (0x1B000, 0x1B0FF, 'Kana Supplement'),\n    (0xAC00, 0xD7AF, 'Hangul Syllables'),\n    (0x1100, 0x11FF, 'Hangul Jamo'),\n    (0xA960, 0xA97F, 'Hangul Jamo Extended A'),\n    (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'),\n    (0x3130, 0x318F, 'Hangul Compatibility Jamo'),\n    (0xA4D0, 0xA4FF, 'Lisu'),\n    (0x16F00, 0x16F9F, 'Miao'),\n    (0xA000, 0xA48F, 'Yi Syllables'),\n    (0xA490, 0xA4CF, 'Yi Radicals'),\n]\n\ndef is_CJK(c):\n    n = ord(c)\n    for imin,imax,name in CJK_INTERVALS:\n        if n>=imin and n<=imax: return True\n    return False\n\n\ndef normalize_text(seed):\n    # normalize\n    seed = unicodedata.normalize('NFKD', seed)\n    # lower\n    seed = seed.lower()\n    # remove accents\n    seed = u''.join([c for c in seed if not unicodedata.combining(c)])\n    # normalize whitespaces\n    seed = u' '.join(seed.split())\n    # remove whitespaces between CJK\n    seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in string.whitespace and is_CJK(seed[i-1]) and is_CJK(seed[i+1]))])\n    return seed\n\ndef load_wordlist(filename):\n    path = os.path.join(os.path.dirname(__file__), 'wordlist', filename)\n    with open(path, 'r') as f:\n        s = f.read().strip()\n    s = unicodedata.normalize('NFKD', s)\n    lines = s.split('\\n')\n    wordlist = []\n    for line in lines:\n        line = line.split('#')[0]\n        line = line.strip(' \\r')\n        assert ' ' not in line\n        if line:\n            wordlist.append(line)\n    return wordlist\n\n\nfilenames = {\n    'en':'english.txt',\n    'es':'spanish.txt',\n    'ja':'japanese.txt',\n    'pt':'portuguese.txt',\n    'zh':'chinese_simplified.txt'\n}\n\n\n\nclass Mnemonic(object):\n    # Seed derivation no longer follows BIP39\n    # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum\n\n    def __init__(self, lang=None):\n        lang = lang or 'en'\n        print_error('language', lang)\n        filename = filenames.get(lang[0:2], 'english.txt')\n        self.wordlist = load_wordlist(filename)\n        print_error(\"wordlist has %d words\"%len(self.wordlist))\n\n    @classmethod\n    def mnemonic_to_seed(self, mnemonic, passphrase):\n        PBKDF2_ROUNDS = 2048\n        mnemonic = normalize_text(mnemonic)\n        passphrase = normalize_text(passphrase)\n        return pbkdf2.PBKDF2(mnemonic, 'electrum' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)\n\n    def mnemonic_encode(self, i):\n        n = len(self.wordlist)\n        words = []\n        while i:\n            x = i%n\n            i = i//n\n            words.append(self.wordlist[x])\n        return ' '.join(words)\n\n    def get_suggestions(self, prefix):\n        for w in self.wordlist:\n            if w.startswith(prefix):\n                yield w\n\n    def mnemonic_decode(self, seed):\n        n = len(self.wordlist)\n        words = seed.split()\n        i = 0\n        while words:\n            w = words.pop()\n            k = self.wordlist.index(w)\n            i = i*n + k\n        return i\n\n    def check_seed(self, seed, custom_entropy):\n        assert is_new_seed(seed)\n        i = self.mnemonic_decode(seed)\n        return i % custom_entropy == 0\n\n    def make_seed(self, seed_type='standard', num_bits=132, custom_entropy=1):\n        prefix = version.seed_prefix(seed_type)\n        # increase num_bits in order to obtain a uniform distibution for the last word\n        bpw = math.log(len(self.wordlist), 2)\n        num_bits = int(math.ceil(num_bits/bpw) * bpw)\n        # handle custom entropy; make sure we add at least 16 bits\n        n_custom = int(math.ceil(math.log(custom_entropy, 2)))\n        n = max(16, num_bits - n_custom)\n        print_error(\"make_seed\", prefix, \"adding %d bits\"%n)\n        my_entropy = 1\n        while my_entropy < pow(2, n - bpw):\n            # try again if seed would not contain enough words\n            my_entropy = ecdsa.util.randrange(pow(2, n))\n        nonce = 0\n        while True:\n            nonce += 1\n            i = custom_entropy * (my_entropy + nonce)\n            seed = self.mnemonic_encode(i)\n            assert i == self.mnemonic_decode(seed)\n            if is_old_seed(seed):\n                continue\n            if is_new_seed(seed, prefix):\n                break\n        print_error('%d words'%len(seed.split()))\n        return seed\n"
  },
  {
    "path": "lib/msqr.py",
    "content": "# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/\n\ndef modular_sqrt(a, p):\n    \"\"\" Find a quadratic residue (mod p) of 'a'. p\n    must be an odd prime.\n\n    Solve the congruence of the form:\n    x^2 = a (mod p)\n    And returns x. Note that p - x is also a root.\n\n    0 is returned is no square root exists for\n    these a and p.\n\n    The Tonelli-Shanks algorithm is used (except\n    for some simple cases in which the solution\n    is known from an identity). This algorithm\n    runs in polynomial time (unless the\n    generalized Riemann hypothesis is false).\n    \"\"\"\n    # Simple cases\n    #\n    if legendre_symbol(a, p) != 1:\n        return 0\n    elif a == 0:\n        return 0\n    elif p == 2:\n        return p\n    elif p % 4 == 3:\n        return pow(a, (p + 1) // 4, p)\n\n    # Partition p-1 to s * 2^e for an odd s (i.e.\n    # reduce all the powers of 2 from p-1)\n    #\n    s = p - 1\n    e = 0\n    while s % 2 == 0:\n        s //= 2\n        e += 1\n\n    # Find some 'n' with a legendre symbol n|p = -1.\n    # Shouldn't take long.\n    #\n    n = 2\n    while legendre_symbol(n, p) != -1:\n        n += 1\n\n    # Here be dragons!\n    # Read the paper \"Square roots from 1; 24, 51,\n    # 10 to Dan Shanks\" by Ezra Brown for more\n    # information\n    #\n\n    # x is a guess of the square root that gets better\n    # with each iteration.\n    # b is the \"fudge factor\" - by how much we're off\n    # with the guess. The invariant x^2 = ab (mod p)\n    # is maintained throughout the loop.\n    # g is used for successive powers of n to update\n    # both a and b\n    # r is the exponent - decreases with each update\n    #\n    x = pow(a, (s + 1) // 2, p)\n    b = pow(a, s, p)\n    g = pow(n, s, p)\n    r = e\n\n    while True:\n        t = b\n        m = 0\n        for m in range(r):\n            if t == 1:\n                break\n            t = pow(t, 2, p)\n\n        if m == 0:\n            return x\n\n        gs = pow(g, 2 ** (r - m - 1), p)\n        g = (gs * gs) % p\n        x = (x * gs) % p\n        b = (b * g) % p\n        r = m\n\ndef legendre_symbol(a, p):\n    \"\"\" Compute the Legendre symbol a|p using\n    Euler's criterion. p is a prime, a is\n    relatively prime to p (if p divides\n    a, then a|p = 0)\n\n    Returns 1 if a has a square root modulo\n    p, -1 otherwise.\n    \"\"\"\n    ls = pow(a, (p - 1) // 2, p)\n    return -1 if ls == p - 1 else ls\n"
  },
  {
    "path": "lib/network.py",
    "content": "# Electrum - Lightweight Bitcoin Client\n# Copyright (c) 2011-2016 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport time\nimport queue\nimport os\nimport stat\nimport errno\nimport random\nimport re\nimport select\nfrom collections import defaultdict\nimport threading\nimport socket\nimport json\n\nimport socks\nfrom . import util\nfrom . import bitcoin\nfrom .bitcoin import *\nfrom .interface import Connection, Interface\nfrom . import blockchain\nfrom .version import ELECTRUM_VERSION, PROTOCOL_VERSION\n\n\nNODES_RETRY_INTERVAL = 60\nSERVER_RETRY_INTERVAL = 10\n\n\ndef parse_servers(result):\n    \"\"\" parse servers list into dict format\"\"\"\n    from .version import PROTOCOL_VERSION\n    servers = {}\n    for item in result:\n        host = item[1]\n        out = {}\n        version = None\n        pruning_level = '-'\n        if len(item) > 2:\n            for v in item[2]:\n                if re.match(\"[st]\\d*\", v):\n                    protocol, port = v[0], v[1:]\n                    if port == '': port = NetworkConstants.DEFAULT_PORTS[protocol]\n                    out[protocol] = port\n                elif re.match(\"v(.?)+\", v):\n                    version = v[1:]\n                elif re.match(\"p\\d*\", v):\n                    pruning_level = v[1:]\n                if pruning_level == '': pruning_level = '0'\n        if out:\n            out['pruning'] = pruning_level\n            out['version'] = version\n            servers[host] = out\n    return servers\n\ndef filter_version(servers):\n    def is_recent(version):\n        try:\n            return util.normalize_version(version) >= util.normalize_version(PROTOCOL_VERSION)\n        except Exception as e:\n            return False\n    return {k: v for k, v in servers.items() if is_recent(v.get('version'))}\n\n\ndef filter_protocol(hostmap, protocol = 's'):\n    '''Filters the hostmap for those implementing protocol.\n    The result is a list in serialized form.'''\n    eligible = []\n    for host, portmap in hostmap.items():\n        port = portmap.get(protocol)\n        if port:\n            eligible.append(serialize_server(host, port, protocol))\n    return eligible\n\ndef pick_random_server(hostmap = None, protocol = 's', exclude_set = set()):\n    if hostmap is None:\n        hostmap = NetworkConstants.DEFAULT_SERVERS\n    eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set)\n    return random.choice(eligible) if eligible else None\n\nfrom .simple_config import SimpleConfig\n\nproxy_modes = ['socks4', 'socks5', 'http']\n\n\ndef serialize_proxy(p):\n    if not isinstance(p, dict):\n        return None\n    return ':'.join([p.get('mode'), p.get('host'), p.get('port'),\n                     p.get('user', ''), p.get('password', '')])\n\n\ndef deserialize_proxy(s):\n    if not isinstance(s, str):\n        return None\n    if s.lower() == 'none':\n        return None\n    proxy = { \"mode\":\"socks5\", \"host\":\"localhost\" }\n    args = s.split(':')\n    n = 0\n    if proxy_modes.count(args[n]) == 1:\n        proxy[\"mode\"] = args[n]\n        n += 1\n    if len(args) > n:\n        proxy[\"host\"] = args[n]\n        n += 1\n    if len(args) > n:\n        proxy[\"port\"] = args[n]\n        n += 1\n    else:\n        proxy[\"port\"] = \"8080\" if proxy[\"mode\"] == \"http\" else \"1080\"\n    if len(args) > n:\n        proxy[\"user\"] = args[n]\n        n += 1\n    if len(args) > n:\n        proxy[\"password\"] = args[n]\n    return proxy\n\n\ndef deserialize_server(server_str):\n    host, port, protocol = str(server_str).split(':')\n    assert protocol in 'st'\n    int(port)    # Throw if cannot be converted to int\n    return host, port, protocol\n\n\ndef serialize_server(host, port, protocol):\n    return str(':'.join([host, port, protocol]))\n\n\nclass Network(util.DaemonThread):\n    \"\"\"The Network class manages a set of connections to remote electrum\n    servers, each connected socket is handled by an Interface() object.\n    Connections are initiated by a Connection() thread which stops once\n    the connection succeeds or fails.\n\n    Our external API:\n\n    - Member functions get_header(), get_interfaces(), get_local_height(),\n          get_parameters(), get_server_height(), get_status_value(),\n          is_connected(), set_parameters(), stop()\n    \"\"\"\n\n    def __init__(self, config=None):\n        if config is None:\n            config = {}  # Do not use mutables as default values!\n        util.DaemonThread.__init__(self)\n        self.config = SimpleConfig(config) if isinstance(config, dict) else config\n        self.num_server = 10 if not self.config.get('oneserver') else 0\n        self.blockchains = blockchain.read_blockchains(self.config)\n        self.print_error(\"blockchains\", self.blockchains.keys())\n        self.blockchain_index = config.get('blockchain_index', 0)\n        if self.blockchain_index not in self.blockchains.keys():\n            self.blockchain_index = 0\n        self.protocol = 't' if self.config.get('nossl') else 's'\n        # Server for addresses and transactions\n        self.default_server = self.config.get('server')\n        # Sanitize default server\n        try:\n            host, port, protocol = deserialize_server(self.default_server)\n            assert protocol == self.protocol\n        except:\n            self.default_server = None\n        if not self.default_server:\n            self.default_server = pick_random_server(protocol=self.protocol)\n        self.lock = threading.Lock()\n        self.pending_sends = []\n        self.message_id = 0\n        self.debug = False\n        self.irc_servers = {} # returned by interface (list from irc)\n        self.recent_servers = self.read_recent_servers()\n\n        self.banner = ''\n        self.donation_address = ''\n        self.relay_fee = None\n        # callbacks passed with subscriptions\n        self.subscriptions = defaultdict(list)\n        self.sub_cache = {}\n        # callbacks set by the GUI\n        self.callbacks = defaultdict(list)\n\n        dir_path = os.path.join( self.config.path, 'certs')\n        if not os.path.exists(dir_path):\n            os.mkdir(dir_path)\n            os.chmod(dir_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)\n\n        # subscriptions and requests\n        self.subscribed_addresses = set()\n        self.h2addr = {}\n        # Requests from client we've not seen a response to\n        self.unanswered_requests = {}\n        # retry times\n        self.server_retry_time = time.time()\n        self.nodes_retry_time = time.time()\n        # kick off the network.  interface is the main server we are currently\n        # communicating with.  interfaces is the set of servers we are connecting\n        # to or have an ongoing connection with\n        self.interface = None\n        self.interfaces = {}\n        self.auto_connect = self.config.get('auto_connect', True)\n        self.connecting = set()\n        self.requested_chunks = set()\n        self.socket_queue = queue.Queue()\n        self.start_network(self.protocol, deserialize_proxy(self.config.get('proxy')))\n\n    def register_callback(self, callback, events):\n        with self.lock:\n            for event in events:\n                self.callbacks[event].append(callback)\n\n    def unregister_callback(self, callback):\n        with self.lock:\n            for callbacks in self.callbacks.values():\n                if callback in callbacks:\n                    callbacks.remove(callback)\n\n    def trigger_callback(self, event, *args):\n        with self.lock:\n            callbacks = self.callbacks[event][:]\n        [callback(event, *args) for callback in callbacks]\n\n    def read_recent_servers(self):\n        if not self.config.path:\n            return []\n        path = os.path.join(self.config.path, \"recent_servers\")\n        try:\n            with open(path, \"r\") as f:\n                data = f.read()\n                return json.loads(data)\n        except:\n            return []\n\n    def save_recent_servers(self):\n        if not self.config.path:\n            return\n        path = os.path.join(self.config.path, \"recent_servers\")\n        s = json.dumps(self.recent_servers, indent=4, sort_keys=True)\n        try:\n            with open(path, \"w\") as f:\n                f.write(s)\n        except:\n            pass\n\n    def get_server_height(self):\n        return self.interface.tip if self.interface else 0\n\n    def server_is_lagging(self):\n        sh = self.get_server_height()\n        if not sh:\n            self.print_error('no height for main interface')\n            return True\n        lh = self.get_local_height()\n        result = (lh - sh) > 1\n        if result:\n            self.print_error('%s is lagging (%d vs %d)' % (self.default_server, sh, lh))\n        return result\n\n    def set_status(self, status):\n        self.connection_status = status\n        self.notify('status')\n\n    def is_connected(self):\n        return self.interface is not None\n\n    def is_connecting(self):\n        return self.connection_status == 'connecting'\n\n    def is_up_to_date(self):\n        return self.unanswered_requests == {}\n\n    def queue_request(self, method, params, interface=None):\n        # If you want to queue a request on any interface it must go\n        # through this function so message ids are properly tracked\n        if interface is None:\n            interface = self.interface\n        message_id = self.message_id\n        self.message_id += 1\n        if self.debug:\n            self.print_error(interface.host, \"-->\", method, params, message_id)\n        interface.queue_request(method, params, message_id)\n        return message_id\n\n    def send_subscriptions(self):\n        self.print_error('sending subscriptions to', self.interface.server, len(self.unanswered_requests), len(self.subscribed_addresses))\n        self.sub_cache.clear()\n        # Resend unanswered requests\n        requests = self.unanswered_requests.values()\n        self.unanswered_requests = {}\n        for request in requests:\n            message_id = self.queue_request(request[0], request[1])\n            self.unanswered_requests[message_id] = request\n        self.queue_request('server.banner', [])\n        self.queue_request('server.donation_address', [])\n        self.queue_request('server.peers.subscribe', [])\n        self.request_fee_estimates()\n        self.queue_request('blockchain.relayfee', [])\n        if self.interface.ping_required():\n            params = [ELECTRUM_VERSION, PROTOCOL_VERSION]\n            self.queue_request('server.version', params, self.interface)\n        for h in self.subscribed_addresses:\n            self.queue_request('blockchain.scripthash.subscribe', [h])\n\n    def request_fee_estimates(self):\n        self.config.requested_fee_estimates()\n        for i in bitcoin.FEE_TARGETS:\n            self.queue_request('blockchain.estimatefee', [i])\n\n    def get_status_value(self, key):\n        if key == 'status':\n            value = self.connection_status\n        elif key == 'banner':\n            value = self.banner\n        elif key == 'fee':\n            value = self.config.fee_estimates\n        elif key == 'updated':\n            value = (self.get_local_height(), self.get_server_height())\n        elif key == 'servers':\n            value = self.get_servers()\n        elif key == 'interfaces':\n            value = self.get_interfaces()\n        return value\n\n    def notify(self, key):\n        if key in ['status', 'updated']:\n            self.trigger_callback(key)\n        else:\n            self.trigger_callback(key, self.get_status_value(key))\n\n    def get_parameters(self):\n        host, port, protocol = deserialize_server(self.default_server)\n        return host, port, protocol, self.proxy, self.auto_connect\n\n    def get_donation_address(self):\n        if self.is_connected():\n            return self.donation_address\n\n    def get_interfaces(self):\n        '''The interfaces that are in connected state'''\n        return list(self.interfaces.keys())\n\n    def get_servers(self):\n        out = NetworkConstants.DEFAULT_SERVERS\n        if self.irc_servers:\n            out.update(filter_version(self.irc_servers.copy()))\n        else:\n            for s in self.recent_servers:\n                try:\n                    host, port, protocol = deserialize_server(s)\n                except:\n                    continue\n                if host not in out:\n                    out[host] = { protocol:port }\n        return out\n\n    def start_interface(self, server):\n        if (not server in self.interfaces and not server in self.connecting):\n            if server == self.default_server:\n                self.print_error(\"connecting to %s as new interface\" % server)\n                self.set_status('connecting')\n            self.connecting.add(server)\n            c = Connection(server, self.socket_queue, self.config.path)\n\n    def start_random_interface(self):\n        exclude_set = self.disconnected_servers.union(set(self.interfaces))\n        server = pick_random_server(self.get_servers(), self.protocol, exclude_set)\n        if server:\n            self.start_interface(server)\n\n    def start_interfaces(self):\n        self.start_interface(self.default_server)\n        for i in range(self.num_server - 1):\n            self.start_random_interface()\n\n    def set_proxy(self, proxy):\n        self.proxy = proxy\n        # Store these somewhere so we can un-monkey-patch\n        if not hasattr(socket, \"_socketobject\"):\n            socket._socketobject = socket.socket\n            socket._getaddrinfo = socket.getaddrinfo\n        if proxy:\n            self.print_error('setting proxy', proxy)\n            proxy_mode = proxy_modes.index(proxy[\"mode\"]) + 1\n            socks.setdefaultproxy(proxy_mode,\n                                  proxy[\"host\"],\n                                  int(proxy[\"port\"]),\n                                  # socks.py seems to want either None or a non-empty string\n                                  username=(proxy.get(\"user\", \"\") or None),\n                                  password=(proxy.get(\"password\", \"\") or None))\n            socket.socket = socks.socksocket\n            # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy\n            socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]\n        else:\n            socket.socket = socket._socketobject\n            socket.getaddrinfo = socket._getaddrinfo\n\n    def start_network(self, protocol, proxy):\n        assert not self.interface and not self.interfaces\n        assert not self.connecting and self.socket_queue.empty()\n        self.print_error('starting network')\n        self.disconnected_servers = set([])\n        self.protocol = protocol\n        self.set_proxy(proxy)\n        self.start_interfaces()\n\n    def stop_network(self):\n        self.print_error(\"stopping network\")\n        for interface in list(self.interfaces.values()):\n            self.close_interface(interface)\n        if self.interface:\n            self.close_interface(self.interface)\n        assert self.interface is None\n        assert not self.interfaces\n        self.connecting = set()\n        # Get a new queue - no old pending connections thanks!\n        self.socket_queue = queue.Queue()\n\n    def set_parameters(self, host, port, protocol, proxy, auto_connect):\n        proxy_str = serialize_proxy(proxy)\n        server = serialize_server(host, port, protocol)\n        # sanitize parameters\n        try:\n            deserialize_server(serialize_server(host, port, protocol))\n            if proxy:\n                proxy_modes.index(proxy[\"mode\"]) + 1\n                int(proxy['port'])\n        except:\n            return\n        self.config.set_key('auto_connect', auto_connect, False)\n        self.config.set_key(\"proxy\", proxy_str, False)\n        self.config.set_key(\"server\", server, True)\n        # abort if changes were not allowed by config\n        if self.config.get('server') != server or self.config.get('proxy') != proxy_str:\n            return\n        self.auto_connect = auto_connect\n        if self.proxy != proxy or self.protocol != protocol:\n            # Restart the network defaulting to the given server\n            self.stop_network()\n            self.default_server = server\n            self.start_network(protocol, proxy)\n        elif self.default_server != server:\n            self.switch_to_interface(server)\n        else:\n            self.switch_lagging_interface()\n            self.notify('updated')\n\n    def switch_to_random_interface(self):\n        '''Switch to a random connected server other than the current one'''\n        servers = self.get_interfaces()    # Those in connected state\n        if self.default_server in servers:\n            servers.remove(self.default_server)\n        if servers:\n            self.switch_to_interface(random.choice(servers))\n\n    def switch_lagging_interface(self):\n        '''If auto_connect and lagging, switch interface'''\n        if self.server_is_lagging() and self.auto_connect:\n            # switch to one that has the correct header (not height)\n            header = self.blockchain().read_header(self.get_local_height())\n            filtered = list(map(lambda x:x[0], filter(lambda x: x[1].tip_header==header, self.interfaces.items())))\n            if filtered:\n                choice = random.choice(filtered)\n                self.switch_to_interface(choice)\n\n    def switch_to_interface(self, server):\n        '''Switch to server as our interface.  If no connection exists nor\n        being opened, start a thread to connect.  The actual switch will\n        happen on receipt of the connection notification.  Do nothing\n        if server already is our interface.'''\n        self.default_server = server\n        if server not in self.interfaces:\n            self.interface = None\n            self.start_interface(server)\n            return\n        i = self.interfaces[server]\n        if self.interface != i:\n            self.print_error(\"switching to\", server)\n            # stop any current interface in order to terminate subscriptions\n            # fixme: we don't want to close headers sub\n            #self.close_interface(self.interface)\n            self.interface = i\n            self.send_subscriptions()\n            self.set_status('connected')\n            self.notify('updated')\n\n    def close_interface(self, interface):\n        if interface:\n            if interface.server in self.interfaces:\n                self.interfaces.pop(interface.server)\n            if interface.server == self.default_server:\n                self.interface = None\n            interface.close()\n\n    def add_recent_server(self, server):\n        # list is ordered\n        if server in self.recent_servers:\n            self.recent_servers.remove(server)\n        self.recent_servers.insert(0, server)\n        self.recent_servers = self.recent_servers[0:20]\n        self.save_recent_servers()\n\n    def process_response(self, interface, response, callbacks):\n        if self.debug:\n            self.print_error(\"<--\", response)\n        error = response.get('error')\n        result = response.get('result')\n        method = response.get('method')\n        params = response.get('params')\n\n        # We handle some responses; return the rest to the client.\n        if method == 'server.version':\n            interface.server_version = result\n        elif method == 'blockchain.headers.subscribe':\n            if error is None:\n                self.on_notify_header(interface, result)\n        elif method == 'server.peers.subscribe':\n            if error is None:\n                self.irc_servers = parse_servers(result)\n                self.notify('servers')\n        elif method == 'server.banner':\n            if error is None:\n                self.banner = result\n                self.notify('banner')\n        elif method == 'server.donation_address':\n            if error is None:\n                self.donation_address = result\n        elif method == 'blockchain.estimatefee':\n            if error is None and result > 0:\n                i = params[0]\n                fee = int(result*COIN)\n                self.config.update_fee_estimates(i, fee)\n                self.print_error(\"fee_estimates[%d]\" % i, fee)\n                self.notify('fee')\n        elif method == 'blockchain.relayfee':\n            if error is None:\n                self.relay_fee = int(result * COIN)\n                self.print_error(\"relayfee\", self.relay_fee)\n        elif method == 'blockchain.block.get_chunk':\n            self.on_get_chunk(interface, response)\n        elif method == 'blockchain.block.get_header':\n            self.on_get_header(interface, response)\n\n        for callback in callbacks:\n            callback(response)\n\n    def get_index(self, method, params):\n        \"\"\" hashable index for subscriptions and cache\"\"\"\n        return str(method) + (':' + str(params[0]) if params else '')\n\n    def process_responses(self, interface):\n        responses = interface.get_responses()\n        for request, response in responses:\n            if request:\n                method, params, message_id = request\n                k = self.get_index(method, params)\n                # client requests go through self.send() with a\n                # callback, are only sent to the current interface,\n                # and are placed in the unanswered_requests dictionary\n                client_req = self.unanswered_requests.pop(message_id, None)\n                if client_req:\n                    assert interface == self.interface\n                    callbacks = [client_req[2]]\n                else:\n                    # fixme: will only work for subscriptions\n                    k = self.get_index(method, params)\n                    callbacks = self.subscriptions.get(k, [])\n\n                # Copy the request method and params to the response\n                response['method'] = method\n                response['params'] = params\n                # Only once we've received a response to an addr subscription\n                # add it to the list; avoids double-sends on reconnection\n                if method == 'blockchain.scripthash.subscribe':\n                    self.subscribed_addresses.add(params[0])\n            else:\n                if not response:  # Closed remotely / misbehaving\n                    self.connection_down(interface.server)\n                    break\n                # Rewrite response shape to match subscription request response\n                method = response.get('method')\n                params = response.get('params')\n                k = self.get_index(method, params)\n                if method == 'blockchain.headers.subscribe':\n                    response['result'] = params[0]\n                    response['params'] = []\n                elif method == 'blockchain.scripthash.subscribe':\n                    response['params'] = [params[0]]  # addr\n                    response['result'] = params[1]\n                callbacks = self.subscriptions.get(k, [])\n\n            # update cache if it's a subscription\n            if method.endswith('.subscribe'):\n                self.sub_cache[k] = response\n            # Response is now in canonical form\n            self.process_response(interface, response, callbacks)\n\n    def addr_to_scripthash(self, addr):\n        h = bitcoin.address_to_scripthash(addr)\n        if h not in self.h2addr:\n            self.h2addr[h] = addr\n        return h\n\n    def overload_cb(self, callback):\n        def cb2(x):\n            x2 = x.copy()\n            p = x2.pop('params')\n            addr = self.h2addr[p[0]]\n            x2['params'] = [addr]\n            callback(x2)\n        return cb2\n\n    def subscribe_to_addresses(self, addresses, callback):\n        hashes = [self.addr_to_scripthash(addr) for addr in addresses]\n        msgs = [('blockchain.scripthash.subscribe', [x]) for x in hashes]\n        self.send(msgs, self.overload_cb(callback))\n\n    def request_address_history(self, address, callback):\n        h = self.addr_to_scripthash(address)\n        self.send([('blockchain.scripthash.get_history', [h])], self.overload_cb(callback))\n\n    def send(self, messages, callback):\n        '''Messages is a list of (method, params) tuples'''\n        messages = list(messages)\n        with self.lock:\n            self.pending_sends.append((messages, callback))\n\n    def process_pending_sends(self):\n        # Requests needs connectivity.  If we don't have an interface,\n        # we cannot process them.\n        if not self.interface:\n            return\n\n        with self.lock:\n            sends = self.pending_sends\n            self.pending_sends = []\n\n        for messages, callback in sends:\n            for method, params in messages:\n                r = None\n                if method.endswith('.subscribe'):\n                    k = self.get_index(method, params)\n                    # add callback to list\n                    l = self.subscriptions.get(k, [])\n                    if callback not in l:\n                        l.append(callback)\n                    self.subscriptions[k] = l\n                    # check cached response for subscriptions\n                    r = self.sub_cache.get(k)\n                if r is not None:\n                    util.print_error(\"cache hit\", k)\n                    callback(r)\n                else:\n                    message_id = self.queue_request(method, params)\n                    self.unanswered_requests[message_id] = method, params, callback\n\n    def unsubscribe(self, callback):\n        '''Unsubscribe a callback to free object references to enable GC.'''\n        # Note: we can't unsubscribe from the server, so if we receive\n        # subsequent notifications process_response() will emit a harmless\n        # \"received unexpected notification\" warning\n        with self.lock:\n            for v in self.subscriptions.values():\n                if callback in v:\n                    v.remove(callback)\n\n    def connection_down(self, server):\n        '''A connection to server either went down, or was never made.\n        We distinguish by whether it is in self.interfaces.'''\n        self.disconnected_servers.add(server)\n        if server == self.default_server:\n            self.set_status('disconnected')\n        if server in self.interfaces:\n            self.close_interface(self.interfaces[server])\n            self.notify('interfaces')\n        for b in self.blockchains.values():\n            if b.catch_up == server:\n                b.catch_up = None\n\n    def new_interface(self, server, socket):\n        # todo: get tip first, then decide which checkpoint to use.\n        self.add_recent_server(server)\n        interface = Interface(server, socket)\n        interface.blockchain = None\n        interface.tip_header = None\n        interface.tip = 0\n        interface.mode = 'default'\n        interface.request = None\n        self.interfaces[server] = interface\n        self.queue_request('blockchain.headers.subscribe', [], interface)\n        if server == self.default_server:\n            self.switch_to_interface(server)\n        #self.notify('interfaces')\n\n    def maintain_sockets(self):\n        '''Socket maintenance.'''\n        # Responses to connection attempts?\n        while not self.socket_queue.empty():\n            server, socket = self.socket_queue.get()\n            if server in self.connecting:\n                self.connecting.remove(server)\n            if socket:\n                self.new_interface(server, socket)\n            else:\n                self.connection_down(server)\n\n        # Send pings and shut down stale interfaces\n        # must use copy of values\n        for interface in list(self.interfaces.values()):\n            if interface.has_timed_out():\n                self.connection_down(interface.server)\n            elif interface.ping_required():\n                params = [ELECTRUM_VERSION, PROTOCOL_VERSION]\n                self.queue_request('server.version', params, interface)\n\n        now = time.time()\n        # nodes\n        if len(self.interfaces) + len(self.connecting) < self.num_server:\n            self.start_random_interface()\n            if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:\n                self.print_error('network: retrying connections')\n                self.disconnected_servers = set([])\n                self.nodes_retry_time = now\n\n        # main interface\n        if not self.is_connected():\n            if self.auto_connect:\n                if not self.is_connecting():\n                    self.switch_to_random_interface()\n            else:\n                if self.default_server in self.disconnected_servers:\n                    if now - self.server_retry_time > SERVER_RETRY_INTERVAL:\n                        self.disconnected_servers.remove(self.default_server)\n                        self.server_retry_time = now\n                else:\n                    self.switch_to_interface(self.default_server)\n        else:\n            if self.config.is_fee_estimates_update_required():\n                self.request_fee_estimates()\n\n    def request_chunk(self, interface, index):\n        if index in self.requested_chunks:\n            return\n        interface.print_error(\"requesting chunk %d\" % index)\n        self.requested_chunks.add(index)\n        self.queue_request('blockchain.block.get_chunk', [index], interface)\n\n    def on_get_chunk(self, interface, response):\n        '''Handle receiving a chunk of block headers'''\n        error = response.get('error')\n        result = response.get('result')\n        params = response.get('params')\n        if result is None or params is None or error is not None:\n            interface.print_error(error or 'bad response')\n            return\n        index = params[0]\n        # Ignore unsolicited chunks\n        if index not in self.requested_chunks:\n            return\n        self.requested_chunks.remove(index)\n        connect = interface.blockchain.connect_chunk(index, result)\n        if not connect:\n            self.connection_down(interface.server)\n            return\n        # If not finished, get the next chunk\n        if interface.blockchain.height() < interface.tip:\n            self.request_chunk(interface, index+1)\n        else:\n            interface.mode = 'default'\n            interface.print_error('catch up done', interface.blockchain.height())\n            interface.blockchain.catch_up = None\n        self.notify('updated')\n\n    def request_header(self, interface, height):\n        interface.print_error(\"requesting header %d\" % height)\n        self.queue_request('blockchain.block.get_header', [height], interface)\n        interface.request = height\n        interface.req_time = time.time()\n\n    def on_get_header(self, interface, response):\n        '''Handle receiving a single block header'''\n        header = response.get('result')\n        if not header:\n            interface.print_error(response)\n            self.connection_down(interface.server)\n            return\n        height = header.get('block_height')\n        if int(interface.request) != height:\n            interface.print_error(\"unsolicited header\",interface.request, height)\n            self.connection_down(interface.server)\n            return\n        interface.print_error(\"interface.mode %s\" % interface.mode)\n        chain = blockchain.check_header(header)\n        if interface.mode == 'backward':\n            can_connect = blockchain.can_connect(header)\n            if can_connect and can_connect.catch_up is None:\n                interface.mode = 'catch_up'\n                interface.blockchain = can_connect\n                interface.blockchain.save_header(header)\n                next_height = height + 1\n                interface.blockchain.catch_up = interface.server\n            elif chain:\n                interface.print_error(\"binary search\")\n                interface.mode = 'binary'\n                interface.blockchain = chain\n                interface.good = height\n                next_height = (interface.bad + interface.good) // 2\n            else:\n                if height == 0:\n                    self.connection_down(interface.server)\n                    next_height = None\n                else:\n                    interface.bad = height\n                    interface.bad_header = header\n                    delta = interface.tip - height\n                    next_height = max(0, interface.tip - 2 * delta)\n        elif interface.mode == 'binary':\n            if chain:\n                interface.good = height\n                interface.blockchain = chain\n            else:\n                interface.bad = height\n                interface.bad_header = header\n            if interface.bad != interface.good + 1:\n                next_height = (interface.bad + interface.good) // 2\n            elif not interface.blockchain.can_connect(interface.bad_header, check_height=False):\n                self.connection_down(interface.server)\n                next_height = None\n            else:\n                branch = self.blockchains.get(interface.bad)\n                if branch is not None:\n                    if branch.check_header(interface.bad_header):\n                        interface.print_error('joining chain', interface.bad)\n                        next_height = None\n                    elif branch.parent().check_header(header):\n                        interface.print_error('reorg', interface.bad, interface.tip)\n                        interface.blockchain = branch.parent()\n                        next_height = None\n                    else:\n                        interface.print_error('checkpoint conflicts with existing fork', branch.path())\n                        branch.write('', 0)\n                        branch.save_header(interface.bad_header)\n                        interface.mode = 'catch_up'\n                        interface.blockchain = branch\n                        next_height = interface.bad + 1\n                        interface.blockchain.catch_up = interface.server\n                else:\n                    bh = interface.blockchain.height()\n                    next_height = None\n                    if bh > interface.good:\n                        if not interface.blockchain.check_header(interface.bad_header):\n                            b = interface.blockchain.fork(interface.bad_header)\n                            self.blockchains[interface.bad] = b\n                            interface.blockchain = b\n                            interface.print_error(\"new chain\", b.checkpoint)\n                            interface.mode = 'catch_up'\n                            next_height = interface.bad + 1\n                            interface.blockchain.catch_up = interface.server\n                    else:\n                        assert bh == interface.good\n                        if interface.blockchain.catch_up is None and bh < interface.tip:\n                            interface.print_error(\"catching up from %d\"% (bh + 1))\n                            interface.mode = 'catch_up'\n                            next_height = bh + 1\n                            interface.blockchain.catch_up = interface.server\n\n                self.notify('updated')\n\n        elif interface.mode == 'catch_up':\n            can_connect = interface.blockchain.can_connect(header)\n            if can_connect:\n                interface.blockchain.save_header(header)\n                next_height = height + 1 if height < interface.tip else None\n            else:\n                # go back\n                interface.print_error(\"cannot connect\", height)\n                interface.mode = 'backward'\n                interface.bad = height\n                interface.bad_header = header\n                next_height = height - 1\n\n            if next_height is None:\n                # exit catch_up state\n                interface.print_error('catch up done', interface.blockchain.height())\n                interface.blockchain.catch_up = None\n                self.switch_lagging_interface()\n                self.notify('updated')\n\n        else:\n            raise BaseException(interface.mode)\n        # If not finished, get the next header\n        if next_height:\n            if interface.mode == 'catch_up' and interface.tip > next_height + 50:\n                self.request_chunk(interface, next_height // NetworkConstants.CHUNK_SIZE)\n            else:\n                self.request_header(interface, next_height)\n        else:\n            interface.mode = 'default'\n            interface.request = None\n            self.notify('updated')\n        # refresh network dialog\n        self.notify('interfaces')\n\n    def maintain_requests(self):\n        for interface in list(self.interfaces.values()):\n            if interface.request and time.time() - interface.request_time > 20:\n                interface.print_error(\"blockchain request timed out\")\n                self.connection_down(interface.server)\n                continue\n\n    def wait_on_sockets(self):\n        # Python docs say Windows doesn't like empty selects.\n        # Sleep to prevent busy looping\n        if not self.interfaces:\n            time.sleep(0.1)\n            return\n        rin = [i for i in self.interfaces.values()]\n        win = [i for i in self.interfaces.values() if i.num_requests()]\n        try:\n            rout, wout, xout = select.select(rin, win, [], 0.1)\n        except socket.error as e:\n            # TODO: py3, get code from e\n            code = None\n            if code == errno.EINTR:\n                return\n            raise\n        assert not xout\n        for interface in wout:\n            interface.send_requests()\n        for interface in rout:\n            self.process_responses(interface)\n\n    def init_headers_file(self):\n        b = self.blockchains[0]\n        print(b.get_hash(0), NetworkConstants.GENESIS)\n        if b.get_hash(0) == NetworkConstants.GENESIS:\n            self.downloading_headers = False\n            return\n        filename = b.path()\n        def download_thread():\n            try:\n                import urllib, socket\n                socket.setdefaulttimeout(30)\n                self.print_error(\"downloading \", NetworkConstants.HEADERS_URL)\n                urllib.request.urlretrieve(NetworkConstants.HEADERS_URL, filename)\n                self.print_error(\"done.\")\n            except Exception:\n                import traceback\n                traceback.print_exc()\n                self.print_error(\"download failed. creating file\", filename)\n                open(filename, 'wb+').close()\n            b = self.blockchains[0]\n            with b.lock: b.update_size()\n            self.downloading_headers = False\n\n        self.downloading_headers = True\n        t = threading.Thread(target = download_thread)\n        t.daemon = True\n        t.start()\n\n    def run(self):\n        self.init_headers_file()\n        while self.is_running() and self.downloading_headers:\n            time.sleep(1)\n        while self.is_running():\n            self.maintain_sockets()\n            self.wait_on_sockets()\n            self.maintain_requests()\n            self.run_jobs()    # Synchronizer and Verifier\n            self.process_pending_sends()\n        self.stop_network()\n        self.on_stop()\n\n    def on_notify_header(self, interface, header):\n        height = header.get('block_height')\n\n        if not height:\n            return\n\n        interface.tip_header = header\n        interface.tip = height\n\n        if interface.mode != 'default':\n            return\n\n        b = blockchain.check_header(header)\n\n        if b:\n            interface.blockchain = b\n            self.switch_lagging_interface()\n            self.notify('updated')\n            self.notify('interfaces')\n            return\n\n        b = blockchain.can_connect(header)\n\n        if b:\n            interface.blockchain = b\n            b.save_header(header)\n            self.switch_lagging_interface()\n            self.notify('updated')\n            self.notify('interfaces')\n            return\n\n        tip = max([x.height() for x in self.blockchains.values()])\n\n        if tip >=0:\n            interface.mode = 'backward'\n            interface.bad = height\n            interface.bad_header = header\n            self.request_header(interface, min(tip +1, height - 1))\n        else:\n            chain = self.blockchains[0]\n\n            if chain.catch_up is None:\n                chain.catch_up = interface\n                interface.mode = 'catch_up'\n                interface.blockchain = chain\n                self.print_error(\"switching to catchup mode\", tip,  self.blockchains)\n                self.request_header(interface, 0)\n            else:\n                self.print_error(\"chain already catching up with\", chain.catch_up.server)\n\n    def blockchain(self):\n        if self.interface and self.interface.blockchain is not None:\n            self.blockchain_index = self.interface.blockchain.checkpoint\n        return self.blockchains[self.blockchain_index]\n\n    def get_blockchains(self):\n        out = {}\n        for k, b in self.blockchains.items():\n            r = list(filter(lambda i: i.blockchain==b, list(self.interfaces.values())))\n            if r:\n                out[k] = r\n        return out\n\n    def follow_chain(self, index):\n        blockchain = self.blockchains.get(index)\n        if blockchain:\n            self.blockchain_index = index\n            self.config.set_key('blockchain_index', index)\n            for i in self.interfaces.values():\n                if i.blockchain == blockchain:\n                    self.switch_to_interface(i.server)\n                    break\n        else:\n            raise BaseException('blockchain not found', index)\n\n        if self.interface:\n            server = self.interface.server\n            host, port, protocol, proxy, auto_connect = self.get_parameters()\n            host, port, protocol = server.split(':')\n            self.set_parameters(host, port, protocol, proxy, auto_connect)\n\n    def get_local_height(self):\n        return self.blockchain().height()\n\n    def synchronous_get(self, request, timeout=30):\n        q = queue.Queue()\n        self.send([request], q.put)\n        try:\n            r = q.get(True, timeout)\n        except queue.Empty:\n            raise BaseException('Server did not answer')\n        if r.get('error'):\n            raise BaseException(r.get('error'))\n        return r.get('result')\n\n    def broadcast(self, tx, timeout=30):\n        tx_hash = tx.txid()\n        try:\n            out = self.synchronous_get(('blockchain.transaction.broadcast', [str(tx)]), timeout)\n        except BaseException as e:\n            return False, \"error: \" + str(e)\n        if out != tx_hash:\n            return False, \"error: \" + out\n        return True, out\n"
  },
  {
    "path": "lib/old_mnemonic.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 thomasv@gitorious\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\n# list of words from http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry\n\nwords = [\n\"like\",\n\"just\",\n\"love\",\n\"know\",\n\"never\",\n\"want\",\n\"time\",\n\"out\",\n\"there\",\n\"make\",\n\"look\",\n\"eye\",\n\"down\",\n\"only\",\n\"think\",\n\"heart\",\n\"back\",\n\"then\",\n\"into\",\n\"about\",\n\"more\",\n\"away\",\n\"still\",\n\"them\",\n\"take\",\n\"thing\",\n\"even\",\n\"through\",\n\"long\",\n\"always\",\n\"world\",\n\"too\",\n\"friend\",\n\"tell\",\n\"try\",\n\"hand\",\n\"thought\",\n\"over\",\n\"here\",\n\"other\",\n\"need\",\n\"smile\",\n\"again\",\n\"much\",\n\"cry\",\n\"been\",\n\"night\",\n\"ever\",\n\"little\",\n\"said\",\n\"end\",\n\"some\",\n\"those\",\n\"around\",\n\"mind\",\n\"people\",\n\"girl\",\n\"leave\",\n\"dream\",\n\"left\",\n\"turn\",\n\"myself\",\n\"give\",\n\"nothing\",\n\"really\",\n\"off\",\n\"before\",\n\"something\",\n\"find\",\n\"walk\",\n\"wish\",\n\"good\",\n\"once\",\n\"place\",\n\"ask\",\n\"stop\",\n\"keep\",\n\"watch\",\n\"seem\",\n\"everything\",\n\"wait\",\n\"got\",\n\"yet\",\n\"made\",\n\"remember\",\n\"start\",\n\"alone\",\n\"run\",\n\"hope\",\n\"maybe\",\n\"believe\",\n\"body\",\n\"hate\",\n\"after\",\n\"close\",\n\"talk\",\n\"stand\",\n\"own\",\n\"each\",\n\"hurt\",\n\"help\",\n\"home\",\n\"god\",\n\"soul\",\n\"new\",\n\"many\",\n\"two\",\n\"inside\",\n\"should\",\n\"true\",\n\"first\",\n\"fear\",\n\"mean\",\n\"better\",\n\"play\",\n\"another\",\n\"gone\",\n\"change\",\n\"use\",\n\"wonder\",\n\"someone\",\n\"hair\",\n\"cold\",\n\"open\",\n\"best\",\n\"any\",\n\"behind\",\n\"happen\",\n\"water\",\n\"dark\",\n\"laugh\",\n\"stay\",\n\"forever\",\n\"name\",\n\"work\",\n\"show\",\n\"sky\",\n\"break\",\n\"came\",\n\"deep\",\n\"door\",\n\"put\",\n\"black\",\n\"together\",\n\"upon\",\n\"happy\",\n\"such\",\n\"great\",\n\"white\",\n\"matter\",\n\"fill\",\n\"past\",\n\"please\",\n\"burn\",\n\"cause\",\n\"enough\",\n\"touch\",\n\"moment\",\n\"soon\",\n\"voice\",\n\"scream\",\n\"anything\",\n\"stare\",\n\"sound\",\n\"red\",\n\"everyone\",\n\"hide\",\n\"kiss\",\n\"truth\",\n\"death\",\n\"beautiful\",\n\"mine\",\n\"blood\",\n\"broken\",\n\"very\",\n\"pass\",\n\"next\",\n\"forget\",\n\"tree\",\n\"wrong\",\n\"air\",\n\"mother\",\n\"understand\",\n\"lip\",\n\"hit\",\n\"wall\",\n\"memory\",\n\"sleep\",\n\"free\",\n\"high\",\n\"realize\",\n\"school\",\n\"might\",\n\"skin\",\n\"sweet\",\n\"perfect\",\n\"blue\",\n\"kill\",\n\"breath\",\n\"dance\",\n\"against\",\n\"fly\",\n\"between\",\n\"grow\",\n\"strong\",\n\"under\",\n\"listen\",\n\"bring\",\n\"sometimes\",\n\"speak\",\n\"pull\",\n\"person\",\n\"become\",\n\"family\",\n\"begin\",\n\"ground\",\n\"real\",\n\"small\",\n\"father\",\n\"sure\",\n\"feet\",\n\"rest\",\n\"young\",\n\"finally\",\n\"land\",\n\"across\",\n\"today\",\n\"different\",\n\"guy\",\n\"line\",\n\"fire\",\n\"reason\",\n\"reach\",\n\"second\",\n\"slowly\",\n\"write\",\n\"eat\",\n\"smell\",\n\"mouth\",\n\"step\",\n\"learn\",\n\"three\",\n\"floor\",\n\"promise\",\n\"breathe\",\n\"darkness\",\n\"push\",\n\"earth\",\n\"guess\",\n\"save\",\n\"song\",\n\"above\",\n\"along\",\n\"both\",\n\"color\",\n\"house\",\n\"almost\",\n\"sorry\",\n\"anymore\",\n\"brother\",\n\"okay\",\n\"dear\",\n\"game\",\n\"fade\",\n\"already\",\n\"apart\",\n\"warm\",\n\"beauty\",\n\"heard\",\n\"notice\",\n\"question\",\n\"shine\",\n\"began\",\n\"piece\",\n\"whole\",\n\"shadow\",\n\"secret\",\n\"street\",\n\"within\",\n\"finger\",\n\"point\",\n\"morning\",\n\"whisper\",\n\"child\",\n\"moon\",\n\"green\",\n\"story\",\n\"glass\",\n\"kid\",\n\"silence\",\n\"since\",\n\"soft\",\n\"yourself\",\n\"empty\",\n\"shall\",\n\"angel\",\n\"answer\",\n\"baby\",\n\"bright\",\n\"dad\",\n\"path\",\n\"worry\",\n\"hour\",\n\"drop\",\n\"follow\",\n\"power\",\n\"war\",\n\"half\",\n\"flow\",\n\"heaven\",\n\"act\",\n\"chance\",\n\"fact\",\n\"least\",\n\"tired\",\n\"children\",\n\"near\",\n\"quite\",\n\"afraid\",\n\"rise\",\n\"sea\",\n\"taste\",\n\"window\",\n\"cover\",\n\"nice\",\n\"trust\",\n\"lot\",\n\"sad\",\n\"cool\",\n\"force\",\n\"peace\",\n\"return\",\n\"blind\",\n\"easy\",\n\"ready\",\n\"roll\",\n\"rose\",\n\"drive\",\n\"held\",\n\"music\",\n\"beneath\",\n\"hang\",\n\"mom\",\n\"paint\",\n\"emotion\",\n\"quiet\",\n\"clear\",\n\"cloud\",\n\"few\",\n\"pretty\",\n\"bird\",\n\"outside\",\n\"paper\",\n\"picture\",\n\"front\",\n\"rock\",\n\"simple\",\n\"anyone\",\n\"meant\",\n\"reality\",\n\"road\",\n\"sense\",\n\"waste\",\n\"bit\",\n\"leaf\",\n\"thank\",\n\"happiness\",\n\"meet\",\n\"men\",\n\"smoke\",\n\"truly\",\n\"decide\",\n\"self\",\n\"age\",\n\"book\",\n\"form\",\n\"alive\",\n\"carry\",\n\"escape\",\n\"damn\",\n\"instead\",\n\"able\",\n\"ice\",\n\"minute\",\n\"throw\",\n\"catch\",\n\"leg\",\n\"ring\",\n\"course\",\n\"goodbye\",\n\"lead\",\n\"poem\",\n\"sick\",\n\"corner\",\n\"desire\",\n\"known\",\n\"problem\",\n\"remind\",\n\"shoulder\",\n\"suppose\",\n\"toward\",\n\"wave\",\n\"drink\",\n\"jump\",\n\"woman\",\n\"pretend\",\n\"sister\",\n\"week\",\n\"human\",\n\"joy\",\n\"crack\",\n\"grey\",\n\"pray\",\n\"surprise\",\n\"dry\",\n\"knee\",\n\"less\",\n\"search\",\n\"bleed\",\n\"caught\",\n\"clean\",\n\"embrace\",\n\"future\",\n\"king\",\n\"son\",\n\"sorrow\",\n\"chest\",\n\"hug\",\n\"remain\",\n\"sat\",\n\"worth\",\n\"blow\",\n\"daddy\",\n\"final\",\n\"parent\",\n\"tight\",\n\"also\",\n\"create\",\n\"lonely\",\n\"safe\",\n\"cross\",\n\"dress\",\n\"evil\",\n\"silent\",\n\"bone\",\n\"fate\",\n\"perhaps\",\n\"anger\",\n\"class\",\n\"scar\",\n\"snow\",\n\"tiny\",\n\"tonight\",\n\"continue\",\n\"control\",\n\"dog\",\n\"edge\",\n\"mirror\",\n\"month\",\n\"suddenly\",\n\"comfort\",\n\"given\",\n\"loud\",\n\"quickly\",\n\"gaze\",\n\"plan\",\n\"rush\",\n\"stone\",\n\"town\",\n\"battle\",\n\"ignore\",\n\"spirit\",\n\"stood\",\n\"stupid\",\n\"yours\",\n\"brown\",\n\"build\",\n\"dust\",\n\"hey\",\n\"kept\",\n\"pay\",\n\"phone\",\n\"twist\",\n\"although\",\n\"ball\",\n\"beyond\",\n\"hidden\",\n\"nose\",\n\"taken\",\n\"fail\",\n\"float\",\n\"pure\",\n\"somehow\",\n\"wash\",\n\"wrap\",\n\"angry\",\n\"cheek\",\n\"creature\",\n\"forgotten\",\n\"heat\",\n\"rip\",\n\"single\",\n\"space\",\n\"special\",\n\"weak\",\n\"whatever\",\n\"yell\",\n\"anyway\",\n\"blame\",\n\"job\",\n\"choose\",\n\"country\",\n\"curse\",\n\"drift\",\n\"echo\",\n\"figure\",\n\"grew\",\n\"laughter\",\n\"neck\",\n\"suffer\",\n\"worse\",\n\"yeah\",\n\"disappear\",\n\"foot\",\n\"forward\",\n\"knife\",\n\"mess\",\n\"somewhere\",\n\"stomach\",\n\"storm\",\n\"beg\",\n\"idea\",\n\"lift\",\n\"offer\",\n\"breeze\",\n\"field\",\n\"five\",\n\"often\",\n\"simply\",\n\"stuck\",\n\"win\",\n\"allow\",\n\"confuse\",\n\"enjoy\",\n\"except\",\n\"flower\",\n\"seek\",\n\"strength\",\n\"calm\",\n\"grin\",\n\"gun\",\n\"heavy\",\n\"hill\",\n\"large\",\n\"ocean\",\n\"shoe\",\n\"sigh\",\n\"straight\",\n\"summer\",\n\"tongue\",\n\"accept\",\n\"crazy\",\n\"everyday\",\n\"exist\",\n\"grass\",\n\"mistake\",\n\"sent\",\n\"shut\",\n\"surround\",\n\"table\",\n\"ache\",\n\"brain\",\n\"destroy\",\n\"heal\",\n\"nature\",\n\"shout\",\n\"sign\",\n\"stain\",\n\"choice\",\n\"doubt\",\n\"glance\",\n\"glow\",\n\"mountain\",\n\"queen\",\n\"stranger\",\n\"throat\",\n\"tomorrow\",\n\"city\",\n\"either\",\n\"fish\",\n\"flame\",\n\"rather\",\n\"shape\",\n\"spin\",\n\"spread\",\n\"ash\",\n\"distance\",\n\"finish\",\n\"image\",\n\"imagine\",\n\"important\",\n\"nobody\",\n\"shatter\",\n\"warmth\",\n\"became\",\n\"feed\",\n\"flesh\",\n\"funny\",\n\"lust\",\n\"shirt\",\n\"trouble\",\n\"yellow\",\n\"attention\",\n\"bare\",\n\"bite\",\n\"money\",\n\"protect\",\n\"amaze\",\n\"appear\",\n\"born\",\n\"choke\",\n\"completely\",\n\"daughter\",\n\"fresh\",\n\"friendship\",\n\"gentle\",\n\"probably\",\n\"six\",\n\"deserve\",\n\"expect\",\n\"grab\",\n\"middle\",\n\"nightmare\",\n\"river\",\n\"thousand\",\n\"weight\",\n\"worst\",\n\"wound\",\n\"barely\",\n\"bottle\",\n\"cream\",\n\"regret\",\n\"relationship\",\n\"stick\",\n\"test\",\n\"crush\",\n\"endless\",\n\"fault\",\n\"itself\",\n\"rule\",\n\"spill\",\n\"art\",\n\"circle\",\n\"join\",\n\"kick\",\n\"mask\",\n\"master\",\n\"passion\",\n\"quick\",\n\"raise\",\n\"smooth\",\n\"unless\",\n\"wander\",\n\"actually\",\n\"broke\",\n\"chair\",\n\"deal\",\n\"favorite\",\n\"gift\",\n\"note\",\n\"number\",\n\"sweat\",\n\"box\",\n\"chill\",\n\"clothes\",\n\"lady\",\n\"mark\",\n\"park\",\n\"poor\",\n\"sadness\",\n\"tie\",\n\"animal\",\n\"belong\",\n\"brush\",\n\"consume\",\n\"dawn\",\n\"forest\",\n\"innocent\",\n\"pen\",\n\"pride\",\n\"stream\",\n\"thick\",\n\"clay\",\n\"complete\",\n\"count\",\n\"draw\",\n\"faith\",\n\"press\",\n\"silver\",\n\"struggle\",\n\"surface\",\n\"taught\",\n\"teach\",\n\"wet\",\n\"bless\",\n\"chase\",\n\"climb\",\n\"enter\",\n\"letter\",\n\"melt\",\n\"metal\",\n\"movie\",\n\"stretch\",\n\"swing\",\n\"vision\",\n\"wife\",\n\"beside\",\n\"crash\",\n\"forgot\",\n\"guide\",\n\"haunt\",\n\"joke\",\n\"knock\",\n\"plant\",\n\"pour\",\n\"prove\",\n\"reveal\",\n\"steal\",\n\"stuff\",\n\"trip\",\n\"wood\",\n\"wrist\",\n\"bother\",\n\"bottom\",\n\"crawl\",\n\"crowd\",\n\"fix\",\n\"forgive\",\n\"frown\",\n\"grace\",\n\"loose\",\n\"lucky\",\n\"party\",\n\"release\",\n\"surely\",\n\"survive\",\n\"teacher\",\n\"gently\",\n\"grip\",\n\"speed\",\n\"suicide\",\n\"travel\",\n\"treat\",\n\"vein\",\n\"written\",\n\"cage\",\n\"chain\",\n\"conversation\",\n\"date\",\n\"enemy\",\n\"however\",\n\"interest\",\n\"million\",\n\"page\",\n\"pink\",\n\"proud\",\n\"sway\",\n\"themselves\",\n\"winter\",\n\"church\",\n\"cruel\",\n\"cup\",\n\"demon\",\n\"experience\",\n\"freedom\",\n\"pair\",\n\"pop\",\n\"purpose\",\n\"respect\",\n\"shoot\",\n\"softly\",\n\"state\",\n\"strange\",\n\"bar\",\n\"birth\",\n\"curl\",\n\"dirt\",\n\"excuse\",\n\"lord\",\n\"lovely\",\n\"monster\",\n\"order\",\n\"pack\",\n\"pants\",\n\"pool\",\n\"scene\",\n\"seven\",\n\"shame\",\n\"slide\",\n\"ugly\",\n\"among\",\n\"blade\",\n\"blonde\",\n\"closet\",\n\"creek\",\n\"deny\",\n\"drug\",\n\"eternity\",\n\"gain\",\n\"grade\",\n\"handle\",\n\"key\",\n\"linger\",\n\"pale\",\n\"prepare\",\n\"swallow\",\n\"swim\",\n\"tremble\",\n\"wheel\",\n\"won\",\n\"cast\",\n\"cigarette\",\n\"claim\",\n\"college\",\n\"direction\",\n\"dirty\",\n\"gather\",\n\"ghost\",\n\"hundred\",\n\"loss\",\n\"lung\",\n\"orange\",\n\"present\",\n\"swear\",\n\"swirl\",\n\"twice\",\n\"wild\",\n\"bitter\",\n\"blanket\",\n\"doctor\",\n\"everywhere\",\n\"flash\",\n\"grown\",\n\"knowledge\",\n\"numb\",\n\"pressure\",\n\"radio\",\n\"repeat\",\n\"ruin\",\n\"spend\",\n\"unknown\",\n\"buy\",\n\"clock\",\n\"devil\",\n\"early\",\n\"false\",\n\"fantasy\",\n\"pound\",\n\"precious\",\n\"refuse\",\n\"sheet\",\n\"teeth\",\n\"welcome\",\n\"add\",\n\"ahead\",\n\"block\",\n\"bury\",\n\"caress\",\n\"content\",\n\"depth\",\n\"despite\",\n\"distant\",\n\"marry\",\n\"purple\",\n\"threw\",\n\"whenever\",\n\"bomb\",\n\"dull\",\n\"easily\",\n\"grasp\",\n\"hospital\",\n\"innocence\",\n\"normal\",\n\"receive\",\n\"reply\",\n\"rhyme\",\n\"shade\",\n\"someday\",\n\"sword\",\n\"toe\",\n\"visit\",\n\"asleep\",\n\"bought\",\n\"center\",\n\"consider\",\n\"flat\",\n\"hero\",\n\"history\",\n\"ink\",\n\"insane\",\n\"muscle\",\n\"mystery\",\n\"pocket\",\n\"reflection\",\n\"shove\",\n\"silently\",\n\"smart\",\n\"soldier\",\n\"spot\",\n\"stress\",\n\"train\",\n\"type\",\n\"view\",\n\"whether\",\n\"bus\",\n\"energy\",\n\"explain\",\n\"holy\",\n\"hunger\",\n\"inch\",\n\"magic\",\n\"mix\",\n\"noise\",\n\"nowhere\",\n\"prayer\",\n\"presence\",\n\"shock\",\n\"snap\",\n\"spider\",\n\"study\",\n\"thunder\",\n\"trail\",\n\"admit\",\n\"agree\",\n\"bag\",\n\"bang\",\n\"bound\",\n\"butterfly\",\n\"cute\",\n\"exactly\",\n\"explode\",\n\"familiar\",\n\"fold\",\n\"further\",\n\"pierce\",\n\"reflect\",\n\"scent\",\n\"selfish\",\n\"sharp\",\n\"sink\",\n\"spring\",\n\"stumble\",\n\"universe\",\n\"weep\",\n\"women\",\n\"wonderful\",\n\"action\",\n\"ancient\",\n\"attempt\",\n\"avoid\",\n\"birthday\",\n\"branch\",\n\"chocolate\",\n\"core\",\n\"depress\",\n\"drunk\",\n\"especially\",\n\"focus\",\n\"fruit\",\n\"honest\",\n\"match\",\n\"palm\",\n\"perfectly\",\n\"pillow\",\n\"pity\",\n\"poison\",\n\"roar\",\n\"shift\",\n\"slightly\",\n\"thump\",\n\"truck\",\n\"tune\",\n\"twenty\",\n\"unable\",\n\"wipe\",\n\"wrote\",\n\"coat\",\n\"constant\",\n\"dinner\",\n\"drove\",\n\"egg\",\n\"eternal\",\n\"flight\",\n\"flood\",\n\"frame\",\n\"freak\",\n\"gasp\",\n\"glad\",\n\"hollow\",\n\"motion\",\n\"peer\",\n\"plastic\",\n\"root\",\n\"screen\",\n\"season\",\n\"sting\",\n\"strike\",\n\"team\",\n\"unlike\",\n\"victim\",\n\"volume\",\n\"warn\",\n\"weird\",\n\"attack\",\n\"await\",\n\"awake\",\n\"built\",\n\"charm\",\n\"crave\",\n\"despair\",\n\"fought\",\n\"grant\",\n\"grief\",\n\"horse\",\n\"limit\",\n\"message\",\n\"ripple\",\n\"sanity\",\n\"scatter\",\n\"serve\",\n\"split\",\n\"string\",\n\"trick\",\n\"annoy\",\n\"blur\",\n\"boat\",\n\"brave\",\n\"clearly\",\n\"cling\",\n\"connect\",\n\"fist\",\n\"forth\",\n\"imagination\",\n\"iron\",\n\"jock\",\n\"judge\",\n\"lesson\",\n\"milk\",\n\"misery\",\n\"nail\",\n\"naked\",\n\"ourselves\",\n\"poet\",\n\"possible\",\n\"princess\",\n\"sail\",\n\"size\",\n\"snake\",\n\"society\",\n\"stroke\",\n\"torture\",\n\"toss\",\n\"trace\",\n\"wise\",\n\"bloom\",\n\"bullet\",\n\"cell\",\n\"check\",\n\"cost\",\n\"darling\",\n\"during\",\n\"footstep\",\n\"fragile\",\n\"hallway\",\n\"hardly\",\n\"horizon\",\n\"invisible\",\n\"journey\",\n\"midnight\",\n\"mud\",\n\"nod\",\n\"pause\",\n\"relax\",\n\"shiver\",\n\"sudden\",\n\"value\",\n\"youth\",\n\"abuse\",\n\"admire\",\n\"blink\",\n\"breast\",\n\"bruise\",\n\"constantly\",\n\"couple\",\n\"creep\",\n\"curve\",\n\"difference\",\n\"dumb\",\n\"emptiness\",\n\"gotta\",\n\"honor\",\n\"plain\",\n\"planet\",\n\"recall\",\n\"rub\",\n\"ship\",\n\"slam\",\n\"soar\",\n\"somebody\",\n\"tightly\",\n\"weather\",\n\"adore\",\n\"approach\",\n\"bond\",\n\"bread\",\n\"burst\",\n\"candle\",\n\"coffee\",\n\"cousin\",\n\"crime\",\n\"desert\",\n\"flutter\",\n\"frozen\",\n\"grand\",\n\"heel\",\n\"hello\",\n\"language\",\n\"level\",\n\"movement\",\n\"pleasure\",\n\"powerful\",\n\"random\",\n\"rhythm\",\n\"settle\",\n\"silly\",\n\"slap\",\n\"sort\",\n\"spoken\",\n\"steel\",\n\"threaten\",\n\"tumble\",\n\"upset\",\n\"aside\",\n\"awkward\",\n\"bee\",\n\"blank\",\n\"board\",\n\"button\",\n\"card\",\n\"carefully\",\n\"complain\",\n\"crap\",\n\"deeply\",\n\"discover\",\n\"drag\",\n\"dread\",\n\"effort\",\n\"entire\",\n\"fairy\",\n\"giant\",\n\"gotten\",\n\"greet\",\n\"illusion\",\n\"jeans\",\n\"leap\",\n\"liquid\",\n\"march\",\n\"mend\",\n\"nervous\",\n\"nine\",\n\"replace\",\n\"rope\",\n\"spine\",\n\"stole\",\n\"terror\",\n\"accident\",\n\"apple\",\n\"balance\",\n\"boom\",\n\"childhood\",\n\"collect\",\n\"demand\",\n\"depression\",\n\"eventually\",\n\"faint\",\n\"glare\",\n\"goal\",\n\"group\",\n\"honey\",\n\"kitchen\",\n\"laid\",\n\"limb\",\n\"machine\",\n\"mere\",\n\"mold\",\n\"murder\",\n\"nerve\",\n\"painful\",\n\"poetry\",\n\"prince\",\n\"rabbit\",\n\"shelter\",\n\"shore\",\n\"shower\",\n\"soothe\",\n\"stair\",\n\"steady\",\n\"sunlight\",\n\"tangle\",\n\"tease\",\n\"treasure\",\n\"uncle\",\n\"begun\",\n\"bliss\",\n\"canvas\",\n\"cheer\",\n\"claw\",\n\"clutch\",\n\"commit\",\n\"crimson\",\n\"crystal\",\n\"delight\",\n\"doll\",\n\"existence\",\n\"express\",\n\"fog\",\n\"football\",\n\"gay\",\n\"goose\",\n\"guard\",\n\"hatred\",\n\"illuminate\",\n\"mass\",\n\"math\",\n\"mourn\",\n\"rich\",\n\"rough\",\n\"skip\",\n\"stir\",\n\"student\",\n\"style\",\n\"support\",\n\"thorn\",\n\"tough\",\n\"yard\",\n\"yearn\",\n\"yesterday\",\n\"advice\",\n\"appreciate\",\n\"autumn\",\n\"bank\",\n\"beam\",\n\"bowl\",\n\"capture\",\n\"carve\",\n\"collapse\",\n\"confusion\",\n\"creation\",\n\"dove\",\n\"feather\",\n\"girlfriend\",\n\"glory\",\n\"government\",\n\"harsh\",\n\"hop\",\n\"inner\",\n\"loser\",\n\"moonlight\",\n\"neighbor\",\n\"neither\",\n\"peach\",\n\"pig\",\n\"praise\",\n\"screw\",\n\"shield\",\n\"shimmer\",\n\"sneak\",\n\"stab\",\n\"subject\",\n\"throughout\",\n\"thrown\",\n\"tower\",\n\"twirl\",\n\"wow\",\n\"army\",\n\"arrive\",\n\"bathroom\",\n\"bump\",\n\"cease\",\n\"cookie\",\n\"couch\",\n\"courage\",\n\"dim\",\n\"guilt\",\n\"howl\",\n\"hum\",\n\"husband\",\n\"insult\",\n\"led\",\n\"lunch\",\n\"mock\",\n\"mostly\",\n\"natural\",\n\"nearly\",\n\"needle\",\n\"nerd\",\n\"peaceful\",\n\"perfection\",\n\"pile\",\n\"price\",\n\"remove\",\n\"roam\",\n\"sanctuary\",\n\"serious\",\n\"shiny\",\n\"shook\",\n\"sob\",\n\"stolen\",\n\"tap\",\n\"vain\",\n\"void\",\n\"warrior\",\n\"wrinkle\",\n\"affection\",\n\"apologize\",\n\"blossom\",\n\"bounce\",\n\"bridge\",\n\"cheap\",\n\"crumble\",\n\"decision\",\n\"descend\",\n\"desperately\",\n\"dig\",\n\"dot\",\n\"flip\",\n\"frighten\",\n\"heartbeat\",\n\"huge\",\n\"lazy\",\n\"lick\",\n\"odd\",\n\"opinion\",\n\"process\",\n\"puzzle\",\n\"quietly\",\n\"retreat\",\n\"score\",\n\"sentence\",\n\"separate\",\n\"situation\",\n\"skill\",\n\"soak\",\n\"square\",\n\"stray\",\n\"taint\",\n\"task\",\n\"tide\",\n\"underneath\",\n\"veil\",\n\"whistle\",\n\"anywhere\",\n\"bedroom\",\n\"bid\",\n\"bloody\",\n\"burden\",\n\"careful\",\n\"compare\",\n\"concern\",\n\"curtain\",\n\"decay\",\n\"defeat\",\n\"describe\",\n\"double\",\n\"dreamer\",\n\"driver\",\n\"dwell\",\n\"evening\",\n\"flare\",\n\"flicker\",\n\"grandma\",\n\"guitar\",\n\"harm\",\n\"horrible\",\n\"hungry\",\n\"indeed\",\n\"lace\",\n\"melody\",\n\"monkey\",\n\"nation\",\n\"object\",\n\"obviously\",\n\"rainbow\",\n\"salt\",\n\"scratch\",\n\"shown\",\n\"shy\",\n\"stage\",\n\"stun\",\n\"third\",\n\"tickle\",\n\"useless\",\n\"weakness\",\n\"worship\",\n\"worthless\",\n\"afternoon\",\n\"beard\",\n\"boyfriend\",\n\"bubble\",\n\"busy\",\n\"certain\",\n\"chin\",\n\"concrete\",\n\"desk\",\n\"diamond\",\n\"doom\",\n\"drawn\",\n\"due\",\n\"felicity\",\n\"freeze\",\n\"frost\",\n\"garden\",\n\"glide\",\n\"harmony\",\n\"hopefully\",\n\"hunt\",\n\"jealous\",\n\"lightning\",\n\"mama\",\n\"mercy\",\n\"peel\",\n\"physical\",\n\"position\",\n\"pulse\",\n\"punch\",\n\"quit\",\n\"rant\",\n\"respond\",\n\"salty\",\n\"sane\",\n\"satisfy\",\n\"savior\",\n\"sheep\",\n\"slept\",\n\"social\",\n\"sport\",\n\"tuck\",\n\"utter\",\n\"valley\",\n\"wolf\",\n\"aim\",\n\"alas\",\n\"alter\",\n\"arrow\",\n\"awaken\",\n\"beaten\",\n\"belief\",\n\"brand\",\n\"ceiling\",\n\"cheese\",\n\"clue\",\n\"confidence\",\n\"connection\",\n\"daily\",\n\"disguise\",\n\"eager\",\n\"erase\",\n\"essence\",\n\"everytime\",\n\"expression\",\n\"fan\",\n\"flag\",\n\"flirt\",\n\"foul\",\n\"fur\",\n\"giggle\",\n\"glorious\",\n\"ignorance\",\n\"law\",\n\"lifeless\",\n\"measure\",\n\"mighty\",\n\"muse\",\n\"north\",\n\"opposite\",\n\"paradise\",\n\"patience\",\n\"patient\",\n\"pencil\",\n\"petal\",\n\"plate\",\n\"ponder\",\n\"possibly\",\n\"practice\",\n\"slice\",\n\"spell\",\n\"stock\",\n\"strife\",\n\"strip\",\n\"suffocate\",\n\"suit\",\n\"tender\",\n\"tool\",\n\"trade\",\n\"velvet\",\n\"verse\",\n\"waist\",\n\"witch\",\n\"aunt\",\n\"bench\",\n\"bold\",\n\"cap\",\n\"certainly\",\n\"click\",\n\"companion\",\n\"creator\",\n\"dart\",\n\"delicate\",\n\"determine\",\n\"dish\",\n\"dragon\",\n\"drama\",\n\"drum\",\n\"dude\",\n\"everybody\",\n\"feast\",\n\"forehead\",\n\"former\",\n\"fright\",\n\"fully\",\n\"gas\",\n\"hook\",\n\"hurl\",\n\"invite\",\n\"juice\",\n\"manage\",\n\"moral\",\n\"possess\",\n\"raw\",\n\"rebel\",\n\"royal\",\n\"scale\",\n\"scary\",\n\"several\",\n\"slight\",\n\"stubborn\",\n\"swell\",\n\"talent\",\n\"tea\",\n\"terrible\",\n\"thread\",\n\"torment\",\n\"trickle\",\n\"usually\",\n\"vast\",\n\"violence\",\n\"weave\",\n\"acid\",\n\"agony\",\n\"ashamed\",\n\"awe\",\n\"belly\",\n\"blend\",\n\"blush\",\n\"character\",\n\"cheat\",\n\"common\",\n\"company\",\n\"coward\",\n\"creak\",\n\"danger\",\n\"deadly\",\n\"defense\",\n\"define\",\n\"depend\",\n\"desperate\",\n\"destination\",\n\"dew\",\n\"duck\",\n\"dusty\",\n\"embarrass\",\n\"engine\",\n\"example\",\n\"explore\",\n\"foe\",\n\"freely\",\n\"frustrate\",\n\"generation\",\n\"glove\",\n\"guilty\",\n\"health\",\n\"hurry\",\n\"idiot\",\n\"impossible\",\n\"inhale\",\n\"jaw\",\n\"kingdom\",\n\"mention\",\n\"mist\",\n\"moan\",\n\"mumble\",\n\"mutter\",\n\"observe\",\n\"ode\",\n\"pathetic\",\n\"pattern\",\n\"pie\",\n\"prefer\",\n\"puff\",\n\"rape\",\n\"rare\",\n\"revenge\",\n\"rude\",\n\"scrape\",\n\"spiral\",\n\"squeeze\",\n\"strain\",\n\"sunset\",\n\"suspend\",\n\"sympathy\",\n\"thigh\",\n\"throne\",\n\"total\",\n\"unseen\",\n\"weapon\",\n\"weary\"\n]\n\n\n\nn = 1626\n\n# Note about US patent no 5892470: Here each word does not represent a given digit.\n# Instead, the digit represented by a word is variable, it depends on the previous word.\n\ndef mn_encode( message ):\n    assert len(message) % 8 == 0\n    out = []\n    for i in range(len(message)//8):\n        word = message[8*i:8*i+8]\n        x = int(word, 16)\n        w1 = (x%n)\n        w2 = ((x//n) + w1)%n\n        w3 = ((x//n//n) + w2)%n\n        out += [ words[w1], words[w2], words[w3] ]\n    return out\n\n\ndef mn_decode( wlist ):\n    out = ''\n    for i in range(len(wlist)//3):\n        word1, word2, word3 = wlist[3*i:3*i+3]\n        w1 =  words.index(word1)\n        w2 = (words.index(word2))%n\n        w3 = (words.index(word3))%n\n        x = w1 +n*((w2-w1)%n) +n*n*((w3-w2)%n)\n        out += '%08x'%x\n    return out\n\n\nif __name__ == '__main__':\n    import sys\n    if len(sys.argv) == 1:\n        print('I need arguments: a hex string to encode, or a list of words to decode')\n    elif len(sys.argv) == 2:\n        print(' '.join(mn_encode(sys.argv[1])))\n    else:\n        print(mn_decode(sys.argv[1:]))\n"
  },
  {
    "path": "lib/paymentrequest.proto",
    "content": "//\n// Simple Bitcoin Payment Protocol messages\n//\n// Use fields 1000+ for extensions;\n// to avoid conflicts, register extensions via pull-req at\n// https://github.com/bitcoin/bips/bip-0070/extensions.mediawiki\n//\n\nsyntax = \"proto2\";\npackage payments;\noption java_package = \"org.bitcoin.protocols.payments\";\noption java_outer_classname = \"Protos\";\n\n// Generalized form of \"send payment to this/these bitcoin addresses\"\nmessage Output {\n        optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis\n        required bytes script = 2; // usually one of the standard Script forms\n}\nmessage PaymentDetails {\n        optional string network = 1 [default = \"main\"]; // \"main\" or \"test\"\n        repeated Output outputs = 2;        // Where payment should be sent\n        required uint64 time = 3;           // Timestamp; when payment request created\n        optional uint64 expires = 4;        // Timestamp; when this request should be considered invalid\n        optional string memo = 5;           // Human-readable description of request for the customer\n        optional string payment_url = 6;    // URL to send Payment and get PaymentACK\n        optional bytes merchant_data = 7;   // Arbitrary data to include in the Payment message\n}\nmessage PaymentRequest {\n        optional uint32 payment_details_version = 1 [default = 1];\n        optional string pki_type = 2 [default = \"none\"];  // none / x509+sha256 / x509+sha1\n        optional bytes pki_data = 3;                      // depends on pki_type\n        required bytes serialized_payment_details = 4;    // PaymentDetails\n        optional bytes signature = 5;                     // pki-dependent signature\n}\nmessage X509Certificates {\n        repeated bytes certificate = 1;    // DER-encoded X.509 certificate chain\n}\nmessage Payment {\n        optional bytes merchant_data = 1;  // From PaymentDetails.merchant_data\n        repeated bytes transactions = 2;   // Signed transactions that satisfy PaymentDetails.outputs\n        repeated Output refund_to = 3;     // Where to send refunds, if a refund is necessary\n        optional string memo = 4;          // Human-readable message for the merchant\n}\nmessage PaymentACK {\n        required Payment payment = 1;      // Payment message that triggered this ACK\n        optional string memo = 2;          // human-readable message for customer\n}\n"
  },
  {
    "path": "lib/paymentrequest.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport hashlib\nimport sys\nimport time\nimport traceback\nimport json\nimport requests\n\nimport urllib.parse\n\n\ntry:\n    from . import paymentrequest_pb2 as pb2\nexcept ImportError:\n    sys.exit(\"Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=lib/ --python_out=lib/ lib/paymentrequest.proto'\")\n\nfrom . import bitcoin\nfrom . import util\nfrom .util import print_error, bh2u, bfh, get_cert_path\nfrom . import transaction\nfrom . import x509\nfrom . import rsakey\n\nfrom .bitcoin import TYPE_ADDRESS\n\nREQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}\nACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}\n\nca_path = get_cert_path()\nca_list = None\nca_keyID = None\n\ndef load_ca_list():\n    global ca_list, ca_keyID\n    if ca_list is None:\n        ca_list, ca_keyID = x509.load_certificates(ca_path)\n\n\n\n# status of payment requests\nPR_UNPAID  = 0\nPR_EXPIRED = 1\nPR_UNKNOWN = 2     # sent but not propagated\nPR_PAID    = 3     # send and propagated\n\n\n\ndef get_payment_request(url):\n    u = urllib.parse.urlparse(url)\n    error = None\n    if u.scheme in ['http', 'https']:\n        try:\n            response = requests.request('GET', url, headers=REQUEST_HEADERS)\n            response.raise_for_status()\n            # Guard against `bitcoin:`-URIs with invalid payment request URLs\n            if \"Content-Type\" not in response.headers \\\n            or response.headers[\"Content-Type\"] != \"application/bitcoin-paymentrequest\":\n                data = None\n                error = \"payment URL not pointing to a payment request handling server\"\n            else:\n                data = response.content\n            print_error('fetched payment request', url, len(response.content))\n        except requests.exceptions.RequestException:\n            data = None\n            error = \"payment URL not pointing to a valid server\"\n    elif u.scheme == 'file':\n        try:\n            with open(u.path, 'r') as f:\n                data = f.read()\n        except IOError:\n            data = None\n            error = \"payment URL not pointing to a valid file\"\n    else:\n        raise BaseException(\"unknown scheme\", url)\n    pr = PaymentRequest(data, error)\n    return pr\n\n\nclass PaymentRequest:\n\n    def __init__(self, data, error=None):\n        self.raw = data\n        self.error = error\n        self.parse(data)\n        self.requestor = None # known after verify\n        self.tx = None\n\n    def __str__(self):\n        return self.raw\n\n    def parse(self, r):\n        if self.error:\n            return\n        self.id = bh2u(bitcoin.sha256(r)[0:16])\n        try:\n            self.data = pb2.PaymentRequest()\n            self.data.ParseFromString(r)\n        except:\n            self.error = \"cannot parse payment request\"\n            return\n        self.details = pb2.PaymentDetails()\n        self.details.ParseFromString(self.data.serialized_payment_details)\n        self.outputs = []\n        for o in self.details.outputs:\n            addr = transaction.get_address_from_output_script(o.script)[1]\n            self.outputs.append((TYPE_ADDRESS, addr, o.amount))\n        self.memo = self.details.memo\n        self.payment_url = self.details.payment_url\n\n    def is_pr(self):\n        return self.get_amount() != 0\n        #return self.get_outputs() != [(TYPE_ADDRESS, self.get_requestor(), self.get_amount())]\n\n    def verify(self, contacts):\n        if self.error:\n            return False\n        if not self.raw:\n            self.error = \"Empty request\"\n            return False\n        pr = pb2.PaymentRequest()\n        try:\n            pr.ParseFromString(self.raw)\n        except:\n            self.error = \"Error: Cannot parse payment request\"\n            return False\n        if not pr.signature:\n            # the address will be dispayed as requestor\n            self.requestor = None\n            return True\n        if pr.pki_type in [\"x509+sha256\", \"x509+sha1\"]:\n            return self.verify_x509(pr)\n        elif pr.pki_type in [\"dnssec+btc\", \"dnssec+ecdsa\"]:\n            return self.verify_dnssec(pr, contacts)\n        else:\n            self.error = \"ERROR: Unsupported PKI Type for Message Signature\"\n            return False\n\n    def verify_x509(self, paymntreq):\n        load_ca_list()\n        if not ca_list:\n            self.error = \"Trusted certificate authorities list not found\"\n            return False\n        cert = pb2.X509Certificates()\n        cert.ParseFromString(paymntreq.pki_data)\n        # verify the chain of certificates\n        try:\n            x, ca = verify_cert_chain(cert.certificate)\n        except BaseException as e:\n            traceback.print_exc(file=sys.stderr)\n            self.error = str(e)\n            return False\n        # get requestor name\n        self.requestor = x.get_common_name()\n        if self.requestor.startswith('*.'):\n            self.requestor = self.requestor[2:]\n        # verify the BIP70 signature\n        pubkey0 = rsakey.RSAKey(x.modulus, x.exponent)\n        sig = paymntreq.signature\n        paymntreq.signature = b''\n        s = paymntreq.SerializeToString()\n        sigBytes = bytearray(sig)\n        msgBytes = bytearray(s)\n        if paymntreq.pki_type == \"x509+sha256\":\n            hashBytes = bytearray(hashlib.sha256(msgBytes).digest())\n            verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes)\n        elif paymntreq.pki_type == \"x509+sha1\":\n            verify = pubkey0.hashAndVerify(sigBytes, msgBytes)\n        if not verify:\n            self.error = \"ERROR: Invalid Signature for Payment Request Data\"\n            return False\n        ### SIG Verified\n        self.error = 'Signed by Trusted CA: ' + ca.get_common_name()\n        return True\n\n    def verify_dnssec(self, pr, contacts):\n        sig = pr.signature\n        alias = pr.pki_data\n        info = contacts.resolve(alias)\n        if info.get('validated') is not True:\n            self.error = \"Alias verification failed (DNSSEC)\"\n            return False\n        if pr.pki_type == \"dnssec+btc\":\n            self.requestor = alias\n            address = info.get('address')\n            pr.signature = ''\n            message = pr.SerializeToString()\n            if bitcoin.verify_message(address, sig, message):\n                self.error = 'Verified with DNSSEC'\n                return True\n            else:\n                self.error = \"verify failed\"\n                return False\n        else:\n            self.error = \"unknown algo\"\n            return False\n\n    def has_expired(self):\n        return self.details.expires and self.details.expires < int(time.time())\n\n    def get_expiration_date(self):\n        return self.details.expires\n\n    def get_amount(self):\n        return sum(map(lambda x:x[2], self.outputs))\n\n    def get_address(self):\n        o = self.outputs[0]\n        assert o[0] == TYPE_ADDRESS\n        return o[1]\n\n    def get_requestor(self):\n        return self.requestor if self.requestor else self.get_address()\n\n    def get_verify_status(self):\n        return self.error if self.requestor else \"No Signature\"\n\n    def get_memo(self):\n        return self.memo\n\n    def get_dict(self):\n        return {\n            'requestor': self.get_requestor(),\n            'memo':self.get_memo(),\n            'exp': self.get_expiration_date(),\n            'amount': self.get_amount(),\n            'signature': self.get_verify_status(),\n            'txid': self.tx,\n            'outputs': self.get_outputs()\n        }\n\n    def get_id(self):\n        return self.id if self.requestor else self.get_address()\n\n    def get_outputs(self):\n        return self.outputs[:]\n\n    def send_ack(self, raw_tx, refund_addr):\n        pay_det = self.details\n        if not self.details.payment_url:\n            return False, \"no url\"\n        paymnt = pb2.Payment()\n        paymnt.merchant_data = pay_det.merchant_data\n        paymnt.transactions.append(bfh(raw_tx))\n        ref_out = paymnt.refund_to.add()\n        ref_out.script = util.bfh(transaction.Transaction.pay_script(TYPE_ADDRESS, refund_addr))\n        paymnt.memo = \"Paid using Electrum\"\n        pm = paymnt.SerializeToString()\n        payurl = urllib.parse.urlparse(pay_det.payment_url)\n        try:\n            r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=ca_path)\n        except requests.exceptions.SSLError:\n            print(\"Payment Message/PaymentACK verify Failed\")\n            try:\n                r = requests.post(payurl.geturl(), data=pm, headers=ACK_HEADERS, verify=False)\n            except Exception as e:\n                print(e)\n                return False, \"Payment Message/PaymentACK Failed\"\n        if r.status_code >= 500:\n            return False, r.reason\n        try:\n            paymntack = pb2.PaymentACK()\n            paymntack.ParseFromString(r.content)\n        except Exception:\n            return False, \"PaymentACK could not be processed. Payment was sent; please manually verify that payment was received.\"\n        print(\"PaymentACK message received: %s\" % paymntack.memo)\n        return True, paymntack.memo\n\n\ndef make_unsigned_request(req):\n    from .transaction import Transaction\n    addr = req['address']\n    time = req.get('time', 0)\n    exp = req.get('exp', 0)\n    if time and type(time) != int:\n        time = 0\n    if exp and type(exp) != int:\n        exp = 0\n    amount = req['amount']\n    if amount is None:\n        amount = 0\n    memo = req['memo']\n    script = bfh(Transaction.pay_script(TYPE_ADDRESS, addr))\n    outputs = [(script, amount)]\n    pd = pb2.PaymentDetails()\n    for script, amount in outputs:\n        pd.outputs.add(amount=amount, script=script)\n    pd.time = time\n    pd.expires = time + exp if exp else 0\n    pd.memo = memo\n    pr = pb2.PaymentRequest()\n    pr.serialized_payment_details = pd.SerializeToString()\n    pr.signature = util.to_bytes('')\n    return pr\n\n\ndef sign_request_with_alias(pr, alias, alias_privkey):\n    pr.pki_type = 'dnssec+btc'\n    pr.pki_data = str(alias)\n    message = pr.SerializeToString()\n    ec_key = bitcoin.regenerate_key(alias_privkey)\n    address = bitcoin.address_from_private_key(alias_privkey)\n    compressed = bitcoin.is_compressed(alias_privkey)\n    pr.signature = ec_key.sign_message(message, compressed, address)\n\n\ndef verify_cert_chain(chain):\n    \"\"\" Verify a chain of certificates. The last certificate is the CA\"\"\"\n    load_ca_list()\n    # parse the chain\n    cert_num = len(chain)\n    x509_chain = []\n    for i in range(cert_num):\n        x = x509.X509(bytearray(chain[i]))\n        x509_chain.append(x)\n        if i == 0:\n            x.check_date()\n        else:\n            if not x.check_ca():\n                raise BaseException(\"ERROR: Supplied CA Certificate Error\")\n    if not cert_num > 1:\n        raise BaseException(\"ERROR: CA Certificate Chain Not Provided by Payment Processor\")\n    # if the root CA is not supplied, add it to the chain\n    ca = x509_chain[cert_num-1]\n    if ca.getFingerprint() not in ca_list:\n        keyID = ca.get_issuer_keyID()\n        f = ca_keyID.get(keyID)\n        if f:\n            root = ca_list[f]\n            x509_chain.append(root)\n        else:\n            raise BaseException(\"Supplied CA Not Found in Trusted CA Store.\")\n    # verify the chain of signatures\n    cert_num = len(x509_chain)\n    for i in range(1, cert_num):\n        x = x509_chain[i]\n        prev_x = x509_chain[i-1]\n        algo, sig, data = prev_x.get_signature()\n        sig = bytearray(sig)\n        pubkey = rsakey.RSAKey(x.modulus, x.exponent)\n        if algo == x509.ALGO_RSA_SHA1:\n            verify = pubkey.hashAndVerify(sig, data)\n        elif algo == x509.ALGO_RSA_SHA256:\n            hashBytes = bytearray(hashlib.sha256(data).digest())\n            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes)\n        elif algo == x509.ALGO_RSA_SHA384:\n            hashBytes = bytearray(hashlib.sha384(data).digest())\n            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes)\n        elif algo == x509.ALGO_RSA_SHA512:\n            hashBytes = bytearray(hashlib.sha512(data).digest())\n            verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes)\n        else:\n            raise BaseException(\"Algorithm not supported\")\n            util.print_error(self.error, algo.getComponentByName('algorithm'))\n        if not verify:\n            raise BaseException(\"Certificate not Signed by Provided CA Certificate Chain\")\n\n    return x509_chain[0], ca\n\n\ndef check_ssl_config(config):\n    from . import pem\n    key_path = config.get('ssl_privkey')\n    cert_path = config.get('ssl_chain')\n    with open(key_path, 'r') as f:\n        params = pem.parse_private_key(f.read())\n    with open(cert_path, 'r') as f:\n        s = f.read()\n    bList = pem.dePemList(s, \"CERTIFICATE\")\n    # verify chain\n    x, ca = verify_cert_chain(bList)\n    # verify that privkey and pubkey match\n    privkey = rsakey.RSAKey(*params)\n    pubkey = rsakey.RSAKey(x.modulus, x.exponent)\n    assert x.modulus == params[0]\n    assert x.exponent == params[1]\n    # return requestor\n    requestor = x.get_common_name()\n    if requestor.startswith('*.'):\n        requestor = requestor[2:]\n    return requestor\n\ndef sign_request_with_x509(pr, key_path, cert_path):\n    from . import pem\n    with open(key_path, 'r') as f:\n        params = pem.parse_private_key(f.read())\n        privkey = rsakey.RSAKey(*params)\n    with open(cert_path, 'r') as f:\n        s = f.read()\n        bList = pem.dePemList(s, \"CERTIFICATE\")\n    certificates = pb2.X509Certificates()\n    certificates.certificate.extend(map(bytes, bList))\n    pr.pki_type = 'x509+sha256'\n    pr.pki_data = certificates.SerializeToString()\n    msgBytes = bytearray(pr.SerializeToString())\n    hashBytes = bytearray(hashlib.sha256(msgBytes).digest())\n    sig = privkey.sign(x509.PREFIX_RSA_SHA256 + hashBytes)\n    pr.signature = bytes(sig)\n\n\ndef serialize_request(req):\n    pr = make_unsigned_request(req)\n    signature = req.get('sig')\n    requestor = req.get('name')\n    if requestor and signature:\n        pr.signature = bfh(signature)\n        pr.pki_type = 'dnssec+btc'\n        pr.pki_data = str(requestor)\n    return pr\n\n\ndef make_request(config, req):\n    pr = make_unsigned_request(req)\n    key_path = config.get('ssl_privkey')\n    cert_path = config.get('ssl_chain')\n    if key_path and cert_path:\n        sign_request_with_x509(pr, key_path, cert_path)\n    return pr\n\n\n\nclass InvoiceStore(object):\n\n    def __init__(self, storage):\n        self.storage = storage\n        self.invoices = {}\n        self.paid = {}\n        d = self.storage.get('invoices', {})\n        self.load(d)\n\n    def set_paid(self, pr, txid):\n        pr.tx = txid\n        self.paid[txid] = pr.get_id()\n\n    def load(self, d):\n        for k, v in d.items():\n            try:\n                pr = PaymentRequest(bfh(v.get('hex')))\n                pr.tx = v.get('txid')\n                pr.requestor = v.get('requestor')\n                self.invoices[k] = pr\n                if pr.tx:\n                    self.paid[pr.tx] = k\n            except:\n                continue\n\n    def import_file(self, path):\n        try:\n            with open(path, 'r') as f:\n                d = json.loads(f.read())\n                self.load(d)\n        except:\n            traceback.print_exc(file=sys.stderr)\n            return\n        self.save()\n\n    def save(self):\n        l = {}\n        for k, pr in self.invoices.items():\n            l[k] = {\n                'hex': bh2u(pr.raw),\n                'requestor': pr.requestor,\n                'txid': pr.tx\n            }\n        self.storage.put('invoices', l)\n\n    def get_status(self, key):\n        pr = self.get(key)\n        if pr is None:\n            print_error(\"[InvoiceStore] get_status() can't find pr for\", key)\n            return\n        if pr.tx is not None:\n            return PR_PAID\n        if pr.has_expired():\n            return PR_EXPIRED\n        return PR_UNPAID\n\n    def add(self, pr):\n        key = pr.get_id()\n        self.invoices[key] = pr\n        self.save()\n        return key\n\n    def remove(self, key):\n        self.invoices.pop(key)\n        self.save()\n\n    def get(self, k):\n        return self.invoices.get(k)\n\n    def sorted_list(self):\n        # sort\n        return self.invoices.values()\n\n    def unpaid_invoices(self):\n        return [ self.invoices[k] for k in filter(lambda x: self.get_status(x)!=PR_PAID, self.invoices.keys())]\n"
  },
  {
    "path": "lib/paymentrequest_pb2.py",
    "content": "# Generated by the protocol buffer compiler.  DO NOT EDIT!\n# source: paymentrequest.proto\n\nimport sys\n_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))\nfrom google.protobuf import descriptor as _descriptor\nfrom google.protobuf import message as _message\nfrom google.protobuf import reflection as _reflection\nfrom google.protobuf import symbol_database as _symbol_database\nfrom google.protobuf import descriptor_pb2\n# @@protoc_insertion_point(imports)\n\n_sym_db = _symbol_database.Default()\n\n\n\n\nDESCRIPTOR = _descriptor.FileDescriptor(\n  name='paymentrequest.proto',\n  package='payments',\n  serialized_pb=_b('\\n\\x14paymentrequest.proto\\x12\\x08payments\\\"+\\n\\x06Output\\x12\\x11\\n\\x06\\x61mount\\x18\\x01 \\x01(\\x04:\\x01\\x30\\x12\\x0e\\n\\x06script\\x18\\x02 \\x02(\\x0c\\\"\\xa3\\x01\\n\\x0ePaymentDetails\\x12\\x15\\n\\x07network\\x18\\x01 \\x01(\\t:\\x04main\\x12!\\n\\x07outputs\\x18\\x02 \\x03(\\x0b\\x32\\x10.payments.Output\\x12\\x0c\\n\\x04time\\x18\\x03 \\x02(\\x04\\x12\\x0f\\n\\x07\\x65xpires\\x18\\x04 \\x01(\\x04\\x12\\x0c\\n\\x04memo\\x18\\x05 \\x01(\\t\\x12\\x13\\n\\x0bpayment_url\\x18\\x06 \\x01(\\t\\x12\\x15\\n\\rmerchant_data\\x18\\x07 \\x01(\\x0c\\\"\\x95\\x01\\n\\x0ePaymentRequest\\x12\\\"\\n\\x17payment_details_version\\x18\\x01 \\x01(\\r:\\x01\\x31\\x12\\x16\\n\\x08pki_type\\x18\\x02 \\x01(\\t:\\x04none\\x12\\x10\\n\\x08pki_data\\x18\\x03 \\x01(\\x0c\\x12\\\"\\n\\x1aserialized_payment_details\\x18\\x04 \\x02(\\x0c\\x12\\x11\\n\\tsignature\\x18\\x05 \\x01(\\x0c\\\"\\'\\n\\x10X509Certificates\\x12\\x13\\n\\x0b\\x63\\x65rtificate\\x18\\x01 \\x03(\\x0c\\\"i\\n\\x07Payment\\x12\\x15\\n\\rmerchant_data\\x18\\x01 \\x01(\\x0c\\x12\\x14\\n\\x0ctransactions\\x18\\x02 \\x03(\\x0c\\x12#\\n\\trefund_to\\x18\\x03 \\x03(\\x0b\\x32\\x10.payments.Output\\x12\\x0c\\n\\x04memo\\x18\\x04 \\x01(\\t\\\">\\n\\nPaymentACK\\x12\\\"\\n\\x07payment\\x18\\x01 \\x02(\\x0b\\x32\\x11.payments.Payment\\x12\\x0c\\n\\x04memo\\x18\\x02 \\x01(\\tB(\\n\\x1eorg.bitcoin.protocols.paymentsB\\x06Protos')\n)\n_sym_db.RegisterFileDescriptor(DESCRIPTOR)\n\n\n\n\n_OUTPUT = _descriptor.Descriptor(\n  name='Output',\n  full_name='payments.Output',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='amount', full_name='payments.Output.amount', index=0,\n      number=1, type=4, cpp_type=4, label=1,\n      has_default_value=True, default_value=0,\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='script', full_name='payments.Output.script', index=1,\n      number=2, type=12, cpp_type=9, label=2,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=34,\n  serialized_end=77,\n)\n\n\n_PAYMENTDETAILS = _descriptor.Descriptor(\n  name='PaymentDetails',\n  full_name='payments.PaymentDetails',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='network', full_name='payments.PaymentDetails.network', index=0,\n      number=1, type=9, cpp_type=9, label=1,\n      has_default_value=True, default_value=_b(\"main\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='outputs', full_name='payments.PaymentDetails.outputs', index=1,\n      number=2, type=11, cpp_type=10, label=3,\n      has_default_value=False, default_value=[],\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='time', full_name='payments.PaymentDetails.time', index=2,\n      number=3, type=4, cpp_type=4, label=2,\n      has_default_value=False, default_value=0,\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='expires', full_name='payments.PaymentDetails.expires', index=3,\n      number=4, type=4, cpp_type=4, label=1,\n      has_default_value=False, default_value=0,\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='memo', full_name='payments.PaymentDetails.memo', index=4,\n      number=5, type=9, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='payment_url', full_name='payments.PaymentDetails.payment_url', index=5,\n      number=6, type=9, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='merchant_data', full_name='payments.PaymentDetails.merchant_data', index=6,\n      number=7, type=12, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=80,\n  serialized_end=243,\n)\n\n\n_PAYMENTREQUEST = _descriptor.Descriptor(\n  name='PaymentRequest',\n  full_name='payments.PaymentRequest',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='payment_details_version', full_name='payments.PaymentRequest.payment_details_version', index=0,\n      number=1, type=13, cpp_type=3, label=1,\n      has_default_value=True, default_value=1,\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='pki_type', full_name='payments.PaymentRequest.pki_type', index=1,\n      number=2, type=9, cpp_type=9, label=1,\n      has_default_value=True, default_value=_b(\"none\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='pki_data', full_name='payments.PaymentRequest.pki_data', index=2,\n      number=3, type=12, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='serialized_payment_details', full_name='payments.PaymentRequest.serialized_payment_details', index=3,\n      number=4, type=12, cpp_type=9, label=2,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='signature', full_name='payments.PaymentRequest.signature', index=4,\n      number=5, type=12, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=246,\n  serialized_end=395,\n)\n\n\n_X509CERTIFICATES = _descriptor.Descriptor(\n  name='X509Certificates',\n  full_name='payments.X509Certificates',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='certificate', full_name='payments.X509Certificates.certificate', index=0,\n      number=1, type=12, cpp_type=9, label=3,\n      has_default_value=False, default_value=[],\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=397,\n  serialized_end=436,\n)\n\n\n_PAYMENT = _descriptor.Descriptor(\n  name='Payment',\n  full_name='payments.Payment',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='merchant_data', full_name='payments.Payment.merchant_data', index=0,\n      number=1, type=12, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\"),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='transactions', full_name='payments.Payment.transactions', index=1,\n      number=2, type=12, cpp_type=9, label=3,\n      has_default_value=False, default_value=[],\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='refund_to', full_name='payments.Payment.refund_to', index=2,\n      number=3, type=11, cpp_type=10, label=3,\n      has_default_value=False, default_value=[],\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='memo', full_name='payments.Payment.memo', index=3,\n      number=4, type=9, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=438,\n  serialized_end=543,\n)\n\n\n_PAYMENTACK = _descriptor.Descriptor(\n  name='PaymentACK',\n  full_name='payments.PaymentACK',\n  filename=None,\n  file=DESCRIPTOR,\n  containing_type=None,\n  fields=[\n    _descriptor.FieldDescriptor(\n      name='payment', full_name='payments.PaymentACK.payment', index=0,\n      number=1, type=11, cpp_type=10, label=2,\n      has_default_value=False, default_value=None,\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n    _descriptor.FieldDescriptor(\n      name='memo', full_name='payments.PaymentACK.memo', index=1,\n      number=2, type=9, cpp_type=9, label=1,\n      has_default_value=False, default_value=_b(\"\").decode('utf-8'),\n      message_type=None, enum_type=None, containing_type=None,\n      is_extension=False, extension_scope=None,\n      options=None),\n  ],\n  extensions=[\n  ],\n  nested_types=[],\n  enum_types=[\n  ],\n  options=None,\n  is_extendable=False,\n  extension_ranges=[],\n  oneofs=[\n  ],\n  serialized_start=545,\n  serialized_end=607,\n)\n\n_PAYMENTDETAILS.fields_by_name['outputs'].message_type = _OUTPUT\n_PAYMENT.fields_by_name['refund_to'].message_type = _OUTPUT\n_PAYMENTACK.fields_by_name['payment'].message_type = _PAYMENT\nDESCRIPTOR.message_types_by_name['Output'] = _OUTPUT\nDESCRIPTOR.message_types_by_name['PaymentDetails'] = _PAYMENTDETAILS\nDESCRIPTOR.message_types_by_name['PaymentRequest'] = _PAYMENTREQUEST\nDESCRIPTOR.message_types_by_name['X509Certificates'] = _X509CERTIFICATES\nDESCRIPTOR.message_types_by_name['Payment'] = _PAYMENT\nDESCRIPTOR.message_types_by_name['PaymentACK'] = _PAYMENTACK\n\nOutput = _reflection.GeneratedProtocolMessageType('Output', (_message.Message,), dict(\n  DESCRIPTOR = _OUTPUT,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.Output)\n  ))\n_sym_db.RegisterMessage(Output)\n\nPaymentDetails = _reflection.GeneratedProtocolMessageType('PaymentDetails', (_message.Message,), dict(\n  DESCRIPTOR = _PAYMENTDETAILS,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.PaymentDetails)\n  ))\n_sym_db.RegisterMessage(PaymentDetails)\n\nPaymentRequest = _reflection.GeneratedProtocolMessageType('PaymentRequest', (_message.Message,), dict(\n  DESCRIPTOR = _PAYMENTREQUEST,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.PaymentRequest)\n  ))\n_sym_db.RegisterMessage(PaymentRequest)\n\nX509Certificates = _reflection.GeneratedProtocolMessageType('X509Certificates', (_message.Message,), dict(\n  DESCRIPTOR = _X509CERTIFICATES,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.X509Certificates)\n  ))\n_sym_db.RegisterMessage(X509Certificates)\n\nPayment = _reflection.GeneratedProtocolMessageType('Payment', (_message.Message,), dict(\n  DESCRIPTOR = _PAYMENT,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.Payment)\n  ))\n_sym_db.RegisterMessage(Payment)\n\nPaymentACK = _reflection.GeneratedProtocolMessageType('PaymentACK', (_message.Message,), dict(\n  DESCRIPTOR = _PAYMENTACK,\n  __module__ = 'paymentrequest_pb2'\n  # @@protoc_insertion_point(class_scope:payments.PaymentACK)\n  ))\n_sym_db.RegisterMessage(PaymentACK)\n\n\nDESCRIPTOR.has_options = True\nDESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\\n\\036org.bitcoin.protocols.paymentsB\\006Protos'))\n# @@protoc_insertion_point(module_scope)\n"
  },
  {
    "path": "lib/pem.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\n# This module uses code from TLSLlite\n# TLSLite Author: Trevor Perrin)\n\n\nimport binascii\n\nfrom .x509 import ASN1_Node, bytestr_to_int, decode_OID\n\n\ndef a2b_base64(s):\n    try:\n        b = bytearray(binascii.a2b_base64(s))\n    except Exception as e:\n        raise SyntaxError(\"base64 error: %s\" % e)\n    return b\n\ndef b2a_base64(b):\n    return binascii.b2a_base64(b)\n\n\ndef dePem(s, name):\n    \"\"\"Decode a PEM string into a bytearray of its payload.\n    \n    The input must contain an appropriate PEM prefix and postfix\n    based on the input name string, e.g. for name=\"CERTIFICATE\":\n\n    -----BEGIN CERTIFICATE-----\n    MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL\n    ...\n    KoZIhvcNAQEFBQADAwA5kw==\n    -----END CERTIFICATE-----    \n\n    The first such PEM block in the input will be found, and its\n    payload will be base64 decoded and returned.\n    \"\"\"\n    prefix  = \"-----BEGIN %s-----\" % name\n    postfix = \"-----END %s-----\" % name    \n    start = s.find(prefix)\n    if start == -1:\n        raise SyntaxError(\"Missing PEM prefix\")\n    end = s.find(postfix, start+len(prefix))\n    if end == -1:\n        raise SyntaxError(\"Missing PEM postfix\")\n    s = s[start+len(\"-----BEGIN %s-----\" % name) : end]\n    retBytes = a2b_base64(s) # May raise SyntaxError\n    return retBytes\n\ndef dePemList(s, name):\n    \"\"\"Decode a sequence of PEM blocks into a list of bytearrays.\n\n    The input must contain any number of PEM blocks, each with the appropriate\n    PEM prefix and postfix based on the input name string, e.g. for\n    name=\"TACK BREAK SIG\".  Arbitrary text can appear between and before and\n    after the PEM blocks.  For example:\n\n    \" Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK\n    BREAK SIG-----\n    ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv\n    YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm\n    SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK\n    BREAK SIG----- Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:11Z\n    -----BEGIN TACK BREAK SIG-----\n    ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv\n    YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM\n    +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK\n    BREAK SIG----- \"\n    \n    All such PEM blocks will be found, decoded, and return in an ordered list\n    of bytearrays, which may have zero elements if not PEM blocks are found.\n     \"\"\"\n    bList = []\n    prefix  = \"-----BEGIN %s-----\" % name\n    postfix = \"-----END %s-----\" % name\n    while 1:\n        start = s.find(prefix)\n        if start == -1:\n            return bList\n        end = s.find(postfix, start+len(prefix))\n        if end == -1:\n            raise SyntaxError(\"Missing PEM postfix\")\n        s2 = s[start+len(prefix) : end]\n        retBytes = a2b_base64(s2) # May raise SyntaxError\n        bList.append(retBytes)\n        s = s[end+len(postfix) : ]\n\ndef pem(b, name):\n    \"\"\"Encode a payload bytearray into a PEM string.\n    \n    The input will be base64 encoded, then wrapped in a PEM prefix/postfix\n    based on the name string, e.g. for name=\"CERTIFICATE\":\n    \n    -----BEGIN CERTIFICATE-----\n    MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL\n    ...\n    KoZIhvcNAQEFBQADAwA5kw==\n    -----END CERTIFICATE-----    \n    \"\"\"\n    s1 = b2a_base64(b)[:-1] # remove terminating \\n\n    s2 = b\"\"\n    while s1:\n        s2 += s1[:64] + b\"\\n\"\n        s1 = s1[64:]\n    s = (\"-----BEGIN %s-----\\n\" % name).encode('ascii') + s2 + \\\n        (\"-----END %s-----\\n\" % name).encode('ascii')\n    return s\n\ndef pemSniff(inStr, name):\n    searchStr = \"-----BEGIN %s-----\" % name\n    return searchStr in inStr\n\n\ndef parse_private_key(s):\n    \"\"\"Parse a string containing a PEM-encoded <privateKey>.\"\"\"\n    if pemSniff(s, \"PRIVATE KEY\"):\n        bytes = dePem(s, \"PRIVATE KEY\")\n        return _parsePKCS8(bytes)\n    elif pemSniff(s, \"RSA PRIVATE KEY\"):\n        bytes = dePem(s, \"RSA PRIVATE KEY\")\n        return _parseSSLeay(bytes)\n    else:\n        raise SyntaxError(\"Not a PEM private key file\")\n\n\ndef _parsePKCS8(_bytes):\n    s = ASN1_Node(_bytes)\n    root = s.root()\n    version_node = s.first_child(root)\n    version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER'))\n    if version != 0:\n        raise SyntaxError(\"Unrecognized PKCS8 version\")\n    rsaOID_node = s.next_node(version_node)\n    ii = s.first_child(rsaOID_node)\n    rsaOID = decode_OID(s.get_value_of_type(ii, 'OBJECT IDENTIFIER'))\n    if rsaOID != '1.2.840.113549.1.1.1':\n        raise SyntaxError(\"Unrecognized AlgorithmIdentifier\")\n    privkey_node = s.next_node(rsaOID_node)\n    value = s.get_value_of_type(privkey_node, 'OCTET STRING')\n    return _parseASN1PrivateKey(value)\n\n\ndef _parseSSLeay(bytes):\n    return _parseASN1PrivateKey(ASN1_Node(bytes))\n\n\ndef bytesToNumber(s):\n    return int(binascii.hexlify(s), 16)\n\n\ndef _parseASN1PrivateKey(s):\n    s = ASN1_Node(s)\n    root = s.root()\n    version_node = s.first_child(root)\n    version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER'))\n    if version != 0:\n        raise SyntaxError(\"Unrecognized RSAPrivateKey version\")\n    n = s.next_node(version_node)\n    e = s.next_node(n)\n    d = s.next_node(e)\n    p = s.next_node(d)\n    q = s.next_node(p)\n    dP = s.next_node(q)\n    dQ = s.next_node(dP)\n    qInv = s.next_node(dQ)\n    return list(map(lambda x: bytesToNumber(s.get_value_of_type(x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv]))\n\n"
  },
  {
    "path": "lib/plot.py",
    "content": "from PyQt5.QtGui import *\nfrom electrum.i18n import _\n\n\nimport datetime\nfrom collections import defaultdict\nfrom electrum.bitcoin import COIN\n\nimport matplotlib\nmatplotlib.use('Qt5Agg')\nimport matplotlib.pyplot as plt\nimport matplotlib.dates as md\nfrom matplotlib.patches import Ellipse\nfrom matplotlib.offsetbox import AnchoredOffsetbox, TextArea, DrawingArea, HPacker\n\n\ndef plot_history(wallet, history):\n    hist_in = defaultdict(int)\n    hist_out = defaultdict(int)\n    for item in history:\n        tx_hash, height, confirmations, timestamp, value, balance = item\n        if not confirmations:\n            continue\n        if timestamp is None:\n            continue\n        value = value*1./COIN\n        date = datetime.datetime.fromtimestamp(timestamp)\n        datenum = int(md.date2num(datetime.date(date.year, date.month, 1)))\n        if value > 0:\n            hist_in[datenum] += value\n        else:\n            hist_out[datenum] -= value\n\n    f, axarr = plt.subplots(2, sharex=True)\n    plt.subplots_adjust(bottom=0.2)\n    plt.xticks( rotation=25 )\n    ax = plt.gca()\n    plt.ylabel('BTC')\n    plt.xlabel('Month')\n    xfmt = md.DateFormatter('%Y-%m-%d')\n    ax.xaxis.set_major_formatter(xfmt)\n    axarr[0].set_title('Monthly Volume')\n    xfmt = md.DateFormatter('%Y-%m')\n    ax.xaxis.set_major_formatter(xfmt)\n    width = 20\n    dates, values = zip(*sorted(hist_in.items()))\n    r1 = axarr[0].bar(dates, values, width, label='incoming')\n    axarr[0].legend(loc='upper left')\n    dates_values = list(zip(*sorted(hist_out.items())))\n    if dates_values and len(dates_values) == 2:\n        dates, values = dates_values\n        r2 = axarr[1].bar(dates, values, width, color='r', label='outgoing')\n        axarr[1].legend(loc='upper left')\n    return plt\n"
  },
  {
    "path": "lib/plugins.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom collections import namedtuple\nimport traceback\nimport sys\nimport os\nimport imp\nimport pkgutil\nimport time\nimport threading\n\nfrom .util import print_error\nfrom .i18n import _\nfrom .util import profiler, PrintError, DaemonThread, UserCancelled, ThreadJob\nfrom . import bitcoin\n\nplugin_loaders = {}\nhook_names = set()\nhooks = {}\n\n\nclass Plugins(DaemonThread):\n\n    @profiler\n    def __init__(self, config, is_local, gui_name):\n        DaemonThread.__init__(self)\n        if is_local:\n            find = imp.find_module('plugins')\n            plugins = imp.load_module('electrum_plugins', *find)\n        else:\n            plugins = __import__('electrum_plugins')\n        self.pkgpath = os.path.dirname(plugins.__file__)\n        self.config = config\n        self.hw_wallets = {}\n        self.plugins = {}\n        self.gui_name = gui_name\n        self.descriptions = {}\n        self.device_manager = DeviceMgr(config)\n        self.load_plugins()\n        self.add_jobs(self.device_manager.thread_jobs())\n        self.start()\n\n    def load_plugins(self):\n        for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):\n            # do not load deprecated plugins\n            if name in ['plot', 'exchange_rate']:\n                continue\n            m = loader.find_module(name).load_module(name)\n            d = m.__dict__\n            gui_good = self.gui_name in d.get('available_for', [])\n            if not gui_good:\n                continue\n            details = d.get('registers_wallet_type')\n            if details:\n                self.register_wallet_type(name, gui_good, details)\n            details = d.get('registers_keystore')\n            if details:\n                self.register_keystore(name, gui_good, details)\n            self.descriptions[name] = d\n            if not d.get('requires_wallet_type') and self.config.get('use_' + name):\n                try:\n                    self.load_plugin(name)\n                except BaseException as e:\n                    traceback.print_exc(file=sys.stdout)\n                    self.print_error(\"cannot initialize plugin %s:\" % name, str(e))\n\n    def get(self, name):\n        return self.plugins.get(name)\n\n    def count(self):\n        return len(self.plugins)\n\n    def load_plugin(self, name):\n        if name in self.plugins:\n            return self.plugins[name]\n        full_name = 'electrum_plugins.' + name + '.' + self.gui_name\n        loader = pkgutil.find_loader(full_name)\n        if not loader:\n            raise RuntimeError(\"%s implementation for %s plugin not found\"\n                               % (self.gui_name, name))\n        p = loader.load_module(full_name)\n        plugin = p.Plugin(self, self.config, name)\n        self.add_jobs(plugin.thread_jobs())\n        self.plugins[name] = plugin\n        self.print_error(\"loaded\", name)\n        return plugin\n\n    def close_plugin(self, plugin):\n        self.remove_jobs(plugin.thread_jobs())\n\n    def enable(self, name):\n        self.config.set_key('use_' + name, True, True)\n        p = self.get(name)\n        if p:\n            return p\n        return self.load_plugin(name)\n\n    def disable(self, name):\n        self.config.set_key('use_' + name, False, True)\n        p = self.get(name)\n        if not p:\n            return\n        self.plugins.pop(name)\n        p.close()\n        self.print_error(\"closed\", name)\n\n    def toggle(self, name):\n        p = self.get(name)\n        return self.disable(name) if p else self.enable(name)\n\n    def is_available(self, name, w):\n        d = self.descriptions.get(name)\n        if not d:\n            return False\n        deps = d.get('requires', [])\n        for dep, s in deps:\n            try:\n                __import__(dep)\n            except ImportError:\n                return False\n        requires = d.get('requires_wallet_type', [])\n        return not requires or w.wallet_type in requires\n\n    def get_hardware_support(self):\n        out = []\n        for name, (gui_good, details) in self.hw_wallets.items():\n            if gui_good:\n                try:\n                    p = self.get_plugin(name)\n                    if p.is_enabled():\n                        out.append([name, details[2], p])\n                except:\n                    traceback.print_exc()\n                    self.print_error(\"cannot load plugin for:\", name)\n        return out\n\n    def register_wallet_type(self, name, gui_good, wallet_type):\n        from .wallet import register_wallet_type, register_constructor\n        self.print_error(\"registering wallet type\", (wallet_type, name))\n        def loader():\n            plugin = self.get_plugin(name)\n            register_constructor(wallet_type, plugin.wallet_class)\n        register_wallet_type(wallet_type)\n        plugin_loaders[wallet_type] = loader\n\n    def register_keystore(self, name, gui_good, details):\n        from .keystore import register_keystore\n        def dynamic_constructor(d):\n            return self.get_plugin(name).keystore_class(d)\n        if details[0] == 'hardware':\n            self.hw_wallets[name] = (gui_good, details)\n            self.print_error(\"registering hardware %s: %s\" %(name, details))\n            register_keystore(details[1], dynamic_constructor)\n\n    def get_plugin(self, name):\n        if not name in self.plugins:\n            self.load_plugin(name)\n        return self.plugins[name]\n\n    def run(self):\n        while self.is_running():\n            time.sleep(0.1)\n            self.run_jobs()\n        self.on_stop()\n\n\ndef hook(func):\n    hook_names.add(func.__name__)\n    return func\n\ndef run_hook(name, *args):\n    results = []\n    f_list = hooks.get(name, [])\n    for p, f in f_list:\n        if p.is_enabled():\n            try:\n                r = f(*args)\n            except Exception:\n                print_error(\"Plugin error\")\n                traceback.print_exc(file=sys.stdout)\n                r = False\n            if r:\n                results.append(r)\n\n    if results:\n        assert len(results) == 1, results\n        return results[0]\n\n\nclass BasePlugin(PrintError):\n\n    def __init__(self, parent, config, name):\n        self.parent = parent  # The plugins object\n        self.name = name\n        self.config = config\n        self.wallet = None\n        # add self to hooks\n        for k in dir(self):\n            if k in hook_names:\n                l = hooks.get(k, [])\n                l.append((self, getattr(self, k)))\n                hooks[k] = l\n\n    def diagnostic_name(self):\n        return self.name\n\n    def __str__(self):\n        return self.name\n\n    def close(self):\n        # remove self from hooks\n        for k in dir(self):\n            if k in hook_names:\n                l = hooks.get(k, [])\n                l.remove((self, getattr(self, k)))\n                hooks[k] = l\n        self.parent.close_plugin(self)\n        self.on_close()\n\n    def on_close(self):\n        pass\n\n    def requires_settings(self):\n        return False\n\n    def thread_jobs(self):\n        return []\n\n    def is_enabled(self):\n        return self.is_available() and self.config.get('use_'+self.name) is True\n\n    def is_available(self):\n        return True\n\n    def can_user_disable(self):\n        return True\n\n    def settings_dialog(self):\n        pass\n\n\nclass DeviceNotFoundError(Exception):\n    pass\n\nclass DeviceUnpairableError(Exception):\n    pass\n\nDevice = namedtuple(\"Device\", \"path interface_number id_ product_key usage_page\")\nDeviceInfo = namedtuple(\"DeviceInfo\", \"device label initialized\")\n\nclass DeviceMgr(ThreadJob, PrintError):\n    '''Manages hardware clients.  A client communicates over a hardware\n    channel with the device.\n\n    In addition to tracking device HID IDs, the device manager tracks\n    hardware wallets and manages wallet pairing.  A HID ID may be\n    paired with a wallet when it is confirmed that the hardware device\n    matches the wallet, i.e. they have the same master public key.  A\n    HID ID can be unpaired if e.g. it is wiped.\n\n    Because of hotplugging, a wallet must request its client\n    dynamically each time it is required, rather than caching it\n    itself.\n\n    The device manager is shared across plugins, so just one place\n    does hardware scans when needed.  By tracking HID IDs, if a device\n    is plugged into a different port the wallet is automatically\n    re-paired.\n\n    Wallets are informed on connect / disconnect events.  It must\n    implement connected(), disconnected() callbacks.  Being connected\n    implies a pairing.  Callbacks can happen in any thread context,\n    and we do them without holding the lock.\n\n    Confusingly, the HID ID (serial number) reported by the HID system\n    doesn't match the device ID reported by the device itself.  We use\n    the HID IDs.\n\n    This plugin is thread-safe.  Currently only devices supported by\n    hidapi are implemented.'''\n\n    def __init__(self, config):\n        super(DeviceMgr, self).__init__()\n        # Keyed by xpub.  The value is the device id\n        # has been paired, and None otherwise.\n        self.xpub_ids = {}\n        # A list of clients.  The key is the client, the value is\n        # a (path, id_) pair.\n        self.clients = {}\n        # What we recognise.  Each entry is a (vendor_id, product_id)\n        # pair.\n        self.recognised_hardware = set()\n        # For synchronization\n        self.lock = threading.RLock()\n        self.hid_lock = threading.RLock()\n        self.config = config\n\n    def thread_jobs(self):\n        # Thread job to handle device timeouts\n        return [self]\n\n    def run(self):\n        '''Handle device timeouts.  Runs in the context of the Plugins\n        thread.'''\n        with self.lock:\n            clients = list(self.clients.keys())\n        cutoff = time.time() - self.config.get_session_timeout()\n        for client in clients:\n            client.timeout(cutoff)\n\n    def register_devices(self, device_pairs):\n        for pair in device_pairs:\n            self.recognised_hardware.add(pair)\n\n    def create_client(self, device, handler, plugin):\n        # Get from cache first\n        client = self.client_lookup(device.id_)\n        if client:\n            return client\n        client = plugin.create_client(device, handler)\n        if client:\n            self.print_error(\"Registering\", client)\n            with self.lock:\n                self.clients[client] = (device.path, device.id_)\n        return client\n\n    def xpub_id(self, xpub):\n        with self.lock:\n            return self.xpub_ids.get(xpub)\n\n    def xpub_by_id(self, id_):\n        with self.lock:\n            for xpub, xpub_id in self.xpub_ids.items():\n                if xpub_id == id_:\n                    return xpub\n            return None\n\n    def unpair_xpub(self, xpub):\n        with self.lock:\n            if not xpub in self.xpub_ids:\n                return\n            _id = self.xpub_ids.pop(xpub)\n        client = self.client_lookup(_id)\n        self.clients.pop(client, None)\n        if client:\n            client.close()\n\n    def unpair_id(self, id_):\n        xpub = self.xpub_by_id(id_)\n        if xpub:\n            self.unpair_xpub(xpub)\n\n    def pair_xpub(self, xpub, id_):\n        with self.lock:\n            self.xpub_ids[xpub] = id_\n\n    def client_lookup(self, id_):\n        with self.lock:\n            for client, (path, client_id) in self.clients.items():\n                if client_id == id_:\n                    return client\n        return None\n\n    def client_by_id(self, id_):\n        '''Returns a client for the device ID if one is registered.  If\n        a device is wiped or in bootloader mode pairing is impossible;\n        in such cases we communicate by device ID and not wallet.'''\n        self.scan_devices()\n        return self.client_lookup(id_)\n\n    def client_for_keystore(self, plugin, handler, keystore, force_pair):\n        self.print_error(\"getting client for keystore\")\n        if handler is None:\n            raise BaseException(_(\"Handler not found for\") + ' ' + plugin.name + '\\n' + _(\"A library is probably missing.\"))\n        handler.update_status(False)\n        devices = self.scan_devices()\n        xpub = keystore.xpub\n        derivation = keystore.get_derivation()\n        client = self.client_by_xpub(plugin, xpub, handler, devices)\n        if client is None and force_pair:\n            info = self.select_device(plugin, handler, keystore, devices)\n            client = self.force_pair_xpub(plugin, handler, info, xpub, derivation, devices)\n        if client:\n            handler.update_status(True)\n        self.print_error(\"end client for keystore\")\n        return client\n\n    def client_by_xpub(self, plugin, xpub, handler, devices):\n        _id = self.xpub_id(xpub)\n        client = self.client_lookup(_id)\n        if client:\n            # An unpaired client might have another wallet's handler\n            # from a prior scan.  Replace to fix dialog parenting.\n            client.handler = handler\n            return client\n\n        for device in devices:\n            if device.id_ == _id:\n                return self.create_client(device, handler, plugin)\n\n\n    def force_pair_xpub(self, plugin, handler, info, xpub, derivation, devices):\n        # The wallet has not been previously paired, so let the user\n        # choose an unpaired device and compare its first address.\n        xtype = bitcoin.xpub_type(xpub)\n        client = self.client_lookup(info.device.id_)\n        if client and client.is_pairable():\n            # See comment above for same code\n            client.handler = handler\n            # This will trigger a PIN/passphrase entry request\n            try:\n                client_xpub = client.get_xpub(derivation, xtype)\n            except (UserCancelled, RuntimeError):\n                 # Bad / cancelled PIN / passphrase\n                client_xpub = None\n            if client_xpub == xpub:\n                self.pair_xpub(xpub, info.device.id_)\n                return client\n\n        # The user input has wrong PIN or passphrase, or cancelled input,\n        # or it is not pairable\n        raise DeviceUnpairableError(\n            _('Electrum cannot pair with your %s.\\n\\n'\n              'Before you request bitcoins to be sent to addresses in this '\n              'wallet, ensure you can pair with your device, or that you have '\n              'its seed (and passphrase, if any).  Otherwise all bitcoins you '\n              'receive will be unspendable.') % plugin.device)\n\n    def unpaired_device_infos(self, handler, plugin, devices=None):\n        '''Returns a list of DeviceInfo objects: one for each connected,\n        unpaired device accepted by the plugin.'''\n        if devices is None:\n            devices = self.scan_devices()\n        devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]\n        infos = []\n        for device in devices:\n            if not device.product_key in plugin.DEVICE_IDS:\n                continue\n            client = self.create_client(device, handler, plugin)\n            if not client:\n                continue\n            infos.append(DeviceInfo(device, client.label(), client.is_initialized()))\n\n        return infos\n\n    def select_device(self, plugin, handler, keystore, devices=None):\n        '''Ask the user to select a device to use if there is more than one,\n        and return the DeviceInfo for the device.'''\n        while True:\n            infos = self.unpaired_device_infos(handler, plugin, devices)\n            if infos:\n                break\n            msg = _('Please insert your %s.  Verify the cable is '\n                    'connected and that no other application is using it.\\n\\n'\n                    'Try to connect again?') % plugin.device\n            if not handler.yes_no_question(msg):\n                raise UserCancelled()\n            devices = None\n        if len(infos) == 1:\n            return infos[0]\n        # select device by label\n        for info in infos:\n            if info.label == keystore.label:\n                return info\n        msg = _(\"Please select which %s device to use:\") % plugin.device\n        descriptions = [info.label + ' (%s)'%(_(\"initialized\") if info.initialized else _(\"wiped\")) for info in infos]\n        c = handler.query_choice(msg, descriptions)\n        if c is None:\n            raise UserCancelled()\n        info = infos[c]\n        # save new label\n        keystore.set_label(info.label)\n        handler.win.wallet.save_keystore()\n        return info\n\n    def scan_devices(self):\n        # All currently supported hardware libraries use hid, so we\n        # assume it here.  This can be easily abstracted if necessary.\n        # Note this import must be local so those without hardware\n        # wallet libraries are not affected.\n        import hid\n        self.print_error(\"scanning devices...\")\n        with self.hid_lock:\n            hid_list = hid.enumerate(0, 0)\n        # First see what's connected that we know about\n        devices = []\n        for d in hid_list:\n            product_key = (d['vendor_id'], d['product_id'])\n            if product_key in self.recognised_hardware:\n                # Older versions of hid don't provide interface_number\n                interface_number = d.get('interface_number', -1)\n                usage_page = d['usage_page']\n                id_ = d['serial_number']\n                if len(id_) == 0:\n                    id_ = str(d['path'])\n                id_ += str(interface_number) + str(usage_page)\n                devices.append(Device(d['path'], interface_number,\n                                      id_, product_key, usage_page))\n\n        # Now find out what was disconnected\n        pairs = [(dev.path, dev.id_) for dev in devices]\n        disconnected_ids = []\n        with self.lock:\n            connected = {}\n            for client, pair in self.clients.items():\n                if pair in pairs:\n                    connected[client] = pair\n                else:\n                    disconnected_ids.append(pair[1])\n            self.clients = connected\n\n        # Unpair disconnected devices\n        for id_ in disconnected_ids:\n            self.unpair_id(id_)\n\n        return devices\n"
  },
  {
    "path": "lib/qrscanner.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport os\nimport sys\nimport ctypes\n\nif sys.platform == 'darwin':\n    name = 'libzbar.dylib'\nelif sys.platform == 'windows':\n    name = 'libzbar.dll'\nelse:\n    name = 'libzbar.so.0'\n\ntry:\n    libzbar = ctypes.cdll.LoadLibrary(name)\nexcept OSError:\n    libzbar = None\n\n\ndef scan_barcode(device='', timeout=-1, display=True, threaded=False):\n    if libzbar is None:\n        raise RuntimeError(\"Cannot start QR scanner; zbar not available.\")\n    libzbar.zbar_symbol_get_data.restype = ctypes.c_char_p\n    libzbar.zbar_processor_create.restype = ctypes.POINTER(ctypes.c_int)\n    libzbar.zbar_processor_get_results.restype = ctypes.POINTER(ctypes.c_int)\n    libzbar.zbar_symbol_set_first_symbol.restype = ctypes.POINTER(ctypes.c_int)\n    proc = libzbar.zbar_processor_create(threaded)\n    libzbar.zbar_processor_request_size(proc, 640, 480)\n    if libzbar.zbar_processor_init(proc, device.encode('utf-8'), display) != 0:\n        raise RuntimeError(\"Can not start QR scanner; initialization failed.\")\n    libzbar.zbar_processor_set_visible(proc)\n    if libzbar.zbar_process_one(proc, timeout):\n        symbols = libzbar.zbar_processor_get_results(proc)\n    else:\n        symbols = None\n    libzbar.zbar_processor_destroy(proc)\n    if symbols is None:\n        return\n    if not libzbar.zbar_symbol_set_get_size(symbols):\n        return\n    symbol = libzbar.zbar_symbol_set_first_symbol(symbols)\n    data = libzbar.zbar_symbol_get_data(symbol)\n    return data.decode('utf8')\n\ndef _find_system_cameras():\n    device_root = \"/sys/class/video4linux\"\n    devices = {} # Name -> device\n    if os.path.exists(device_root):\n        for device in os.listdir(device_root):\n            try:\n                with open(os.path.join(device_root, device, 'name')) as f:\n                    name = f.read()\n            except IOError:\n                continue\n            name = name.strip('\\n')\n            devices[name] = os.path.join(\"/dev\", device)\n    return devices\n\n\nif __name__ == \"__main__\":\n    print(scan_barcode())\n"
  },
  {
    "path": "lib/ripemd.py",
    "content": "## ripemd.py - pure Python implementation of the RIPEMD-160 algorithm.\r\n## Bjorn Edstrom <be@bjrn.se> 16 december 2007.\r\n##\r\n## Copyrights\r\n## ==========\r\n##\r\n## This code is a derived from an implementation by Markus Friedl which is\r\n## subject to the following license. This Python implementation is not\r\n## subject to any other license.\r\n##\r\n##/*\r\n## * Copyright (c) 2001 Markus Friedl.  All rights reserved.\r\n## *\r\n## * Redistribution and use in source and binary forms, with or without\r\n## * modification, are permitted provided that the following conditions\r\n## * are met:\r\n## * 1. Redistributions of source code must retain the above copyright\r\n## *    notice, this list of conditions and the following disclaimer.\r\n## * 2. Redistributions in binary form must reproduce the above copyright\r\n## *    notice, this list of conditions and the following disclaimer in the\r\n## *    documentation and/or other materials provided with the distribution.\r\n## *\r\n## * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\r\n## * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\r\n## * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\r\n## * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\r\n## * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\r\n## * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r\n## * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r\n## * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r\n## * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\r\n## * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n## */\r\n##/*\r\n## * Preneel, Bosselaers, Dobbertin, \"The Cryptographic Hash Function RIPEMD-160\",\r\n## * RSA Laboratories, CryptoBytes, Volume 3, Number 2, Autumn 1997,\r\n## * ftp://ftp.rsasecurity.com/pub/cryptobytes/crypto3n2.pdf\r\n## */\r\n\r\n#block_size = 1\r\ndigest_size = 20\r\ndigestsize = 20\r\n\r\nclass RIPEMD160:\r\n    \"\"\"Return a new RIPEMD160 object. An optional string argument\r\n    may be provided; if present, this string will be automatically\r\n    hashed.\"\"\"\r\n\r\n    def __init__(self, arg=None):\r\n        self.ctx = RMDContext()\r\n        if arg:\r\n            self.update(arg)\r\n        self.dig = None\r\n\r\n    def update(self, arg):\r\n        \"\"\"update(arg)\"\"\"\r\n        RMD160Update(self.ctx, arg, len(arg))\r\n        self.dig = None\r\n\r\n    def digest(self):\r\n        \"\"\"digest()\"\"\"\r\n        if self.dig:\r\n            return self.dig\r\n        ctx = self.ctx.copy()\r\n        self.dig = RMD160Final(self.ctx)\r\n        self.ctx = ctx\r\n        return self.dig\r\n\r\n    def hexdigest(self):\r\n        \"\"\"hexdigest()\"\"\"\r\n        dig = self.digest()\r\n        hex_digest = ''\r\n        for d in dig:\r\n            hex_digest += '%02x' % d\r\n        return hex_digest\r\n\r\n    def copy(self):\r\n        \"\"\"copy()\"\"\"\r\n        import copy\r\n        return copy.deepcopy(self)\r\n\r\n\r\n\r\ndef new(arg=None):\r\n    \"\"\"Return a new RIPEMD160 object. An optional string argument\r\n    may be provided; if present, this string will be automatically\r\n    hashed.\"\"\"\r\n    return RIPEMD160(arg)\r\n\r\n\r\n\r\n#\r\n# Private.\r\n#\r\n\r\nclass RMDContext:\r\n    def __init__(self):\r\n        self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE,\r\n                      0x10325476, 0xC3D2E1F0] # uint32\r\n        self.count = 0 # uint64\r\n        self.buffer = [0]*64 # uchar\r\n    def copy(self):\r\n        ctx = RMDContext()\r\n        ctx.state = self.state[:]\r\n        ctx.count = self.count\r\n        ctx.buffer = self.buffer[:]\r\n        return ctx\r\n\r\nK0 = 0x00000000\r\nK1 = 0x5A827999\r\nK2 = 0x6ED9EBA1\r\nK3 = 0x8F1BBCDC\r\nK4 = 0xA953FD4E\r\n\r\nKK0 = 0x50A28BE6\r\nKK1 = 0x5C4DD124\r\nKK2 = 0x6D703EF3\r\nKK3 = 0x7A6D76E9\r\nKK4 = 0x00000000\r\n\r\ndef ROL(n, x):\r\n    return ((x << n) & 0xffffffff) | (x >> (32 - n))\r\n\r\ndef F0(x, y, z):\r\n    return x ^ y ^ z\r\n\r\ndef F1(x, y, z):\r\n    return (x & y) | (((~x) % 0x100000000) & z)\r\n\r\ndef F2(x, y, z):\r\n    return (x | ((~y) % 0x100000000)) ^ z\r\n\r\ndef F3(x, y, z):\r\n    return (x & z) | (((~z) % 0x100000000) & y)\r\n\r\ndef F4(x, y, z):\r\n    return x ^ (y | ((~z) % 0x100000000))\r\n\r\ndef R(a, b, c, d, e, Fj, Kj, sj, rj, X):\r\n    a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e\r\n    c = ROL(10, c)\r\n    return a % 0x100000000, c\r\n\r\nPADDING = [0x80] + [0]*63\r\n\r\nimport sys\r\nimport struct\r\n\r\ndef RMD160Transform(state, block): #uint32 state[5], uchar block[64]\r\n    x = [0]*16\r\n    if sys.byteorder == 'little':\r\n        x = struct.unpack('<16L', bytes([x for x in block[0:64]]))\r\n    else:\r\n        raise \"Error!!\"\r\n    a = state[0]\r\n    b = state[1]\r\n    c = state[2]\r\n    d = state[3]\r\n    e = state[4]\r\n\r\n    #/* Round 1 */\r\n    a, c = R(a, b, c, d, e, F0, K0, 11,  0, x);\r\n    e, b = R(e, a, b, c, d, F0, K0, 14,  1, x);\r\n    d, a = R(d, e, a, b, c, F0, K0, 15,  2, x);\r\n    c, e = R(c, d, e, a, b, F0, K0, 12,  3, x);\r\n    b, d = R(b, c, d, e, a, F0, K0,  5,  4, x);\r\n    a, c = R(a, b, c, d, e, F0, K0,  8,  5, x);\r\n    e, b = R(e, a, b, c, d, F0, K0,  7,  6, x);\r\n    d, a = R(d, e, a, b, c, F0, K0,  9,  7, x);\r\n    c, e = R(c, d, e, a, b, F0, K0, 11,  8, x);\r\n    b, d = R(b, c, d, e, a, F0, K0, 13,  9, x);\r\n    a, c = R(a, b, c, d, e, F0, K0, 14, 10, x);\r\n    e, b = R(e, a, b, c, d, F0, K0, 15, 11, x);\r\n    d, a = R(d, e, a, b, c, F0, K0,  6, 12, x);\r\n    c, e = R(c, d, e, a, b, F0, K0,  7, 13, x);\r\n    b, d = R(b, c, d, e, a, F0, K0,  9, 14, x);\r\n    a, c = R(a, b, c, d, e, F0, K0,  8, 15, x); #/* #15 */\r\n    #/* Round 2 */\r\n    e, b = R(e, a, b, c, d, F1, K1,  7,  7, x);\r\n    d, a = R(d, e, a, b, c, F1, K1,  6,  4, x);\r\n    c, e = R(c, d, e, a, b, F1, K1,  8, 13, x);\r\n    b, d = R(b, c, d, e, a, F1, K1, 13,  1, x);\r\n    a, c = R(a, b, c, d, e, F1, K1, 11, 10, x);\r\n    e, b = R(e, a, b, c, d, F1, K1,  9,  6, x);\r\n    d, a = R(d, e, a, b, c, F1, K1,  7, 15, x);\r\n    c, e = R(c, d, e, a, b, F1, K1, 15,  3, x);\r\n    b, d = R(b, c, d, e, a, F1, K1,  7, 12, x);\r\n    a, c = R(a, b, c, d, e, F1, K1, 12,  0, x);\r\n    e, b = R(e, a, b, c, d, F1, K1, 15,  9, x);\r\n    d, a = R(d, e, a, b, c, F1, K1,  9,  5, x);\r\n    c, e = R(c, d, e, a, b, F1, K1, 11,  2, x);\r\n    b, d = R(b, c, d, e, a, F1, K1,  7, 14, x);\r\n    a, c = R(a, b, c, d, e, F1, K1, 13, 11, x);\r\n    e, b = R(e, a, b, c, d, F1, K1, 12,  8, x); #/* #31 */\r\n    #/* Round 3 */\r\n    d, a = R(d, e, a, b, c, F2, K2, 11,  3, x);\r\n    c, e = R(c, d, e, a, b, F2, K2, 13, 10, x);\r\n    b, d = R(b, c, d, e, a, F2, K2,  6, 14, x);\r\n    a, c = R(a, b, c, d, e, F2, K2,  7,  4, x);\r\n    e, b = R(e, a, b, c, d, F2, K2, 14,  9, x);\r\n    d, a = R(d, e, a, b, c, F2, K2,  9, 15, x);\r\n    c, e = R(c, d, e, a, b, F2, K2, 13,  8, x);\r\n    b, d = R(b, c, d, e, a, F2, K2, 15,  1, x);\r\n    a, c = R(a, b, c, d, e, F2, K2, 14,  2, x);\r\n    e, b = R(e, a, b, c, d, F2, K2,  8,  7, x);\r\n    d, a = R(d, e, a, b, c, F2, K2, 13,  0, x);\r\n    c, e = R(c, d, e, a, b, F2, K2,  6,  6, x);\r\n    b, d = R(b, c, d, e, a, F2, K2,  5, 13, x);\r\n    a, c = R(a, b, c, d, e, F2, K2, 12, 11, x);\r\n    e, b = R(e, a, b, c, d, F2, K2,  7,  5, x);\r\n    d, a = R(d, e, a, b, c, F2, K2,  5, 12, x); #/* #47 */\r\n    #/* Round 4 */\r\n    c, e = R(c, d, e, a, b, F3, K3, 11,  1, x);\r\n    b, d = R(b, c, d, e, a, F3, K3, 12,  9, x);\r\n    a, c = R(a, b, c, d, e, F3, K3, 14, 11, x);\r\n    e, b = R(e, a, b, c, d, F3, K3, 15, 10, x);\r\n    d, a = R(d, e, a, b, c, F3, K3, 14,  0, x);\r\n    c, e = R(c, d, e, a, b, F3, K3, 15,  8, x);\r\n    b, d = R(b, c, d, e, a, F3, K3,  9, 12, x);\r\n    a, c = R(a, b, c, d, e, F3, K3,  8,  4, x);\r\n    e, b = R(e, a, b, c, d, F3, K3,  9, 13, x);\r\n    d, a = R(d, e, a, b, c, F3, K3, 14,  3, x);\r\n    c, e = R(c, d, e, a, b, F3, K3,  5,  7, x);\r\n    b, d = R(b, c, d, e, a, F3, K3,  6, 15, x);\r\n    a, c = R(a, b, c, d, e, F3, K3,  8, 14, x);\r\n    e, b = R(e, a, b, c, d, F3, K3,  6,  5, x);\r\n    d, a = R(d, e, a, b, c, F3, K3,  5,  6, x);\r\n    c, e = R(c, d, e, a, b, F3, K3, 12,  2, x); #/* #63 */\r\n    #/* Round 5 */\r\n    b, d = R(b, c, d, e, a, F4, K4,  9,  4, x);\r\n    a, c = R(a, b, c, d, e, F4, K4, 15,  0, x);\r\n    e, b = R(e, a, b, c, d, F4, K4,  5,  5, x);\r\n    d, a = R(d, e, a, b, c, F4, K4, 11,  9, x);\r\n    c, e = R(c, d, e, a, b, F4, K4,  6,  7, x);\r\n    b, d = R(b, c, d, e, a, F4, K4,  8, 12, x);\r\n    a, c = R(a, b, c, d, e, F4, K4, 13,  2, x);\r\n    e, b = R(e, a, b, c, d, F4, K4, 12, 10, x);\r\n    d, a = R(d, e, a, b, c, F4, K4,  5, 14, x);\r\n    c, e = R(c, d, e, a, b, F4, K4, 12,  1, x);\r\n    b, d = R(b, c, d, e, a, F4, K4, 13,  3, x);\r\n    a, c = R(a, b, c, d, e, F4, K4, 14,  8, x);\r\n    e, b = R(e, a, b, c, d, F4, K4, 11, 11, x);\r\n    d, a = R(d, e, a, b, c, F4, K4,  8,  6, x);\r\n    c, e = R(c, d, e, a, b, F4, K4,  5, 15, x);\r\n    b, d = R(b, c, d, e, a, F4, K4,  6, 13, x); #/* #79 */\r\n\r\n    aa = a;\r\n    bb = b;\r\n    cc = c;\r\n    dd = d;\r\n    ee = e;\r\n\r\n    a = state[0]\r\n    b = state[1]\r\n    c = state[2]\r\n    d = state[3]\r\n    e = state[4]\r\n\r\n    #/* Parallel round 1 */\r\n    a, c = R(a, b, c, d, e, F4, KK0,  8,  5, x)\r\n    e, b = R(e, a, b, c, d, F4, KK0,  9, 14, x)\r\n    d, a = R(d, e, a, b, c, F4, KK0,  9,  7, x)\r\n    c, e = R(c, d, e, a, b, F4, KK0, 11,  0, x)\r\n    b, d = R(b, c, d, e, a, F4, KK0, 13,  9, x)\r\n    a, c = R(a, b, c, d, e, F4, KK0, 15,  2, x)\r\n    e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x)\r\n    d, a = R(d, e, a, b, c, F4, KK0,  5,  4, x)\r\n    c, e = R(c, d, e, a, b, F4, KK0,  7, 13, x)\r\n    b, d = R(b, c, d, e, a, F4, KK0,  7,  6, x)\r\n    a, c = R(a, b, c, d, e, F4, KK0,  8, 15, x)\r\n    e, b = R(e, a, b, c, d, F4, KK0, 11,  8, x)\r\n    d, a = R(d, e, a, b, c, F4, KK0, 14,  1, x)\r\n    c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x)\r\n    b, d = R(b, c, d, e, a, F4, KK0, 12,  3, x)\r\n    a, c = R(a, b, c, d, e, F4, KK0,  6, 12, x) #/* #15 */\r\n    #/* Parallel round 2 */\r\n    e, b = R(e, a, b, c, d, F3, KK1,  9,  6, x)\r\n    d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x)\r\n    c, e = R(c, d, e, a, b, F3, KK1, 15,  3, x)\r\n    b, d = R(b, c, d, e, a, F3, KK1,  7,  7, x)\r\n    a, c = R(a, b, c, d, e, F3, KK1, 12,  0, x)\r\n    e, b = R(e, a, b, c, d, F3, KK1,  8, 13, x)\r\n    d, a = R(d, e, a, b, c, F3, KK1,  9,  5, x)\r\n    c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x)\r\n    b, d = R(b, c, d, e, a, F3, KK1,  7, 14, x)\r\n    a, c = R(a, b, c, d, e, F3, KK1,  7, 15, x)\r\n    e, b = R(e, a, b, c, d, F3, KK1, 12,  8, x)\r\n    d, a = R(d, e, a, b, c, F3, KK1,  7, 12, x)\r\n    c, e = R(c, d, e, a, b, F3, KK1,  6,  4, x)\r\n    b, d = R(b, c, d, e, a, F3, KK1, 15,  9, x)\r\n    a, c = R(a, b, c, d, e, F3, KK1, 13,  1, x)\r\n    e, b = R(e, a, b, c, d, F3, KK1, 11,  2, x) #/* #31 */\r\n    #/* Parallel round 3 */\r\n    d, a = R(d, e, a, b, c, F2, KK2,  9, 15, x)\r\n    c, e = R(c, d, e, a, b, F2, KK2,  7,  5, x)\r\n    b, d = R(b, c, d, e, a, F2, KK2, 15,  1, x)\r\n    a, c = R(a, b, c, d, e, F2, KK2, 11,  3, x)\r\n    e, b = R(e, a, b, c, d, F2, KK2,  8,  7, x)\r\n    d, a = R(d, e, a, b, c, F2, KK2,  6, 14, x)\r\n    c, e = R(c, d, e, a, b, F2, KK2,  6,  6, x)\r\n    b, d = R(b, c, d, e, a, F2, KK2, 14,  9, x)\r\n    a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x)\r\n    e, b = R(e, a, b, c, d, F2, KK2, 13,  8, x)\r\n    d, a = R(d, e, a, b, c, F2, KK2,  5, 12, x)\r\n    c, e = R(c, d, e, a, b, F2, KK2, 14,  2, x)\r\n    b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x)\r\n    a, c = R(a, b, c, d, e, F2, KK2, 13,  0, x)\r\n    e, b = R(e, a, b, c, d, F2, KK2,  7,  4, x)\r\n    d, a = R(d, e, a, b, c, F2, KK2,  5, 13, x) #/* #47 */\r\n    #/* Parallel round 4 */\r\n    c, e = R(c, d, e, a, b, F1, KK3, 15,  8, x)\r\n    b, d = R(b, c, d, e, a, F1, KK3,  5,  6, x)\r\n    a, c = R(a, b, c, d, e, F1, KK3,  8,  4, x)\r\n    e, b = R(e, a, b, c, d, F1, KK3, 11,  1, x)\r\n    d, a = R(d, e, a, b, c, F1, KK3, 14,  3, x)\r\n    c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x)\r\n    b, d = R(b, c, d, e, a, F1, KK3,  6, 15, x)\r\n    a, c = R(a, b, c, d, e, F1, KK3, 14,  0, x)\r\n    e, b = R(e, a, b, c, d, F1, KK3,  6,  5, x)\r\n    d, a = R(d, e, a, b, c, F1, KK3,  9, 12, x)\r\n    c, e = R(c, d, e, a, b, F1, KK3, 12,  2, x)\r\n    b, d = R(b, c, d, e, a, F1, KK3,  9, 13, x)\r\n    a, c = R(a, b, c, d, e, F1, KK3, 12,  9, x)\r\n    e, b = R(e, a, b, c, d, F1, KK3,  5,  7, x)\r\n    d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x)\r\n    c, e = R(c, d, e, a, b, F1, KK3,  8, 14, x) #/* #63 */\r\n    #/* Parallel round 5 */\r\n    b, d = R(b, c, d, e, a, F0, KK4,  8, 12, x)\r\n    a, c = R(a, b, c, d, e, F0, KK4,  5, 15, x)\r\n    e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x)\r\n    d, a = R(d, e, a, b, c, F0, KK4,  9,  4, x)\r\n    c, e = R(c, d, e, a, b, F0, KK4, 12,  1, x)\r\n    b, d = R(b, c, d, e, a, F0, KK4,  5,  5, x)\r\n    a, c = R(a, b, c, d, e, F0, KK4, 14,  8, x)\r\n    e, b = R(e, a, b, c, d, F0, KK4,  6,  7, x)\r\n    d, a = R(d, e, a, b, c, F0, KK4,  8,  6, x)\r\n    c, e = R(c, d, e, a, b, F0, KK4, 13,  2, x)\r\n    b, d = R(b, c, d, e, a, F0, KK4,  6, 13, x)\r\n    a, c = R(a, b, c, d, e, F0, KK4,  5, 14, x)\r\n    e, b = R(e, a, b, c, d, F0, KK4, 15,  0, x)\r\n    d, a = R(d, e, a, b, c, F0, KK4, 13,  3, x)\r\n    c, e = R(c, d, e, a, b, F0, KK4, 11,  9, x)\r\n    b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */\r\n\r\n    t = (state[1] + cc + d) % 0x100000000;\r\n    state[1] = (state[2] + dd + e) % 0x100000000;\r\n    state[2] = (state[3] + ee + a) % 0x100000000;\r\n    state[3] = (state[4] + aa + b) % 0x100000000;\r\n    state[4] = (state[0] + bb + c) % 0x100000000;\r\n    state[0] = t % 0x100000000;\r\n\r\n    pass\r\n\r\n\r\ndef RMD160Update(ctx, inp, inplen):\r\n    if type(inp) == str:\r\n        inp = [ord(i)&0xff for i in inp]\r\n\r\n    have = (ctx.count // 8) % 64\r\n    need = 64 - have\r\n    ctx.count += 8 * inplen\r\n    off = 0\r\n    if inplen >= need:\r\n        if have:\r\n            for i in range(need):\r\n                ctx.buffer[have+i] = inp[i]\r\n            RMD160Transform(ctx.state, ctx.buffer)\r\n            off = need\r\n            have = 0\r\n        while off + 64 <= inplen:\r\n            RMD160Transform(ctx.state, inp[off:]) #<---\r\n            off += 64\r\n    if off < inplen:\r\n        # memcpy(ctx->buffer + have, input+off, len-off);\r\n        for i in range(inplen - off):\r\n            ctx.buffer[have+i] = inp[off+i]\r\n\r\ndef RMD160Final(ctx):\r\n    size = struct.pack(\"<Q\", ctx.count)\r\n    padlen = 64 - ((ctx.count // 8) % 64)\r\n    if padlen < 1+8:\r\n        padlen += 64\r\n    RMD160Update(ctx, PADDING, padlen-8)\r\n    RMD160Update(ctx, size, 8)\r\n    return struct.pack(\"<5L\", *ctx.state)\r\n\r\n\r\nassert '37f332f68db77bd9d7edd4969571ad671cf9dd3b' == \\\r\n       new(b'The quick brown fox jumps over the lazy dog').hexdigest()\r\nassert '132072df690933835eb8b6ad0b77e7b6f14acad7' == \\\r\n       new(b'The quick brown fox jumps over the lazy cog').hexdigest()\r\nassert '9c1185a5c5e9fc54612808977ee8f548b2258d31' == \\\r\n       new('').hexdigest()\r\n"
  },
  {
    "path": "lib/rsakey.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n# This module uses functions from TLSLite (public domain)\n#\n# TLSLite Authors: \n#   Trevor Perrin\n#   Martin von Loewis - python 3 port\n#   Yngve Pettersen (ported by Paul Sokolovsky) - TLS 1.2\n#\n\n\"\"\"Pure-Python RSA implementation.\"\"\"\n\nimport os\nimport math\nimport hashlib\n\nfrom .pem import *\n\n\ndef SHA1(x):\n    return hashlib.sha1(x).digest()\n\n\n# **************************************************************************\n# PRNG Functions\n# **************************************************************************\n\n# Check that os.urandom works\nimport zlib\nlength = len(zlib.compress(os.urandom(1000)))\nassert(length > 900)\n\ndef getRandomBytes(howMany):\n    b = bytearray(os.urandom(howMany))\n    assert(len(b) == howMany)\n    return b\n\nprngName = \"os.urandom\"\n\n\n# **************************************************************************\n# Converter Functions\n# **************************************************************************\n\ndef bytesToNumber(b):\n    total = 0\n    multiplier = 1\n    for count in range(len(b)-1, -1, -1):\n        byte = b[count]\n        total += multiplier * byte\n        multiplier *= 256\n    return total\n\ndef numberToByteArray(n, howManyBytes=None):\n    \"\"\"Convert an integer into a bytearray, zero-pad to howManyBytes.\n\n    The returned bytearray may be smaller than howManyBytes, but will\n    not be larger.  The returned bytearray will contain a big-endian\n    encoding of the input integer (n).\n    \"\"\"    \n    if howManyBytes == None:\n        howManyBytes = numBytes(n)\n    b = bytearray(howManyBytes)\n    for count in range(howManyBytes-1, -1, -1):\n        b[count] = int(n % 256)\n        n >>= 8\n    return b\n\ndef mpiToNumber(mpi): #mpi is an openssl-format bignum string\n    if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number\n        raise AssertionError()\n    b = bytearray(mpi[4:])\n    return bytesToNumber(b)\n\ndef numberToMPI(n):\n    b = numberToByteArray(n)\n    ext = 0\n    #If the high-order bit is going to be set,\n    #add an extra byte of zeros\n    if (numBits(n) & 0x7)==0:\n        ext = 1\n    length = numBytes(n) + ext\n    b = bytearray(4+ext) + b\n    b[0] = (length >> 24) & 0xFF\n    b[1] = (length >> 16) & 0xFF\n    b[2] = (length >> 8) & 0xFF\n    b[3] = length & 0xFF\n    return bytes(b)\n\n\n# **************************************************************************\n# Misc. Utility Functions\n# **************************************************************************\n\ndef numBits(n):\n    if n==0:\n        return 0\n    s = \"%x\" % n\n    return ((len(s)-1)*4) + \\\n    {'0':0, '1':1, '2':2, '3':2,\n     '4':3, '5':3, '6':3, '7':3,\n     '8':4, '9':4, 'a':4, 'b':4,\n     'c':4, 'd':4, 'e':4, 'f':4,\n     }[s[0]]\n    return int(math.floor(math.log(n, 2))+1)\n\ndef numBytes(n):\n    if n==0:\n        return 0\n    bits = numBits(n)\n    return int(math.ceil(bits / 8.0))\n\n# **************************************************************************\n# Big Number Math\n# **************************************************************************\n\ndef getRandomNumber(low, high):\n    if low >= high:\n        raise AssertionError()\n    howManyBits = numBits(high)\n    howManyBytes = numBytes(high)\n    lastBits = howManyBits % 8\n    while 1:\n        bytes = getRandomBytes(howManyBytes)\n        if lastBits:\n            bytes[0] = bytes[0] % (1 << lastBits)\n        n = bytesToNumber(bytes)\n        if n >= low and n < high:\n            return n\n\ndef gcd(a,b):\n    a, b = max(a,b), min(a,b)\n    while b:\n        a, b = b, a % b\n    return a\n\ndef lcm(a, b):\n    return (a * b) // gcd(a, b)\n\n#Returns inverse of a mod b, zero if none\n#Uses Extended Euclidean Algorithm\ndef invMod(a, b):\n    c, d = a, b\n    uc, ud = 1, 0\n    while c != 0:\n        q = d // c\n        c, d = d-(q*c), c\n        uc, ud = ud - (q * uc), uc\n    if d == 1:\n        return ud % b\n    return 0\n\n\ndef powMod(base, power, modulus):\n    if power < 0:\n        result = pow(base, power*-1, modulus)\n        result = invMod(result, modulus)\n        return result\n    else:\n        return pow(base, power, modulus)\n\n#Pre-calculate a sieve of the ~100 primes < 1000:\ndef makeSieve(n):\n    sieve = list(range(n))\n    for count in range(2, int(math.sqrt(n))+1):\n        if sieve[count] == 0:\n            continue\n        x = sieve[count] * 2\n        while x < len(sieve):\n            sieve[x] = 0\n            x += sieve[count]\n    sieve = [x for x in sieve[2:] if x]\n    return sieve\n\nsieve = makeSieve(1000)\n\ndef isPrime(n, iterations=5, display=False):\n    #Trial division with sieve\n    for x in sieve:\n        if x >= n: return True\n        if n % x == 0: return False\n    #Passed trial division, proceed to Rabin-Miller\n    #Rabin-Miller implemented per Ferguson & Schneier\n    #Compute s, t for Rabin-Miller\n    if display: print(\"*\", end=' ')\n    s, t = n-1, 0\n    while s % 2 == 0:\n        s, t = s//2, t+1\n    #Repeat Rabin-Miller x times\n    a = 2 #Use 2 as a base for first iteration speedup, per HAC\n    for count in range(iterations):\n        v = powMod(a, s, n)\n        if v==1:\n            continue\n        i = 0\n        while v != n-1:\n            if i == t-1:\n                return False\n            else:\n                v, i = powMod(v, 2, n), i+1\n        a = getRandomNumber(2, n)\n    return True\n\ndef getRandomPrime(bits, display=False):\n    if bits < 10:\n        raise AssertionError()\n    #The 1.5 ensures the 2 MSBs are set\n    #Thus, when used for p,q in RSA, n will have its MSB set\n    #\n    #Since 30 is lcm(2,3,5), we'll set our test numbers to\n    #29 % 30 and keep them there\n    low = ((2 ** (bits-1)) * 3) // 2\n    high = 2 ** bits - 30\n    p = getRandomNumber(low, high)\n    p += 29 - (p % 30)\n    while 1:\n        if display: print(\".\", end=' ')\n        p += 30\n        if p >= high:\n            p = getRandomNumber(low, high)\n            p += 29 - (p % 30)\n        if isPrime(p, display=display):\n            return p\n\n#Unused at the moment...\ndef getRandomSafePrime(bits, display=False):\n    if bits < 10:\n        raise AssertionError()\n    #The 1.5 ensures the 2 MSBs are set\n    #Thus, when used for p,q in RSA, n will have its MSB set\n    #\n    #Since 30 is lcm(2,3,5), we'll set our test numbers to\n    #29 % 30 and keep them there\n    low = (2 ** (bits-2)) * 3//2\n    high = (2 ** (bits-1)) - 30\n    q = getRandomNumber(low, high)\n    q += 29 - (q % 30)\n    while 1:\n        if display: print(\".\", end=' ')\n        q += 30\n        if (q >= high):\n            q = getRandomNumber(low, high)\n            q += 29 - (q % 30)\n        #Ideas from Tom Wu's SRP code\n        #Do trial division on p and q before Rabin-Miller\n        if isPrime(q, 0, display=display):\n            p = (2 * q) + 1\n            if isPrime(p, display=display):\n                if isPrime(q, display=display):\n                    return p\n\n\nclass RSAKey(object):\n\n    def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0):\n        if (n and not e) or (e and not n):\n            raise AssertionError()\n        self.n = n\n        self.e = e\n        self.d = d\n        self.p = p\n        self.q = q\n        self.dP = dP\n        self.dQ = dQ\n        self.qInv = qInv\n        self.blinder = 0\n        self.unblinder = 0\n\n    def __len__(self):\n        \"\"\"Return the length of this key in bits.\n\n        @rtype: int\n        \"\"\"\n        return numBits(self.n)\n\n    def hasPrivateKey(self):\n        return self.d != 0\n\n    def hashAndSign(self, bytes):\n        \"\"\"Hash and sign the passed-in bytes.\n\n        This requires the key to have a private component.  It performs\n        a PKCS1-SHA1 signature on the passed-in data.\n\n        @type bytes: str or L{bytearray} of unsigned bytes\n        @param bytes: The value which will be hashed and signed.\n\n        @rtype: L{bytearray} of unsigned bytes.\n        @return: A PKCS1-SHA1 signature on the passed-in data.\n        \"\"\"\n        hashBytes = SHA1(bytearray(bytes))\n        prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes)\n        sigBytes = self.sign(prefixedHashBytes)\n        return sigBytes\n\n    def hashAndVerify(self, sigBytes, bytes):\n        \"\"\"Hash and verify the passed-in bytes with the signature.\n\n        This verifies a PKCS1-SHA1 signature on the passed-in data.\n\n        @type sigBytes: L{bytearray} of unsigned bytes\n        @param sigBytes: A PKCS1-SHA1 signature.\n\n        @type bytes: str or L{bytearray} of unsigned bytes\n        @param bytes: The value which will be hashed and verified.\n\n        @rtype: bool\n        @return: Whether the signature matches the passed-in data.\n        \"\"\"\n        hashBytes = SHA1(bytearray(bytes))\n        \n        # Try it with/without the embedded NULL\n        prefixedHashBytes1 = self._addPKCS1SHA1Prefix(hashBytes, False)\n        prefixedHashBytes2 = self._addPKCS1SHA1Prefix(hashBytes, True)\n        result1 = self.verify(sigBytes, prefixedHashBytes1)\n        result2 = self.verify(sigBytes, prefixedHashBytes2)\n        return (result1 or result2)\n\n    def sign(self, bytes):\n        \"\"\"Sign the passed-in bytes.\n\n        This requires the key to have a private component.  It performs\n        a PKCS1 signature on the passed-in data.\n\n        @type bytes: L{bytearray} of unsigned bytes\n        @param bytes: The value which will be signed.\n\n        @rtype: L{bytearray} of unsigned bytes.\n        @return: A PKCS1 signature on the passed-in data.\n        \"\"\"\n        if not self.hasPrivateKey():\n            raise AssertionError()\n        paddedBytes = self._addPKCS1Padding(bytes, 1)\n        m = bytesToNumber(paddedBytes)\n        if m >= self.n:\n            raise ValueError()\n        c = self._rawPrivateKeyOp(m)\n        sigBytes = numberToByteArray(c, numBytes(self.n))\n        return sigBytes\n\n    def verify(self, sigBytes, bytes):\n        \"\"\"Verify the passed-in bytes with the signature.\n\n        This verifies a PKCS1 signature on the passed-in data.\n\n        @type sigBytes: L{bytearray} of unsigned bytes\n        @param sigBytes: A PKCS1 signature.\n\n        @type bytes: L{bytearray} of unsigned bytes\n        @param bytes: The value which will be verified.\n\n        @rtype: bool\n        @return: Whether the signature matches the passed-in data.\n        \"\"\"\n        if len(sigBytes) != numBytes(self.n):\n            return False\n        paddedBytes = self._addPKCS1Padding(bytes, 1)\n        c = bytesToNumber(sigBytes)\n        if c >= self.n:\n            return False\n        m = self._rawPublicKeyOp(c)\n        checkBytes = numberToByteArray(m, numBytes(self.n))\n        return checkBytes == paddedBytes\n\n    def encrypt(self, bytes):\n        \"\"\"Encrypt the passed-in bytes.\n\n        This performs PKCS1 encryption of the passed-in data.\n\n        @type bytes: L{bytearray} of unsigned bytes\n        @param bytes: The value which will be encrypted.\n\n        @rtype: L{bytearray} of unsigned bytes.\n        @return: A PKCS1 encryption of the passed-in data.\n        \"\"\"\n        paddedBytes = self._addPKCS1Padding(bytes, 2)\n        m = bytesToNumber(paddedBytes)\n        if m >= self.n:\n            raise ValueError()\n        c = self._rawPublicKeyOp(m)\n        encBytes = numberToByteArray(c, numBytes(self.n))\n        return encBytes\n\n    def decrypt(self, encBytes):\n        \"\"\"Decrypt the passed-in bytes.\n\n        This requires the key to have a private component.  It performs\n        PKCS1 decryption of the passed-in data.\n\n        @type encBytes: L{bytearray} of unsigned bytes\n        @param encBytes: The value which will be decrypted.\n\n        @rtype: L{bytearray} of unsigned bytes or None.\n        @return: A PKCS1 decryption of the passed-in data or None if\n        the data is not properly formatted.\n        \"\"\"\n        if not self.hasPrivateKey():\n            raise AssertionError()\n        if len(encBytes) != numBytes(self.n):\n            return None\n        c = bytesToNumber(encBytes)\n        if c >= self.n:\n            return None\n        m = self._rawPrivateKeyOp(c)\n        decBytes = numberToByteArray(m, numBytes(self.n))\n        #Check first two bytes\n        if decBytes[0] != 0 or decBytes[1] != 2:\n            return None\n        #Scan through for zero separator\n        for x in range(1, len(decBytes)-1):\n            if decBytes[x]== 0:\n                break\n        else:\n            return None\n        return decBytes[x+1:] #Return everything after the separator\n\n\n\n\n    # **************************************************************************\n    # Helper Functions for RSA Keys\n    # **************************************************************************\n\n    def _addPKCS1SHA1Prefix(self, bytes, withNULL=True):\n        # There is a long history of confusion over whether the SHA1 \n        # algorithmIdentifier should be encoded with a NULL parameter or \n        # with the parameter omitted.  While the original intention was \n        # apparently to omit it, many toolkits went the other way.  TLS 1.2\n        # specifies the NULL should be included, and this behavior is also\n        # mandated in recent versions of PKCS #1, and is what tlslite has\n        # always implemented.  Anyways, verification code should probably \n        # accept both.  However, nothing uses this code yet, so this is \n        # all fairly moot.\n        if not withNULL:\n            prefixBytes = bytearray(\\\n            [0x30,0x1f,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x04,0x14])            \n        else:\n            prefixBytes = bytearray(\\\n            [0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14])            \n        prefixedBytes = prefixBytes + bytes\n        return prefixedBytes\n\n    def _addPKCS1Padding(self, bytes, blockType):\n        padLength = (numBytes(self.n) - (len(bytes)+3))\n        if blockType == 1: #Signature padding\n            pad = [0xFF] * padLength\n        elif blockType == 2: #Encryption padding\n            pad = bytearray(0)\n            while len(pad) < padLength:\n                padBytes = getRandomBytes(padLength * 2)\n                pad = [b for b in padBytes if b != 0]\n                pad = pad[:padLength]\n        else:\n            raise AssertionError()\n\n        padding = bytearray([0,blockType] + pad + [0])\n        paddedBytes = padding + bytes\n        return paddedBytes\n\n\n\n\n    def _rawPrivateKeyOp(self, m):\n        #Create blinding values, on the first pass:\n        if not self.blinder:\n            self.unblinder = getRandomNumber(2, self.n)\n            self.blinder = powMod(invMod(self.unblinder, self.n), self.e,\n                                  self.n)\n\n        #Blind the input\n        m = (m * self.blinder) % self.n\n\n        #Perform the RSA operation\n        c = self._rawPrivateKeyOpHelper(m)\n\n        #Unblind the output\n        c = (c * self.unblinder) % self.n\n\n        #Update blinding values\n        self.blinder = (self.blinder * self.blinder) % self.n\n        self.unblinder = (self.unblinder * self.unblinder) % self.n\n\n        #Return the output\n        return c\n\n\n    def _rawPrivateKeyOpHelper(self, m):\n        #Non-CRT version\n        #c = powMod(m, self.d, self.n)\n\n        #CRT version  (~3x faster)\n        s1 = powMod(m, self.dP, self.p)\n        s2 = powMod(m, self.dQ, self.q)\n        h = ((s1 - s2) * self.qInv) % self.p\n        c = s2 + self.q * h\n        return c\n\n    def _rawPublicKeyOp(self, c):\n        m = powMod(c, self.e, self.n)\n        return m\n\n    def acceptsPassword(self):\n        return False\n\n    def generate(bits):\n        key = RSAKey()\n        p = getRandomPrime(bits//2, False)\n        q = getRandomPrime(bits//2, False)\n        t = lcm(p-1, q-1)\n        key.n = p * q\n        key.e = 65537\n        key.d = invMod(key.e, t)\n        key.p = p\n        key.q = q\n        key.dP = key.d % (p-1)\n        key.dQ = key.d % (q-1)\n        key.qInv = invMod(q, p)\n        return key\n    generate = staticmethod(generate)\n"
  },
  {
    "path": "lib/segwit_addr.py",
    "content": "# Copyright (c) 2017 Pieter Wuille\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n\"\"\"Reference implementation for Bech32 and segwit addresses.\"\"\"\n\n\nCHARSET = \"qpzry9x8gf2tvdw0s3jn54khce6mua7l\"\n\n\ndef bech32_polymod(values):\n    \"\"\"Internal function that computes the Bech32 checksum.\"\"\"\n    generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]\n    chk = 1\n    for value in values:\n        top = chk >> 25\n        chk = (chk & 0x1ffffff) << 5 ^ value\n        for i in range(5):\n            chk ^= generator[i] if ((top >> i) & 1) else 0\n    return chk\n\n\ndef bech32_hrp_expand(hrp):\n    \"\"\"Expand the HRP into values for checksum computation.\"\"\"\n    return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]\n\n\ndef bech32_verify_checksum(hrp, data):\n    \"\"\"Verify a checksum given HRP and converted data characters.\"\"\"\n    return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1\n\n\ndef bech32_create_checksum(hrp, data):\n    \"\"\"Compute the checksum values given HRP and data.\"\"\"\n    values = bech32_hrp_expand(hrp) + data\n    polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1\n    return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]\n\n\ndef bech32_encode(hrp, data):\n    \"\"\"Compute a Bech32 string given HRP and data values.\"\"\"\n    combined = data + bech32_create_checksum(hrp, data)\n    return hrp + '1' + ''.join([CHARSET[d] for d in combined])\n\n\ndef bech32_decode(bech):\n    \"\"\"Validate a Bech32 string, and determine HRP and data.\"\"\"\n    if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or\n            (bech.lower() != bech and bech.upper() != bech)):\n        return (None, None)\n    bech = bech.lower()\n    pos = bech.rfind('1')\n    if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:\n        return (None, None)\n    if not all(x in CHARSET for x in bech[pos+1:]):\n        return (None, None)\n    hrp = bech[:pos]\n    data = [CHARSET.find(x) for x in bech[pos+1:]]\n    if not bech32_verify_checksum(hrp, data):\n        return (None, None)\n    return (hrp, data[:-6])\n\n\ndef convertbits(data, frombits, tobits, pad=True):\n    \"\"\"General power-of-2 base conversion.\"\"\"\n    acc = 0\n    bits = 0\n    ret = []\n    maxv = (1 << tobits) - 1\n    max_acc = (1 << (frombits + tobits - 1)) - 1\n    for value in data:\n        if value < 0 or (value >> frombits):\n            return None\n        acc = ((acc << frombits) | value) & max_acc\n        bits += frombits\n        while bits >= tobits:\n            bits -= tobits\n            ret.append((acc >> bits) & maxv)\n    if pad:\n        if bits:\n            ret.append((acc << (tobits - bits)) & maxv)\n    elif bits >= frombits or ((acc << (tobits - bits)) & maxv):\n        return None\n    return ret\n\n\ndef decode(hrp, addr):\n    \"\"\"Decode a segwit address.\"\"\"\n    hrpgot, data = bech32_decode(addr)\n    if hrpgot != hrp:\n        return (None, None)\n    decoded = convertbits(data[1:], 5, 8, False)\n    if decoded is None or len(decoded) < 2 or len(decoded) > 40:\n        return (None, None)\n    if data[0] > 16:\n        return (None, None)\n    if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:\n        return (None, None)\n    return (data[0], decoded)\n\n\ndef encode(hrp, witver, witprog):\n    \"\"\"Encode a segwit address.\"\"\"\n    ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))\n    assert decode(hrp, ret) is not (None, None)\n    return ret\n"
  },
  {
    "path": "lib/servers-orig.json",
    "content": "{\n    \"E-X.not.fyi\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"ELECTRUMX.not.fyi\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"ELEX01.blackpole.online\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"VPS.hsmiths.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"bitcoin.freedomnode.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"btc.smsys.me\": {\n        \"pruning\": \"-\",\n        \"s\": \"995\",\n        \"version\": \"1.1\"\n    },\n    \"currentlane.lovebitco.in\": {\n        \"pruning\": \"-\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"daedalus.bauerj.eu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"de01.hamster.science\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"ecdsa.net\": {\n        \"pruning\": \"-\",\n        \"s\": \"110\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"elec.luggs.co\": {\n        \"pruning\": \"-\",\n        \"s\": \"443\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.akinbo.org\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.antumbra.se\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.be\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.coinucopia.io\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.cutie.ga\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.festivaldelhumor.org\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.hsmiths.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.qtornado.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum.vom-stausee.de\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrum3.hachre.de\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrumx.bot.nu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"electrumx.westeurope.cloudapp.azure.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"elx01.knas.systems\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"ex-btc.server-on.net\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"helicarrier.bauerj.eu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"mooo.not.fyi\": {\n        \"pruning\": \"-\",\n        \"s\": \"50012\",\n        \"t\": \"50011\",\n        \"version\": \"1.1\"\n    },\n    \"ndnd.selfhost.eu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"node.arihanc.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"node.xbt.eu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"node1.volatilevictory.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"noserver4u.de\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"qmebr.spdns.org\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"raspi.hsmiths.com\": {\n        \"pruning\": \"-\",\n        \"s\": \"51002\",\n        \"t\": \"51001\",\n        \"version\": \"1.1\"\n    },\n    \"s2.noip.pl\": {\n        \"pruning\": \"-\",\n        \"s\": \"50102\",\n        \"version\": \"1.1\"\n    },\n    \"s5.noip.pl\": {\n        \"pruning\": \"-\",\n        \"s\": \"50105\",\n        \"version\": \"1.1\"\n    },\n    \"songbird.bauerj.eu\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"us.electrum.be\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    },\n    \"us01.hamster.science\": {\n        \"pruning\": \"-\",\n        \"s\": \"50002\",\n        \"t\": \"50001\",\n        \"version\": \"1.1\"\n    }\n}\n"
  },
  {
    "path": "lib/servers.json",
    "content": "{\n    \"electrum.btcprivate.org\": {\"s\":\"5222\"}\n}\n\n"
  },
  {
    "path": "lib/servers_testnet.json",
    "content": "{\n    \"35.190.188.161\": {\"t\":\"50001\"}\n}\n"
  },
  {
    "path": "lib/simple_config.py",
    "content": "import json\nimport threading\nimport time\nimport os\nimport stat\n\nfrom copy import deepcopy\nfrom .util import user_dir, print_error, print_stderr, PrintError\n\nfrom .bitcoin import DEFAULT_FEE_RATE, MAX_FEE_RATE, FEE_TARGETS\n\nSYSTEM_CONFIG_PATH = \"/etc/electrum.conf\"\n\nconfig = None\n\n\ndef get_config():\n    global config\n    return config\n\n\ndef set_config(c):\n    global config\n    config = c\n\n\nclass SimpleConfig(PrintError):\n    \"\"\"\n    The SimpleConfig class is responsible for handling operations involving\n    configuration files.\n\n    There are 3 different sources of possible configuration values:\n        1. Command line options.\n        2. User configuration (in the user's config directory)\n        3. System configuration (in /etc/)\n    They are taken in order (1. overrides config options set in 2., that\n    override config set in 3.)\n    \"\"\"\n    fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000]\n\n    def __init__(self, options={}, read_system_config_function=None,\n                 read_user_config_function=None, read_user_dir_function=None):\n\n        # This lock needs to be acquired for updating and reading the config in\n        # a thread-safe way.\n        self.lock = threading.RLock()\n\n        self.fee_estimates = {}\n        self.fee_estimates_last_updated = {}\n        self.last_time_fee_estimates_requested = 0  # zero ensures immediate fees\n\n        # The following two functions are there for dependency injection when\n        # testing.\n        if read_system_config_function is None:\n            read_system_config_function = read_system_config\n        if read_user_config_function is None:\n            read_user_config_function = read_user_config\n        if read_user_dir_function is None:\n            self.user_dir = user_dir\n        else:\n            self.user_dir = read_user_dir_function\n\n        # The command line options\n        self.cmdline_options = deepcopy(options)\n\n        # Portable wallets don't use a system config\n        if self.cmdline_options.get('portable', False):\n            self.system_config = {}\n        else:\n            self.system_config = read_system_config_function()\n\n        # Set self.path and read the user config\n        self.user_config = {}  # for self.get in electrum_path()\n        self.path = self.electrum_path()\n        self.user_config = read_user_config_function(self.path)\n        # Upgrade obsolete keys\n        self.fixup_keys({'auto_cycle': 'auto_connect'})\n        # Make a singleton instance of 'self'\n        set_config(self)\n\n    def electrum_path(self):\n        # Read electrum_path from command line / system configuration\n        # Otherwise use the user's default data directory.\n        path = self.get('electrum_path')\n        if path is None:\n            path = self.user_dir()\n\n        def make_dir(path):\n            # Make directory if it does not yet exist.\n            if not os.path.exists(path):\n                if os.path.islink(path):\n                    raise BaseException('Dangling link: ' + path)\n                os.mkdir(path)\n                os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)\n\n        make_dir(path)\n        if self.get('testnet'):\n            path = os.path.join(path, 'testnet')\n            make_dir(path)\n\n        self.print_error(\"electrum directory\", path)\n        return path\n\n    def fixup_config_keys(self, config, keypairs):\n        updated = False\n        for old_key, new_key in keypairs.items():\n            if old_key in config:\n                if not new_key in config:\n                    config[new_key] = config[old_key]\n                del config[old_key]\n                updated = True\n        return updated\n\n    def fixup_keys(self, keypairs):\n        '''Migrate old key names to new ones'''\n        self.fixup_config_keys(self.cmdline_options, keypairs)\n        self.fixup_config_keys(self.system_config, keypairs)\n        if self.fixup_config_keys(self.user_config, keypairs):\n            self.save_user_config()\n\n    def set_key(self, key, value, save = True):\n        if not self.is_modifiable(key):\n            print_stderr(\"Warning: not changing config key '%s' set on the command line\" % key)\n            return\n\n        with self.lock:\n            self.user_config[key] = value\n            if save:\n                self.save_user_config()\n        return\n\n    def get(self, key, default=None):\n        with self.lock:\n            out = self.cmdline_options.get(key)\n            if out is None:\n                out = self.user_config.get(key)\n                if out is None:\n                    out = self.system_config.get(key, default)\n        return out\n\n    def is_modifiable(self, key):\n        return not key in self.cmdline_options\n\n    def save_user_config(self):\n        if not self.path:\n            return\n        path = os.path.join(self.path, \"config\")\n        s = json.dumps(self.user_config, indent=4, sort_keys=True)\n        with open(path, \"w\") as f:\n            f.write(s)\n        os.chmod(path, stat.S_IREAD | stat.S_IWRITE)\n\n    def get_wallet_path(self):\n        \"\"\"Set the path of the wallet.\"\"\"\n\n        # command line -w option\n        if self.get('wallet_path'):\n            return os.path.join(self.get('cwd'), self.get('wallet_path'))\n\n        # path in config file\n        path = self.get('default_wallet_path')\n        if path and os.path.exists(path):\n            return path\n\n        # default path\n        dirpath = os.path.join(self.path, \"wallets\")\n        if not os.path.exists(dirpath):\n            if os.path.islink(dirpath):\n                raise BaseException('Dangling link: ' + dirpath)\n            os.mkdir(dirpath)\n            os.chmod(dirpath, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)\n\n        new_path = os.path.join(self.path, \"wallets\", \"default_wallet\")\n\n        # default path in pre 1.9 versions\n        old_path = os.path.join(self.path, \"electrum.dat\")\n        if os.path.exists(old_path) and not os.path.exists(new_path):\n            os.rename(old_path, new_path)\n\n        return new_path\n\n    def remove_from_recently_open(self, filename):\n        recent = self.get('recently_open', [])\n        if filename in recent:\n            recent.remove(filename)\n            self.set_key('recently_open', recent)\n\n    def set_session_timeout(self, seconds):\n        self.print_error(\"session timeout -> %d seconds\" % seconds)\n        self.set_key('session_timeout', seconds)\n\n    def get_session_timeout(self):\n        return self.get('session_timeout', 300)\n\n    def open_last_wallet(self):\n        if self.get('wallet_path') is None:\n            last_wallet = self.get('gui_last_wallet')\n            if last_wallet is not None and os.path.exists(last_wallet):\n                self.cmdline_options['default_wallet_path'] = last_wallet\n\n    def save_last_wallet(self, wallet):\n        if self.get('wallet_path') is None:\n            path = wallet.storage.path\n            self.set_key('gui_last_wallet', path)\n\n    def default_fee_rate(self):\n        f = self.get('default_fee_rate', DEFAULT_FEE_RATE)\n        if f==0:\n            f = DEFAULT_FEE_RATE\n        return f\n\n    def max_fee_rate(self):\n        f = self.get('max_fee_rate', MAX_FEE_RATE)\n        if f==0:\n            f = MAX_FEE_RATE\n        return f\n\n    def dynfee(self, i):\n        if i < 4:\n            j = FEE_TARGETS[i]\n            fee = self.fee_estimates.get(j)\n        else:\n            assert i == 4\n            fee = self.fee_estimates.get(2)\n            if fee is not None:\n                fee += fee/2\n        if fee is not None:\n            fee = min(5*MAX_FEE_RATE, fee)\n        return fee\n\n    def reverse_dynfee(self, fee_per_kb):\n        import operator\n        l = list(self.fee_estimates.items()) + [(1, self.dynfee(4))]\n        dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), l)\n        min_target, min_value = min(dist, key=operator.itemgetter(1))\n        if fee_per_kb < self.fee_estimates.get(25)/2:\n            min_target = -1\n        return min_target\n\n    def static_fee(self, i):\n        return self.fee_rates[i]\n\n    def static_fee_index(self, value):\n        dist = list(map(lambda x: abs(x - value), self.fee_rates))\n        return min(range(len(dist)), key=dist.__getitem__)\n\n    def has_fee_estimates(self):\n        return len(self.fee_estimates)==4\n\n    # 'dynamic' fees are disabled - we use 'static' (but adjustable) fees\n    def is_dynfee(self):\n        return self.get('dynamic_fees', False)\n\n    def fee_per_kb(self):\n        dyn = self.is_dynfee()\n        if dyn:\n            fee_rate = self.dynfee(self.get('fee_level', 2))\n        else:\n            fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2)\n        return fee_rate\n\n    def estimate_fee(self, size):\n        return self.estimate_fee_for_feerate(self.fee_per_kb(), size)\n\n    @classmethod\n    def estimate_fee_for_feerate(cls, fee_per_kb, size):\n        return int(fee_per_kb * size / 1000.)\n\n    def update_fee_estimates(self, key, value):\n        self.fee_estimates[key] = value\n        self.fee_estimates_last_updated[key] = time.time()\n\n    def is_fee_estimates_update_required(self):\n        \"\"\"Checks time since last requested and updated fee estimates.\n        Returns True if an update should be requested.\n        \"\"\"\n        now = time.time()\n        prev_updates = self.fee_estimates_last_updated.values()\n        oldest_fee_time = min(prev_updates) if prev_updates else 0\n        stale_fees = now - oldest_fee_time > 7200\n        old_request = now - self.last_time_fee_estimates_requested > 60\n        return stale_fees and old_request\n\n    def requested_fee_estimates(self):\n        self.last_time_fee_estimates_requested = time.time()\n\n    def get_video_device(self):\n        device = self.get(\"video_device\", \"default\")\n        if device == 'default':\n            device = ''\n        return device\n\n\ndef read_system_config(path=SYSTEM_CONFIG_PATH):\n    \"\"\"Parse and return the system config settings in /etc/electrum.conf.\"\"\"\n    result = {}\n    if os.path.exists(path):\n        import configparser\n        p = configparser.ConfigParser()\n        try:\n            p.read(path)\n            for k, v in p.items('client'):\n                result[k] = v\n        except (configparser.NoSectionError, configparser.MissingSectionHeaderError):\n            pass\n\n    return result\n\ndef read_user_config(path):\n    \"\"\"Parse and store the user config settings in electrum.conf into user_config[].\"\"\"\n    if not path:\n        return {}\n    config_path = os.path.join(path, \"config\")\n    if not os.path.exists(config_path):\n        return {}\n    try:\n        with open(config_path, \"r\") as f:\n            data = f.read()\n        result = json.loads(data)\n    except:\n        print_error(\"Warning: Cannot read config file.\", config_path)\n        return {}\n    if not type(result) is dict:\n        return {}\n    return result\n"
  },
  {
    "path": "lib/storage.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport os\nimport ast\nimport threading\nimport json\nimport copy\nimport re\nimport stat\nimport pbkdf2, hmac, hashlib\nimport base64\nimport zlib\n\nfrom .util import PrintError, profiler\nfrom .plugins import run_hook, plugin_loaders\nfrom .keystore import bip44_derivation\nfrom . import bitcoin\n\n\n# seed_version is now used for the version of the wallet file\n\nOLD_SEED_VERSION = 4        # electrum versions < 2.0\nNEW_SEED_VERSION = 11       # electrum versions >= 2.0\nFINAL_SEED_VERSION = 16     # electrum >= 2.7 will set this to prevent\n                            # old versions from overwriting new format\n\n\n\ndef multisig_type(wallet_type):\n    '''If wallet_type is mofn multi-sig, return [m, n],\n    otherwise return None.'''\n    match = re.match('(\\d+)of(\\d+)', wallet_type)\n    if match:\n        match = [int(x) for x in match.group(1, 2)]\n    return match\n\n\nclass WalletStorage(PrintError):\n\n    def __init__(self, path, manual_upgrades=False):\n        self.print_error(\"wallet path\", path)\n        self.manual_upgrades = manual_upgrades\n        self.lock = threading.RLock()\n        self.data = {}\n        self.path = path\n        self.modified = False\n        self.pubkey = None\n        if self.file_exists():\n            with open(self.path, \"r\") as f:\n                self.raw = f.read()\n            if not self.is_encrypted():\n                self.load_data(self.raw)\n        else:\n            # avoid new wallets getting 'upgraded'\n            self.put('seed_version', FINAL_SEED_VERSION)\n\n    def load_data(self, s):\n        try:\n            self.data = json.loads(s)\n        except:\n            try:\n                d = ast.literal_eval(s)\n                labels = d.get('labels', {})\n            except Exception as e:\n                raise IOError(\"Cannot read wallet file '%s'\" % self.path)\n            self.data = {}\n            for key, value in d.items():\n                try:\n                    json.dumps(key)\n                    json.dumps(value)\n                except:\n                    self.print_error('Failed to convert label to json format', key)\n                    continue\n                self.data[key] = value\n\n        # check here if I need to load a plugin\n        t = self.get('wallet_type')\n        l = plugin_loaders.get(t)\n        if l: l()\n\n        if not self.manual_upgrades:\n            if self.requires_split():\n                raise BaseException(\"This wallet has multiple accounts and must be split\")\n            if self.requires_upgrade():\n                self.upgrade()\n\n    def is_encrypted(self):\n        try:\n            return base64.b64decode(self.raw)[0:4] == b'BIE1'\n        except:\n            return False\n\n    def file_exists(self):\n        return self.path and os.path.exists(self.path)\n\n    def get_key(self, password):\n        secret = pbkdf2.PBKDF2(password, '', iterations = 1024, macmodule = hmac, digestmodule = hashlib.sha512).read(64)\n        ec_key = bitcoin.EC_KEY(secret)\n        return ec_key\n\n    def decrypt(self, password):\n        ec_key = self.get_key(password)\n        s = zlib.decompress(ec_key.decrypt_message(self.raw)) if self.raw else None\n        self.pubkey = ec_key.get_public_key()\n        s = s.decode('utf8')\n        self.load_data(s)\n\n    def set_password(self, password, encrypt):\n        self.put('use_encryption', bool(password))\n        if encrypt and password:\n            ec_key = self.get_key(password)\n            self.pubkey = ec_key.get_public_key()\n        else:\n            self.pubkey = None\n\n    def get(self, key, default=None):\n        with self.lock:\n            v = self.data.get(key)\n            if v is None:\n                v = default\n            else:\n                v = copy.deepcopy(v)\n        return v\n\n    def put(self, key, value):\n        try:\n            json.dumps(key)\n            json.dumps(value)\n        except:\n            self.print_error(\"json error: cannot save\", key)\n            return\n        with self.lock:\n            if value is not None:\n                if self.data.get(key) != value:\n                    self.modified = True\n                    self.data[key] = copy.deepcopy(value)\n            elif key in self.data:\n                self.modified = True\n                self.data.pop(key)\n\n    @profiler\n    def write(self):\n        with self.lock:\n            self._write()\n\n    def _write(self):\n        if threading.currentThread().isDaemon():\n            self.print_error('warning: daemon thread cannot write wallet')\n            return\n        if not self.modified:\n            return\n        s = json.dumps(self.data, indent=4, sort_keys=True)\n        if self.pubkey:\n            s = bytes(s, 'utf8')\n            c = zlib.compress(s)\n            s = bitcoin.encrypt_message(c, self.pubkey)\n            s = s.decode('utf8')\n\n        temp_path = \"%s.tmp.%s\" % (self.path, os.getpid())\n        with open(temp_path, \"w\") as f:\n            f.write(s)\n            f.flush()\n            os.fsync(f.fileno())\n\n        mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE\n        # perform atomic write on POSIX systems\n        try:\n            os.rename(temp_path, self.path)\n        except:\n            os.remove(self.path)\n            os.rename(temp_path, self.path)\n        os.chmod(self.path, mode)\n        self.print_error(\"saved\", self.path)\n        self.modified = False\n\n    def requires_split(self):\n        d = self.get('accounts', {})\n        return len(d) > 1\n\n    def split_accounts(storage):\n        result = []\n        # backward compatibility with old wallets\n        d = storage.get('accounts', {})\n        if len(d) < 2:\n            return\n        wallet_type = storage.get('wallet_type')\n        if wallet_type == 'old':\n            assert len(d) == 2\n            storage1 = WalletStorage(storage.path + '.deterministic')\n            storage1.data = copy.deepcopy(storage.data)\n            storage1.put('accounts', {'0': d['0']})\n            storage1.upgrade()\n            storage1.write()\n            storage2 = WalletStorage(storage.path + '.imported')\n            storage2.data = copy.deepcopy(storage.data)\n            storage2.put('accounts', {'/x': d['/x']})\n            storage2.put('seed', None)\n            storage2.put('seed_version', None)\n            storage2.put('master_public_key', None)\n            storage2.put('wallet_type', 'imported')\n            storage2.upgrade()\n            storage2.write()\n            result = [storage1.path, storage2.path]\n        elif wallet_type in ['bip44', 'trezor', 'keepkey', 'ledger', 'btchip', 'digitalbitbox']:\n            mpk = storage.get('master_public_keys')\n            for k in d.keys():\n                i = int(k)\n                x = d[k]\n                if x.get(\"pending\"):\n                    continue\n                xpub = mpk[\"x/%d'\"%i]\n                new_path = storage.path + '.' + k\n                storage2 = WalletStorage(new_path)\n                storage2.data = copy.deepcopy(storage.data)\n                # save account, derivation and xpub at index 0\n                storage2.put('accounts', {'0': x})\n                storage2.put('master_public_keys', {\"x/0'\": xpub})\n                storage2.put('derivation', bip44_derivation(k))\n                storage2.upgrade()\n                storage2.write()\n                result.append(new_path)\n        else:\n            raise BaseException(\"This wallet has multiple accounts and must be split\")\n        return result\n\n    def requires_upgrade(self):\n        return self.file_exists() and self.get_seed_version() < FINAL_SEED_VERSION\n\n    def upgrade(self):\n        self.print_error('upgrading wallet format')\n\n        self.convert_imported()\n        self.convert_wallet_type()\n        self.convert_account()\n        self.convert_version_13_b()\n        self.convert_version_14()\n        self.convert_version_15()\n        self.convert_version_16()\n\n        self.put('seed_version', FINAL_SEED_VERSION)  # just to be sure\n        self.write()\n\n    def convert_wallet_type(self):\n        wallet_type = self.get('wallet_type')\n        if wallet_type == 'btchip': wallet_type = 'ledger'\n        if self.get('keystore') or self.get('x1/') or wallet_type=='imported':\n            return False\n        assert not self.requires_split()\n        seed_version = self.get_seed_version()\n        seed = self.get('seed')\n        xpubs = self.get('master_public_keys')\n        xprvs = self.get('master_private_keys', {})\n        mpk = self.get('master_public_key')\n        keypairs = self.get('keypairs')\n        key_type = self.get('key_type')\n        if seed_version == OLD_SEED_VERSION or wallet_type == 'old':\n            d = {\n                'type': 'old',\n                'seed': seed,\n                'mpk': mpk,\n            }\n            self.put('wallet_type', 'standard')\n            self.put('keystore', d)\n\n        elif key_type == 'imported':\n            d = {\n                'type': 'imported',\n                'keypairs': keypairs,\n            }\n            self.put('wallet_type', 'standard')\n            self.put('keystore', d)\n\n        elif wallet_type in ['xpub', 'standard']:\n            xpub = xpubs[\"x/\"]\n            xprv = xprvs.get(\"x/\")\n            d = {\n                'type': 'bip32',\n                'xpub': xpub,\n                'xprv': xprv,\n                'seed': seed,\n            }\n            self.put('wallet_type', 'standard')\n            self.put('keystore', d)\n\n        elif wallet_type in ['bip44']:\n            xpub = xpubs[\"x/0'\"]\n            xprv = xprvs.get(\"x/0'\")\n            d = {\n                'type': 'bip32',\n                'xpub': xpub,\n                'xprv': xprv,\n            }\n            self.put('wallet_type', 'standard')\n            self.put('keystore', d)\n\n        elif wallet_type in ['trezor', 'keepkey', 'ledger', 'digitalbitbox']:\n            xpub = xpubs[\"x/0'\"]\n            derivation = self.get('derivation', bip44_derivation(0))\n            d = {\n                'type': 'hardware',\n                'hw_type': wallet_type,\n                'xpub': xpub,\n                'derivation': derivation,\n            }\n            self.put('wallet_type', 'standard')\n            self.put('keystore', d)\n\n        elif (wallet_type == '2fa') or multisig_type(wallet_type):\n            for key in xpubs.keys():\n                d = {\n                    'type': 'bip32',\n                    'xpub': xpubs[key],\n                    'xprv': xprvs.get(key),\n                }\n                if key == 'x1/' and seed:\n                    d['seed'] = seed\n                self.put(key, d)\n        else:\n            raise\n        # remove junk\n        self.put('master_public_key', None)\n        self.put('master_public_keys', None)\n        self.put('master_private_keys', None)\n        self.put('derivation', None)\n        self.put('seed', None)\n        self.put('keypairs', None)\n        self.put('key_type', None)\n\n    def convert_version_13_b(self):\n        # version 13 is ambiguous, and has an earlier and a later structure\n        if not self._is_upgrade_method_needed(0, 13):\n            return\n\n        if self.get('wallet_type') == 'standard':\n            if self.get('keystore').get('type') == 'imported':\n                pubkeys = self.get('keystore').get('keypairs').keys()\n                d = {'change': []}\n                receiving_addresses = []\n                for pubkey in pubkeys:\n                    addr = bitcoin.pubkey_to_address('p2pkh', pubkey)\n                    receiving_addresses.append(addr)\n                d['receiving'] = receiving_addresses\n                self.put('addresses', d)\n                self.put('pubkeys', None)\n\n        self.put('seed_version', 13)\n\n    def convert_version_14(self):\n        # convert imported wallets for 3.0\n        if not self._is_upgrade_method_needed(13, 13):\n            return\n\n        if self.get('wallet_type') =='imported':\n            addresses = self.get('addresses')\n            if type(addresses) is list:\n                addresses = dict([(x, None) for x in addresses])\n                self.put('addresses', addresses)\n        elif self.get('wallet_type') == 'standard':\n            if self.get('keystore').get('type')=='imported':\n                addresses = set(self.get('addresses').get('receiving'))\n                pubkeys = self.get('keystore').get('keypairs').keys()\n                assert len(addresses) == len(pubkeys)\n                d = {}\n                for pubkey in pubkeys:\n                    addr = bitcoin.pubkey_to_address('p2pkh', pubkey)\n                    assert addr in addresses\n                    d[addr] = {\n                        'pubkey': pubkey,\n                        'redeem_script': None,\n                        'type': 'p2pkh'\n                    }\n                self.put('addresses', d)\n                self.put('pubkeys', None)\n                self.put('wallet_type', 'imported')\n        self.put('seed_version', 14)\n\n    def convert_version_15(self):\n        if not self._is_upgrade_method_needed(14, 14):\n            return\n        assert self.get('seed_type') != 'segwit'  # unsupported derivation\n        self.put('seed_version', 15)\n\n    def convert_version_16(self):\n        # fixes issue #3193 for Imported_Wallets with addresses\n        # also, previous versions allowed importing any garbage as an address\n        #       which we now try to remove, see pr #3191\n        if not self._is_upgrade_method_needed(15, 15):\n            return\n\n        def remove_address(addr):\n            def remove_from_dict(dict_name):\n                d = self.get(dict_name, None)\n                if d is not None:\n                    d.pop(addr, None)\n                    self.put(dict_name, d)\n\n            def remove_from_list(list_name):\n                lst = self.get(list_name, None)\n                if lst is not None:\n                    s = set(lst)\n                    s -= {addr}\n                    self.put(list_name, list(s))\n\n            # note: we don't remove 'addr' from self.get('addresses')\n            remove_from_dict('addr_history')\n            remove_from_dict('labels')\n            remove_from_dict('payment_requests')\n            remove_from_list('frozen_addresses')\n\n        if self.get('wallet_type') == 'imported':\n            addresses = self.get('addresses')\n            assert isinstance(addresses, dict)\n            addresses_new = dict()\n            for address, details in addresses.items():\n                if not bitcoin.is_address(address):\n                    remove_address(address)\n                    continue\n                if details is None:\n                    addresses_new[address] = {}\n                else:\n                    addresses_new[address] = details\n            self.put('addresses', addresses_new)\n\n        self.put('seed_version', 16)\n\n    def convert_imported(self):\n        # '/x' is the internal ID for imported accounts\n        d = self.get('accounts', {}).get('/x', {}).get('imported',{})\n        if not d:\n            return False\n        addresses = []\n        keypairs = {}\n        for addr, v in d.items():\n            pubkey, privkey = v\n            if privkey:\n                keypairs[pubkey] = privkey\n            else:\n                addresses.append(addr)\n        if addresses and keypairs:\n            raise BaseException('mixed addresses and privkeys')\n        elif addresses:\n            self.put('addresses', addresses)\n            self.put('accounts', None)\n        elif keypairs:\n            self.put('wallet_type', 'standard')\n            self.put('key_type', 'imported')\n            self.put('keypairs', keypairs)\n            self.put('accounts', None)\n        else:\n            raise BaseException('no addresses or privkeys')\n\n    def convert_account(self):\n        self.put('accounts', None)\n\n    def _is_upgrade_method_needed(self, min_version, max_version):\n        cur_version = self.get_seed_version()\n        if cur_version > max_version:\n            return False\n        elif cur_version < min_version:\n            raise BaseException(\n                ('storage upgrade: unexpected version %d (should be %d-%d)'\n                 % (cur_version, min_version, max_version)))\n        else:\n            return True\n\n    def get_action(self):\n        action = run_hook('get_action', self)\n        if action:\n            return action\n        if not self.file_exists():\n            return 'new'\n\n    def get_seed_version(self):\n        seed_version = self.get('seed_version')\n        if not seed_version:\n            seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION\n        if seed_version > FINAL_SEED_VERSION:\n            raise BaseException('This version of Electrum is too old to open this wallet')\n        if seed_version==14 and self.get('seed_type') == 'segwit':\n            self.raise_unsupported_version(seed_version)\n        if seed_version >=12:\n            return seed_version\n        if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]:\n            self.raise_unsupported_version(seed_version)\n        return seed_version\n\n    def raise_unsupported_version(self, seed_version):\n        msg = \"Your wallet has an unsupported seed version.\"\n        msg += '\\n\\nWallet file: %s' % os.path.abspath(self.path)\n        if seed_version in [5, 7, 8, 9, 10, 14]:\n            msg += \"\\n\\nTo open this wallet, try 'git checkout seed_v%d'\"%seed_version\n        if seed_version == 6:\n            # version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog\n            msg += '\\n\\nThis file was created because of a bug in version 1.9.8.'\n            if self.get('master_public_keys') is None and self.get('master_private_keys') is None and self.get('imported_keys') is None:\n                # pbkdf2 was not included with the binaries, and wallet creation aborted.\n                msg += \"\\nIt does not contain any keys, and can safely be removed.\"\n            else:\n                # creation was complete if electrum was run from source\n                msg += \"\\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet.\"\n        raise BaseException(msg)\n"
  },
  {
    "path": "lib/synchronizer.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom threading import Lock\nimport hashlib\n\n# from .bitcoin import Hash, hash_encode\nfrom .transaction import Transaction\nfrom .util import ThreadJob, bh2u\n\n\nclass Synchronizer(ThreadJob):\n    '''The synchronizer keeps the wallet up-to-date with its set of\n    addresses and their transactions.  It subscribes over the network\n    to wallet addresses, gets the wallet to generate new addresses\n    when necessary, requests the transaction history of any addresses\n    we don't have the full history of, and requests binary transaction\n    data of any transactions the wallet doesn't have.\n\n    External interface: __init__() and add() member functions.\n    '''\n\n    def __init__(self, wallet, network):\n        self.wallet = wallet\n        self.network = network\n        self.new_addresses = set()\n        # Entries are (tx_hash, tx_height) tuples\n        self.requested_tx = {}\n        self.requested_histories = {}\n        self.requested_addrs = set()\n        self.lock = Lock()\n        self.initialize()\n\n    def parse_response(self, response):\n        if response.get('error'):\n            self.print_error(\"response error:\", response)\n            return None, None\n        return response['params'], response['result']\n\n    def is_up_to_date(self):\n        return (not self.requested_tx and not self.requested_histories\n                and not self.requested_addrs)\n\n    def release(self):\n        self.network.unsubscribe(self.on_address_status)\n\n    def add(self, address):\n        '''This can be called from the proxy or GUI threads.'''\n        with self.lock:\n            self.new_addresses.add(address)\n\n    def subscribe_to_addresses(self, addresses):\n        if addresses:\n            self.requested_addrs |= addresses\n            self.network.subscribe_to_addresses(addresses, self.on_address_status)\n\n    def get_status(self, h):\n        if not h:\n            return None\n        status = ''\n        for tx_hash, height in h:\n            status += tx_hash + ':%d:' % height\n        return bh2u(hashlib.sha256(status.encode('ascii')).digest())\n\n    def on_address_status(self, response):\n        params, result = self.parse_response(response)\n        if not params:\n            return\n        addr = params[0]\n        history = self.wallet.get_address_history(addr)\n        if self.get_status(history) != result:\n            if self.requested_histories.get(addr) is None:\n                self.requested_histories[addr] = result\n                self.network.request_address_history(addr, self.on_address_history)\n        # remove addr from list only after it is added to requested_histories\n        if addr in self.requested_addrs:  # Notifications won't be in\n            self.requested_addrs.remove(addr)\n\n    def on_address_history(self, response):\n        params, result = self.parse_response(response)\n        if not params:\n            return\n        addr = params[0]\n        self.print_error(\"receiving history\", addr, len(result))\n        server_status = self.requested_histories[addr]\n        hashes = set(map(lambda item: item['tx_hash'], result))\n        hist = list(map(lambda item: (item['tx_hash'], item['height']), result))\n        # tx_fees\n        tx_fees = [(item['tx_hash'], item.get('fee')) for item in result]\n        tx_fees = dict(filter(lambda x:x[1] is not None, tx_fees))\n        # Note if the server hasn't been patched to sort the items properly\n        if hist != sorted(hist, key=lambda x:x[1]):\n            self.network.interface.print_error(\"serving improperly sorted address histories\")\n        # Check that txids are unique\n        if len(hashes) != len(result):\n            self.print_error(\"error: server history has non-unique txids: %s\"% addr)\n        # Check that the status corresponds to what was announced\n        elif self.get_status(hist) != server_status:\n            self.print_error(\"error: status mismatch: %s\" % addr)\n        else:\n            # Store received history\n            self.wallet.receive_history_callback(addr, hist, tx_fees)\n            # Request transactions we don't have\n            self.request_missing_txs(hist)\n        # Remove request; this allows up_to_date to be True\n        self.requested_histories.pop(addr)\n\n    def tx_response(self, response):\n        params, result = self.parse_response(response)\n        if not params:\n            return\n        tx_hash = params[0]\n        #assert tx_hash == hash_encode(Hash(bytes.fromhex(result)))\n        tx = Transaction(result)\n        try:\n            tx.deserialize()\n        except Exception:\n            self.print_msg(\"cannot deserialize transaction, skipping\", tx_hash)\n            return\n        tx_height = self.requested_tx.pop(tx_hash)\n        self.wallet.receive_tx_callback(tx_hash, tx, tx_height)\n        self.print_error(\"received tx %s height: %d bytes: %d\" %\n                         (tx_hash, tx_height, len(tx.raw)))\n        # callbacks\n        self.network.trigger_callback('new_transaction', tx)\n        if not self.requested_tx:\n            self.network.trigger_callback('updated')\n\n\n    def request_missing_txs(self, hist):\n        # \"hist\" is a list of [tx_hash, tx_height] lists\n        requests = []\n        for tx_hash, tx_height in hist:\n            if tx_hash in self.requested_tx:\n                continue\n            if tx_hash in self.wallet.transactions:\n                continue\n            requests.append(('blockchain.transaction.get', [tx_hash]))\n            self.requested_tx[tx_hash] = tx_height\n        self.network.send(requests, self.tx_response)\n\n\n    def initialize(self):\n        '''Check the initial state of the wallet.  Subscribe to all its\n        addresses, and request any transactions in its address history\n        we don't have.\n        '''\n        for history in self.wallet.history.values():\n            # Old electrum servers returned ['*'] when all history for\n            # the address was pruned.  This no longer happens but may\n            # remain in old wallets.\n            if history == ['*']:\n                continue\n            self.request_missing_txs(history)\n\n        if self.requested_tx:\n            self.print_error(\"missing tx\", self.requested_tx)\n        self.subscribe_to_addresses(set(self.wallet.get_addresses()))\n\n    def run(self):\n        '''Called from the network proxy thread main loop.'''\n        # 1. Create new addresses\n        self.wallet.synchronize()\n\n        # 2. Subscribe to new addresses\n        with self.lock:\n            addresses = self.new_addresses\n            self.new_addresses = set()\n        self.subscribe_to_addresses(addresses)\n\n        # 3. Detect if situation has changed\n        up_to_date = self.is_up_to_date()\n        if up_to_date != self.wallet.is_up_to_date():\n            self.wallet.set_up_to_date(up_to_date)\n            self.network.trigger_callback('updated')\n"
  },
  {
    "path": "lib/tests/__init__.py",
    "content": ""
  },
  {
    "path": "lib/tests/test_bitcoin.py",
    "content": "import base64\nimport unittest\nimport sys\nfrom ecdsa.util import number_to_string\n\nfrom lib.bitcoin import (\n    generator_secp256k1, point_to_ser, public_key_to_p2pkh, EC_KEY,\n    bip32_root, bip32_public_derivation, bip32_private_derivation, pw_encode,\n    pw_decode, Hash, public_key_from_private_key, address_from_private_key,\n    is_address, is_private_key, xpub_from_xprv, is_new_seed, is_old_seed,\n    var_int, op_push, address_to_script, regenerate_key,\n    verify_message, deserialize_privkey, serialize_privkey, is_segwit_address,\n    is_b58_address, address_to_scripthash, is_minikey, is_compressed, is_xpub,\n    xpub_type, is_xprv, is_bip32_derivation, seed_type, NetworkConstants)\nfrom lib.util import bfh\n\ntry:\n    import ecdsa\nexcept ImportError:\n    sys.exit(\"Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'\")\n\n\nclass Test_bitcoin(unittest.TestCase):\n\n    def test_crypto(self):\n        for message in [b\"Chancellor on brink of second bailout for banks\", b'\\xff'*512]:\n            self._do_test_crypto(message)\n\n    def _do_test_crypto(self, message):\n        G = generator_secp256k1\n        _r  = G.order()\n        pvk = ecdsa.util.randrange( pow(2,256) ) %_r\n\n        Pub = pvk*G\n        pubkey_c = point_to_ser(Pub,True)\n        #pubkey_u = point_to_ser(Pub,False)\n        addr_c = public_key_to_p2pkh(pubkey_c)\n\n        #print \"Private key            \", '%064x'%pvk\n        eck = EC_KEY(number_to_string(pvk,_r))\n\n        #print \"Compressed public key  \", pubkey_c.encode('hex')\n        enc = EC_KEY.encrypt_message(message, pubkey_c)\n        dec = eck.decrypt_message(enc)\n        self.assertEqual(message, dec)\n\n        #print \"Uncompressed public key\", pubkey_u.encode('hex')\n        #enc2 = EC_KEY.encrypt_message(message, pubkey_u)\n        dec2 = eck.decrypt_message(enc)\n        self.assertEqual(message, dec2)\n\n        signature = eck.sign_message(message, True)\n        #print signature\n        EC_KEY.verify_message(eck, signature, message)\n\n    def test_msg_signing(self):\n        msg1 = b'Chancellor on brink of second bailout for banks'\n        msg2 = b'Electrum'\n\n        def sign_message_with_wif_privkey(wif_privkey, msg):\n            txin_type, privkey, compressed = deserialize_privkey(wif_privkey)\n            key = regenerate_key(privkey)\n            return key.sign_message(msg, compressed)\n\n        sig1 = sign_message_with_wif_privkey(\n            'L1TnU2zbNaAqMoVh65Cyvmcjzbrj41Gs9iTLcWbpJCMynXuap6UN', msg1)\n        addr1 = '15hETetDmcXm1mM4sEf7U2KXC9hDHFMSzz'\n        sig2 = sign_message_with_wif_privkey(\n            '5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD', msg2)\n        addr2 = '1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6'\n\n        sig1_b64 = base64.b64encode(sig1)\n        sig2_b64 = base64.b64encode(sig2)\n\n        self.assertEqual(sig1_b64, b'H/9jMOnj4MFbH3d7t4yCQ9i7DgZU/VZ278w3+ySv2F4yIsdqjsc5ng3kmN8OZAThgyfCZOQxZCWza9V5XzlVY0Y=')\n        self.assertEqual(sig2_b64, b'G84dmJ8TKIDKMT9qBRhpX2sNmR0y5t+POcYnFFJCs66lJmAs3T8A6Sbpx7KA6yTQ9djQMabwQXRrDomOkIKGn18=')\n\n        self.assertTrue(verify_message(addr1, sig1, msg1))\n        self.assertTrue(verify_message(addr2, sig2, msg2))\n\n        self.assertFalse(verify_message(addr1, b'wrong', msg1))\n        self.assertFalse(verify_message(addr1, sig2, msg1))\n\n    def test_aes_homomorphic(self):\n        \"\"\"Make sure AES is homomorphic.\"\"\"\n        payload = u'\\u66f4\\u7a33\\u5b9a\\u7684\\u4ea4\\u6613\\u5e73\\u53f0'\n        password = u'secret'\n        enc = pw_encode(payload, password)\n        dec = pw_decode(enc, password)\n        self.assertEqual(dec, payload)\n\n    def test_aes_encode_without_password(self):\n        \"\"\"When not passed a password, pw_encode is noop on the payload.\"\"\"\n        payload = u'\\u66f4\\u7a33\\u5b9a\\u7684\\u4ea4\\u6613\\u5e73\\u53f0'\n        enc = pw_encode(payload, None)\n        self.assertEqual(payload, enc)\n\n    def test_aes_deencode_without_password(self):\n        \"\"\"When not passed a password, pw_decode is noop on the payload.\"\"\"\n        payload = u'\\u66f4\\u7a33\\u5b9a\\u7684\\u4ea4\\u6613\\u5e73\\u53f0'\n        enc = pw_decode(payload, None)\n        self.assertEqual(payload, enc)\n\n    def test_aes_decode_with_invalid_password(self):\n        \"\"\"pw_decode raises an Exception when supplied an invalid password.\"\"\"\n        payload = u\"blah\"\n        password = u\"uber secret\"\n        wrong_password = u\"not the password\"\n        enc = pw_encode(payload, password)\n        self.assertRaises(Exception, pw_decode, enc, wrong_password)\n\n    def test_hash(self):\n        \"\"\"Make sure the Hash function does sha256 twice\"\"\"\n        payload = u\"test\"\n        expected = b'\\x95MZI\\xfdp\\xd9\\xb8\\xbc\\xdb5\\xd2R&x)\\x95\\x7f~\\xf7\\xfalt\\xf8\\x84\\x19\\xbd\\xc5\\xe8\"\\t\\xf4'\n\n        result = Hash(payload)\n        self.assertEqual(expected, result)\n\n    def test_var_int(self):\n        for i in range(0xfd):\n            self.assertEqual(var_int(i), \"{:02x}\".format(i) )\n\n        self.assertEqual(var_int(0xfd), \"fdfd00\")\n        self.assertEqual(var_int(0xfe), \"fdfe00\")\n        self.assertEqual(var_int(0xff), \"fdff00\")\n        self.assertEqual(var_int(0x1234), \"fd3412\")\n        self.assertEqual(var_int(0xffff), \"fdffff\")\n        self.assertEqual(var_int(0x10000), \"fe00000100\")\n        self.assertEqual(var_int(0x12345678), \"fe78563412\")\n        self.assertEqual(var_int(0xffffffff), \"feffffffff\")\n        self.assertEqual(var_int(0x100000000), \"ff0000000001000000\")\n        self.assertEqual(var_int(0x0123456789abcdef), \"ffefcdab8967452301\")\n\n    def test_op_push(self):\n        self.assertEqual(op_push(0x00), '00')\n        self.assertEqual(op_push(0x12), '12')\n        self.assertEqual(op_push(0x4b), '4b')\n        self.assertEqual(op_push(0x4c), '4c4c')\n        self.assertEqual(op_push(0xfe), '4cfe')\n        self.assertEqual(op_push(0xff), '4dff00')\n        self.assertEqual(op_push(0x100), '4d0001')\n        self.assertEqual(op_push(0x1234), '4d3412')\n        self.assertEqual(op_push(0xfffe), '4dfeff')\n        self.assertEqual(op_push(0xffff), '4effff0000')\n        self.assertEqual(op_push(0x10000), '4e00000100')\n        self.assertEqual(op_push(0x12345678), '4e78563412')\n\n    def test_address_to_script(self):\n        # bech32 native segwit\n        # test vectors from BIP-0173\n        self.assertEqual(address_to_script('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'), '0014751e76e8199196d454941c45d1b3a323f1433bd6')\n        self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')\n        self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e')\n        self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323')\n\n        # base58 P2PKH\n        self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')\n        self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')\n\n        # base58 P2SH\n        self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')\n        self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387')\n\n\nclass Test_bitcoin_testnet(unittest.TestCase):\n\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        NetworkConstants.set_testnet()\n\n    @classmethod\n    def tearDownClass(cls):\n        super().tearDownClass()\n        NetworkConstants.set_mainnet()\n\n    def test_address_to_script(self):\n        # bech32 native segwit\n        # test vectors from BIP-0173\n        self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262')\n        self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433')\n\n        # base58 P2PKH\n        self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX'), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac')\n        self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K'), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac')\n\n        # base58 P2SH\n        self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p'), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87')\n        self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87')\n\n\nclass Test_xprv_xpub(unittest.TestCase):\n\n    xprv_xpub = (\n        # Taken from test vectors in https://en.bitcoin.it/wiki/BIP_0032_TestVectors\n        {'xprv': 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',\n         'xpub': 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',\n         'xtype': 'standard'},\n        {'xprv': 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7',\n         'xpub': 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4',\n         'xtype': 'p2wpkh-p2sh'},\n        {'xprv': 'zprvAWgYBBk7JR8GkraNZJeEodAp2UR1VRWJTXyV1ywuUVs1awUgTiBS1ZTDtLA5F3MFDn1LZzu8dUpSKdT7ToDpvEG6PQu4bJs7zQY47Sd3sEZ',\n         'xpub': 'zpub6jftahH18ngZyLeqfLBFAm7YaWFVttE9pku5pNMX2qPzTjoq1FVgZMmhjecyB2nqFb31gHE9vNvbaggU6vvWpNZbXEWLLUjYjFqG95LNyT8',\n         'xtype': 'p2wpkh'},\n    )\n\n    def _do_test_bip32(self, seed, sequence):\n        xprv, xpub = bip32_root(bfh(seed), 'standard')\n        self.assertEqual(\"m/\", sequence[0:2])\n        path = 'm'\n        sequence = sequence[2:]\n        for n in sequence.split('/'):\n            child_path = path + '/' + n\n            if n[-1] != \"'\":\n                xpub2 = bip32_public_derivation(xpub, path, child_path)\n            xprv, xpub = bip32_private_derivation(xprv, path, child_path)\n            if n[-1] != \"'\":\n                self.assertEqual(xpub, xpub2)\n            path = child_path\n\n        return xpub, xprv\n\n    def test_bip32(self):\n        # see https://en.bitcoin.it/wiki/BIP_0032_TestVectors\n        xpub, xprv = self._do_test_bip32(\"000102030405060708090a0b0c0d0e0f\", \"m/0'/1/2'/2/1000000000\")\n        self.assertEqual(\"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy\", xpub)\n        self.assertEqual(\"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76\", xprv)\n\n        xpub, xprv = self._do_test_bip32(\"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542\",\"m/0/2147483647'/1/2147483646'/2\")\n        self.assertEqual(\"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt\", xpub)\n        self.assertEqual(\"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j\", xprv)\n\n    def test_xpub_from_xprv(self):\n        \"\"\"We can derive the xpub key from a xprv.\"\"\"\n        for xprv_details in self.xprv_xpub:\n            result = xpub_from_xprv(xprv_details['xprv'])\n            self.assertEqual(result, xprv_details['xpub'])\n\n    def test_is_xpub(self):\n        for xprv_details in self.xprv_xpub:\n            xpub = xprv_details['xpub']\n            self.assertTrue(is_xpub(xpub))\n        self.assertFalse(is_xpub('xpub1nval1d'))\n        self.assertFalse(is_xpub('xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG'))\n\n    def test_xpub_type(self):\n        for xprv_details in self.xprv_xpub:\n            xpub = xprv_details['xpub']\n            self.assertEqual(xprv_details['xtype'], xpub_type(xpub))\n\n    def test_is_xprv(self):\n        for xprv_details in self.xprv_xpub:\n            xprv = xprv_details['xprv']\n            self.assertTrue(is_xprv(xprv))\n        self.assertFalse(is_xprv('xprv1nval1d'))\n        self.assertFalse(is_xprv('xprv661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52WRONGBADWRONG'))\n\n    def test_is_bip32_derivation(self):\n        self.assertTrue(is_bip32_derivation(\"m/0'/1\"))\n        self.assertTrue(is_bip32_derivation(\"m/0'/0'\"))\n        self.assertTrue(is_bip32_derivation(\"m/44'/0'/0'/0/0\"))\n        self.assertTrue(is_bip32_derivation(\"m/49'/0'/0'/0/0\"))\n        self.assertFalse(is_bip32_derivation(\"mmmmmm\"))\n        self.assertFalse(is_bip32_derivation(\"n/\"))\n        self.assertFalse(is_bip32_derivation(\"\"))\n        self.assertFalse(is_bip32_derivation(\"m/q8462\"))\n\n\nclass Test_keyImport(unittest.TestCase):\n\n    priv_pub_addr = (\n           {'priv': 'KzMFjMC2MPadjvX5Cd7b8AKKjjpBSoRKUTpoAtN6B3J9ezWYyXS6',\n            'pub': '02c6467b7e621144105ed3e4835b0b4ab7e35266a2ae1c4f8baa19e9ca93452997',\n            'address': '17azqT8T16coRmWKYFj3UjzJuxiYrYFRBR',\n            'minikey' : False,\n            'txin_type': 'p2pkh',\n            'compressed': True,\n            'addr_encoding': 'base58',\n            'scripthash': 'c9aecd1fef8d661a42c560bf75c8163e337099800b8face5ca3d1393a30508a7'},\n           {'priv': '5Hxn5C4SQuiV6e62A1MtZmbSeQyrLFhu5uYks62pU5VBUygK2KD',\n            'pub': '04e5fe91a20fac945845a5518450d23405ff3e3e1ce39827b47ee6d5db020a9075422d56a59195ada0035e4a52a238849f68e7a325ba5b2247013e0481c5c7cb3f',\n            'address': '1GPHVTY8UD9my6jyP4tb2TYJwUbDetyNC6',\n            'minikey': False,\n            'txin_type': 'p2pkh',\n            'compressed': False,\n            'addr_encoding': 'base58',\n            'scripthash': 'f5914651408417e1166f725a5829ff9576d0dbf05237055bf13abd2af7f79473'},\n           {'priv': 'LHJnnvRzsdrTX2j5QeWVsaBkabK7gfMNqNNqxnbBVRaJYfk24iJz',\n            'pub': '0279ad237ca0d812fb503ab86f25e15ebd5fa5dd95c193639a8a738dcd1acbad81',\n            'address': '3GeVJB3oKr7psgKR6BTXSxKtWUkfsHHhk7',\n            'minikey': False,\n            'txin_type': 'p2wpkh-p2sh',\n            'compressed': True,\n            'addr_encoding': 'base58',\n            'scripthash': 'd7b04e882fa6b13246829ac552a2b21461d9152eb00f0a6adb58457a3e63d7c5'},\n           {'priv': 'L8g5V8kFFeg2WbecahRSdobARbHz2w2STH9S8ePHVSY4fmia7Rsj',\n            'pub': '03e9f948421aaa89415dc5f281a61b60dde12aae3181b3a76cd2d849b164fc6d0b',\n            'address': 'bc1qqmpt7u5e9hfznljta5gnvhyvfd2kdd0r90hwue',\n            'minikey': False,\n            'txin_type': 'p2wpkh',\n            'compressed': True,\n            'addr_encoding': 'bech32',\n            'scripthash': '1929acaaef3a208c715228e9f1ca0318e3a6b9394ab53c8d026137f847ecf97b'},\n           # from http://bitscan.com/articles/security/spotlight-on-mini-private-keys\n           {'priv': 'SzavMBLoXU6kDrqtUVmffv',\n            'pub': '02588d202afcc1ee4ab5254c7847ec25b9a135bbda0f2bc69ee1a714749fd77dc9',\n            'address': '19GuvDvMMUZ8vq84wT79fvnvhMd5MnfTkR',\n            'minikey': True,\n            'txin_type': 'p2pkh',\n            'compressed': True,  # this is actually ambiguous... issue #2748\n            'addr_encoding': 'base58',\n            'scripthash': '60ad5a8b922f758cd7884403e90ee7e6f093f8d21a0ff24c9a865e695ccefdf1'},\n    )\n\n    def test_public_key_from_private_key(self):\n        for priv_details in self.priv_pub_addr:\n            txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])\n            result = public_key_from_private_key(privkey, compressed)\n            self.assertEqual(priv_details['pub'], result)\n            self.assertEqual(priv_details['txin_type'], txin_type)\n            self.assertEqual(priv_details['compressed'], compressed)\n\n    def test_address_from_private_key(self):\n        for priv_details in self.priv_pub_addr:\n            addr2 = address_from_private_key(priv_details['priv'])\n            self.assertEqual(priv_details['address'], addr2)\n\n    def test_is_valid_address(self):\n        for priv_details in self.priv_pub_addr:\n            addr = priv_details['address']\n            self.assertFalse(is_address(priv_details['priv']))\n            self.assertFalse(is_address(priv_details['pub']))\n            self.assertTrue(is_address(addr))\n\n            is_enc_b58 = priv_details['addr_encoding'] == 'base58'\n            self.assertEqual(is_enc_b58, is_b58_address(addr))\n\n            is_enc_bech32 = priv_details['addr_encoding'] == 'bech32'\n            self.assertEqual(is_enc_bech32, is_segwit_address(addr))\n\n        self.assertFalse(is_address(\"not an address\"))\n\n    def test_is_private_key(self):\n        for priv_details in self.priv_pub_addr:\n            self.assertTrue(is_private_key(priv_details['priv']))\n            self.assertFalse(is_private_key(priv_details['pub']))\n            self.assertFalse(is_private_key(priv_details['address']))\n        self.assertFalse(is_private_key(\"not a privkey\"))\n\n    def test_serialize_privkey(self):\n        for priv_details in self.priv_pub_addr:\n            txin_type, privkey, compressed = deserialize_privkey(priv_details['priv'])\n            priv2 = serialize_privkey(privkey, compressed, txin_type)\n            if not priv_details['minikey']:\n                self.assertEqual(priv_details['priv'], priv2)\n\n    def test_address_to_scripthash(self):\n        for priv_details in self.priv_pub_addr:\n            sh = address_to_scripthash(priv_details['address'])\n            self.assertEqual(priv_details['scripthash'], sh)\n\n    def test_is_minikey(self):\n        for priv_details in self.priv_pub_addr:\n            minikey = priv_details['minikey']\n            priv = priv_details['priv']\n            self.assertEqual(minikey, is_minikey(priv))\n\n    def test_is_compressed(self):\n        for priv_details in self.priv_pub_addr:\n            self.assertEqual(priv_details['compressed'],\n                             is_compressed(priv_details['priv']))\n\n\nclass Test_seeds(unittest.TestCase):\n    \"\"\" Test old and new seeds. \"\"\"\n\n    mnemonics = {\n        ('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),\n        ('cell dumb heartbeat north boom tease ' * 4, 'old'),\n        ('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),\n        ('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),\n        ('   cElL  DuMb hEaRtBeAt nOrTh bOoM  TeAsE ShIp    bAbY BrIgHt kInGdOm rArE SqUeEzE   ', 'old'),\n        # below seed is actually 'invalid old' as it maps to 33 hex chars\n        ('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),\n        ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),\n        ('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),\n        ('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),\n        ('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),\n        ('   oStRiCh sEcUrItY DeEr aUnT ClImB       InNeR AlPhA ArM MuTuAl mArBlE   SoLiD TaSk  ', 'standard'),\n        ('x8', 'standard'),\n        ('science dawn member doll dutch real can brick knife deny drive list', '2fa'),\n        ('science dawn member doll dutch real ca brick knife deny drive list', ''),\n        (' sCience dawn   member doll Dutch rEAl can brick knife deny drive  lisT', '2fa'),\n        ('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),\n        ('  fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),\n        ('9dk', 'segwit'),\n    }\n    \n    def test_new_seed(self):\n        seed = \"cram swing cover prefer miss modify ritual silly deliver chunk behind inform able\"\n        self.assertTrue(is_new_seed(seed))\n\n        seed = \"cram swing cover prefer miss modify ritual silly deliver chunk behind inform\"\n        self.assertFalse(is_new_seed(seed))\n\n    def test_old_seed(self):\n        self.assertTrue(is_old_seed(\" \".join([\"like\"] * 12)))\n        self.assertFalse(is_old_seed(\" \".join([\"like\"] * 18)))\n        self.assertTrue(is_old_seed(\" \".join([\"like\"] * 24)))\n        self.assertFalse(is_old_seed(\"not a seed\"))\n\n        self.assertTrue(is_old_seed(\"0123456789ABCDEF\" * 2))\n        self.assertTrue(is_old_seed(\"0123456789ABCDEF\" * 4))\n\n    def test_seed_type(self):\n        for seed_words, _type in self.mnemonics:\n            self.assertEqual(_type, seed_type(seed_words), msg=seed_words)\n"
  },
  {
    "path": "lib/tests/test_interface.py",
    "content": "import unittest\n\nfrom lib import interface\n\n\nclass TestInterface(unittest.TestCase):\n\n    def test_match_host_name(self):\n        self.assertTrue(interface._match_hostname('asd.fgh.com', 'asd.fgh.com'))\n        self.assertFalse(interface._match_hostname('asd.fgh.com', 'asd.zxc.com'))\n        self.assertTrue(interface._match_hostname('asd.fgh.com', '*.fgh.com'))\n        self.assertFalse(interface._match_hostname('asd.fgh.com', '*fgh.com'))\n        self.assertFalse(interface._match_hostname('asd.fgh.com', '*.zxc.com'))\n\n    def test_check_host_name(self):\n        i = interface.TcpConnection(server=':1:', queue=None, config_path=None)\n\n        self.assertFalse(i.check_host_name(None, None))\n        self.assertFalse(i.check_host_name(\n            peercert={'subjectAltName': []}, name=''))\n        self.assertTrue(i.check_host_name(\n            peercert={'subjectAltName': [('DNS', 'foo.bar.com')]},\n            name='foo.bar.com'))\n        self.assertTrue(i.check_host_name(\n            peercert={'subject': [('commonName', 'foo.bar.com')]},\n            name='foo.bar.com'))\n"
  },
  {
    "path": "lib/tests/test_mnemonic.py",
    "content": "import unittest\nfrom lib import keystore\nfrom lib import mnemonic\nfrom lib import old_mnemonic\nfrom lib.util import bh2u\n\n\nclass Test_NewMnemonic(unittest.TestCase):\n\n    def test_to_seed(self):\n        seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar', passphrase='none')\n        self.assertEqual(bh2u(seed),\n                          '741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5b'\n                          'b22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce')\n\n    def test_random_seeds(self):\n        iters = 10\n        m = mnemonic.Mnemonic(lang='en')\n        for _ in range(iters):\n            seed = m.make_seed()\n            i = m.mnemonic_decode(seed)\n            self.assertEqual(m.mnemonic_encode(i), seed)\n\n\nclass Test_OldMnemonic(unittest.TestCase):\n\n    def test(self):\n        seed = '8edad31a95e7d59f8837667510d75a4d'\n        result = old_mnemonic.mn_encode(seed)\n        words = 'hardly point goal hallway patience key stone difference ready caught listen fact'\n        self.assertEqual(result, words.split())\n        self.assertEqual(old_mnemonic.mn_decode(result), seed)\n\nclass Test_BIP39Checksum(unittest.TestCase):\n\n    def test(self):\n        mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'\n        is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic)\n        self.assertTrue(is_wordlist_valid)\n        self.assertTrue(is_checksum_valid)\n"
  },
  {
    "path": "lib/tests/test_simple_config.py",
    "content": "import ast\nimport sys\nimport os\nimport unittest\nimport tempfile\nimport shutil\n\nfrom io import StringIO\nfrom lib.simple_config import (SimpleConfig, read_system_config,\n                               read_user_config)\n\n\nclass Test_SimpleConfig(unittest.TestCase):\n\n    def setUp(self):\n        super(Test_SimpleConfig, self).setUp()\n        # make sure \"read_user_config\" and \"user_dir\" return a temporary directory.\n        self.electrum_dir = tempfile.mkdtemp()\n        # Do the same for the user dir to avoid overwriting the real configuration\n        # for development machines with electrum installed :)\n        self.user_dir = tempfile.mkdtemp()\n\n        self.options = {\"electrum_path\": self.electrum_dir}\n        self._saved_stdout = sys.stdout\n        self._stdout_buffer = StringIO()\n        sys.stdout = self._stdout_buffer\n\n    def tearDown(self):\n        super(Test_SimpleConfig, self).tearDown()\n        # Remove the temporary directory after each test (to make sure we don't\n        # pollute /tmp for nothing.\n        shutil.rmtree(self.electrum_dir)\n        shutil.rmtree(self.user_dir)\n\n        # Restore the \"real\" stdout\n        sys.stdout = self._saved_stdout\n\n    def test_simple_config_key_rename(self):\n        \"\"\"auto_cycle was renamed auto_connect\"\"\"\n        fake_read_system = lambda : {}\n        fake_read_user = lambda _: {\"auto_cycle\": True}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options=self.options,\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(config.get(\"auto_connect\"), True)\n        self.assertEqual(config.get(\"auto_cycle\"), None)\n        fake_read_user = lambda _: {\"auto_connect\": False, \"auto_cycle\": True}\n        config = SimpleConfig(options=self.options,\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(config.get(\"auto_connect\"), False)\n        self.assertEqual(config.get(\"auto_cycle\"), None)\n\n    def test_simple_config_command_line_overrides_everything(self):\n        \"\"\"Options passed by command line override all other configuration\n        sources\"\"\"\n        fake_read_system = lambda : {\"electrum_path\": \"a\"}\n        fake_read_user = lambda _: {\"electrum_path\": \"b\"}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options=self.options,\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(self.options.get(\"electrum_path\"),\n                         config.get(\"electrum_path\"))\n\n    def test_simple_config_user_config_overrides_system_config(self):\n        \"\"\"Options passed in user config override system config.\"\"\"\n        fake_read_system = lambda : {\"electrum_path\": self.electrum_dir}\n        fake_read_user = lambda _: {\"electrum_path\": \"b\"}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(\"b\", config.get(\"electrum_path\"))\n\n    def test_simple_config_system_config_ignored_if_portable(self):\n        \"\"\"If electrum is started with the \"portable\" flag, system\n        configuration is completely ignored.\"\"\"\n        fake_read_system = lambda : {\"some_key\": \"some_value\"}\n        fake_read_user = lambda _: {}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={\"portable\": True},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(config.get(\"some_key\"), None)\n\n    def test_simple_config_user_config_is_used_if_others_arent_specified(self):\n        \"\"\"If no system-wide configuration and no command-line options are\n        specified, the user configuration is used instead.\"\"\"\n        fake_read_system = lambda : {}\n        fake_read_user = lambda _: {\"electrum_path\": self.electrum_dir}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        self.assertEqual(self.options.get(\"electrum_path\"),\n                         config.get(\"electrum_path\"))\n\n    def test_cannot_set_options_passed_by_command_line(self):\n        fake_read_system = lambda : {}\n        fake_read_user = lambda _: {\"electrum_path\": \"b\"}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options=self.options,\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        config.set_key(\"electrum_path\", \"c\")\n        self.assertEqual(self.options.get(\"electrum_path\"),\n                         config.get(\"electrum_path\"))\n\n    def test_can_set_options_from_system_config(self):\n        fake_read_system = lambda : {\"electrum_path\": self.electrum_dir}\n        fake_read_user = lambda _: {}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        config.set_key(\"electrum_path\", \"c\")\n        self.assertEqual(\"c\", config.get(\"electrum_path\"))\n\n    def test_can_set_options_set_in_user_config(self):\n        another_path = tempfile.mkdtemp()\n        fake_read_system = lambda : {}\n        fake_read_user = lambda _: {\"electrum_path\": self.electrum_dir}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        config.set_key(\"electrum_path\", another_path)\n        self.assertEqual(another_path, config.get(\"electrum_path\"))\n\n    def test_can_set_options_from_system_config_if_portable(self):\n        \"\"\"If the \"portable\" flag is set, the user can overwrite system\n        configuration options.\"\"\"\n        another_path = tempfile.mkdtemp()\n        fake_read_system = lambda : {\"electrum_path\": self.electrum_dir}\n        fake_read_user = lambda _: {}\n        read_user_dir = lambda : self.user_dir\n        config = SimpleConfig(options={\"portable\": True},\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        config.set_key(\"electrum_path\", another_path)\n        self.assertEqual(another_path, config.get(\"electrum_path\"))\n\n    def test_user_config_is_not_written_with_read_only_config(self):\n        \"\"\"The user config does not contain command-line options or system\n        options when saved.\"\"\"\n        fake_read_system = lambda : {\"something\": \"b\"}\n        fake_read_user = lambda _: {\"something\": \"a\"}\n        read_user_dir = lambda : self.user_dir\n        self.options.update({\"something\": \"c\"})\n        config = SimpleConfig(options=self.options,\n                              read_system_config_function=fake_read_system,\n                              read_user_config_function=fake_read_user,\n                              read_user_dir_function=read_user_dir)\n        config.save_user_config()\n        contents = None\n        with open(os.path.join(self.electrum_dir, \"config\"), \"r\") as f:\n            contents = f.read()\n        result = ast.literal_eval(contents)\n        self.assertEqual({\"something\": \"a\"}, result)\n\n\nclass TestSystemConfig(unittest.TestCase):\n\n    sample_conf = \"\"\"\n[client]\ngap_limit = 5\n\n[something_else]\neverything = 42\n\"\"\"\n\n    def setUp(self):\n        super(TestSystemConfig, self).setUp()\n        self.thefile = tempfile.mkstemp(suffix=\".electrum.test.conf\")[1]\n\n    def tearDown(self):\n        super(TestSystemConfig, self).tearDown()\n        os.remove(self.thefile)\n\n    def test_read_system_config_file_does_not_exist(self):\n        somefile = \"/foo/I/do/not/exist/electrum.conf\"\n        result = read_system_config(somefile)\n        self.assertEqual({}, result)\n\n    def test_read_system_config_file_returns_file_options(self):\n        with open(self.thefile, \"w\") as f:\n            f.write(self.sample_conf)\n\n        result = read_system_config(self.thefile)\n        self.assertEqual({\"gap_limit\": \"5\"}, result)\n\n    def test_read_system_config_file_no_sections(self):\n\n        with open(self.thefile, \"w\") as f:\n            f.write(\"gap_limit = 5\")  # The file has no sections at all\n\n        result = read_system_config(self.thefile)\n        self.assertEqual({}, result)\n\n\nclass TestUserConfig(unittest.TestCase):\n\n    def setUp(self):\n        super(TestUserConfig, self).setUp()\n        self._saved_stdout = sys.stdout\n        self._stdout_buffer = StringIO()\n        sys.stdout = self._stdout_buffer\n\n        self.user_dir = tempfile.mkdtemp()\n\n    def tearDown(self):\n        super(TestUserConfig, self).tearDown()\n        shutil.rmtree(self.user_dir)\n        sys.stdout = self._saved_stdout\n\n    def test_no_path_means_no_result(self):\n       result = read_user_config(None)\n       self.assertEqual({}, result)\n\n    def test_path_without_config_file(self):\n        \"\"\"We pass a path but if does not contain a \"config\" file.\"\"\"\n        result = read_user_config(self.user_dir)\n        self.assertEqual({}, result)\n\n    def test_path_with_reprd_object(self):\n\n        class something(object):\n            pass\n\n        thefile = os.path.join(self.user_dir, \"config\")\n        payload = something()\n        with open(thefile, \"w\") as f:\n            f.write(repr(payload))\n\n        result = read_user_config(self.user_dir)\n        self.assertEqual({}, result)\n"
  },
  {
    "path": "lib/tests/test_storage_upgrade.py",
    "content": "import shutil\nimport tempfile\n\nfrom lib.storage import WalletStorage\nfrom lib.wallet import Wallet\n\nfrom lib.tests.test_wallet import WalletTestCase\n\n\n# TODO add other wallet types: 2fa, xpub-only\n# TODO hw wallet with client version 2.6.x (single-, and multiacc)\nclass TestStorageUpgrade(WalletTestCase):\n\n    def test_upgrade_from_client_1_9_8_seeded(self):\n        wallet_str = \"{'addr_history':{'177hEYTccmuYH8u68pYfaLteTxwJrVgvJj':[],'15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc':[],'1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf':[],'1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs':[],'1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC':[],'1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm':[],'1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj':[],'1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC','1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj','177hEYTccmuYH8u68pYfaLteTxwJrVgvJj','1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm','15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc'],1:['1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs','1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa','1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf']}},'seed_version':4}\"\n        self._upgrade_storage(wallet_str)\n\n    # TODO pre-2.0 mixed wallets are not split currently\n    #def test_upgrade_from_client_1_9_8_mixed(self):\n    #    wallet_str = \"{'addr_history':{'15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc':[],'177hEYTccmuYH8u68pYfaLteTxwJrVgvJj':[],'1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC':[],'1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm':[],'1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj':[],'1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf':[],'1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs':[],'1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa':[]},'accounts_expanded':{},'master_public_key':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb','use_encryption':False,'seed':'2605aafe50a45bdf2eb155302437e678','accounts':{0:{0:['1DjtUCcQwwzA3GSPA7Kd79PMnri7tLDPYC','1PAgpPxnL42Hp3cWxmSfdChPqqGiM8g7zj','177hEYTccmuYH8u68pYfaLteTxwJrVgvJj','1PGEgaPG1XJqmuSj68GouotWeYkCtwo4wm','15V7MsQK2vjF5aEXLVG11qi2eZPZsXdnYc'],1:['1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs','1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa','1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf'],'mpk':'756d1fe6ded28d43d4fea902a9695feb785447514d6e6c3bdf369f7c3432fdde4409e4efbffbcf10084d57c5a98d1f34d20ac1f133bdb64fa02abf4f7bde1dfb'}},'imported_keys':{'15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA':'5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq','1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6':'L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U','1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr':'L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM'},'seed_version':4}\"\n    #    self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_0_4_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"03d8e267e8de7769b52a8727585b3c44b4e148b86b2c90e3393f78a75bd6aab83f\",\"03f09b3562bec870b4eb8626c20d449ee85ef17ea896a6a82b454e092eef91b296\",\"02df953880df9284715e8199254edcf3708c635adc92a90dbf97fbd64d1eb88a36\"],\"receiving\":[\"02cd4d73d5e335dafbf5c9338f88ceea3d7511ab0f9b8910745ac940ff40913a30\",\"0243ed44278a178101e0fb14d36b68e6e13d00fe3434edb56e4504ea6f5db2e467\",\"0367c0aa3681ec3635078f79f8c78aa339f19e38d9e1c9e2853e30e66ade02cac3\",\"0237d0fe142cff9d254a3bdd3254f0d5f72676b0099ba799764a993a0d0ba80111\",\"020a899fd417527b3929c8f625c93b45392244bab69ff91b582ed131977d5cd91e\",\"039e84264920c716909b88700ef380336612f48237b70179d0b523784de28101f7\",\"03125452df109a51be51fe21e71c3a4b0bba900c9c0b8d29b4ee2927b51f570848\",\"0291fa554217090bab96eeff63e1c6fdec37358ed597d18fa32c60c02a48878c8c\",\"030b6354a4365bab55e86269fb76241fd69716f02090ead389e1fce13d474aa569\",\"023dcba431d8887ab63595f0df1e978e4a5f1c3aac6670e43d03956448a229f740\",\"0332a61cbe04fe027033369ce7569b860c24462878bdd8c0332c22a3f5fdcc1790\",\"021249480422d93dba2aafcd4575e6f630c4e3a2a832dd8a15f884e1052b6836e4\",\"02516e91dede15d3a15dd648591bb92e107b3a53d5bc34b286ab389ce1af3130aa\",\"02e1da3dddd81fa6e4895816da9d4b8ab076d6ea8034b1175169c0f247f002f4cf\",\"0390ef1e3fdbe137767f8b5abad0088b105eee8c39e075305545d405be3154757a\",\"03fca30eb33c6e1ffa071d204ccae3060680856ae9b93f31f13dd11455e67ee85d\",\"034f6efdbbe1bfa06b32db97f16ff3a0dd6cf92769e8d9795c465ff76d2fbcb794\",\"021e2901009954f23d2bf3429d4a531c8ca3f68e9598687ef816f20da08ff53848\",\"02d3ccf598939ff7919ee23d828d229f85e3e58842582bf054491c59c8b974aa6e\",\"03a1daffa39f42c1aaae24b859773a170905c6ee8a6dab8c1bfbfc93f09b88f4db\"],\"xpub\":\"xpub661MyMwAqRbcFsrzES8RWNiD7RxDqT4p8NjvTY9mLi8xdphQ9x1TiY8GnqCpQx4LqJBdcGeXrsAa2b2G7ZcjJcest9wHcqYfTqXmQja6vfV\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K3PnX8QbR9EmUZQ7jRzLxm9pKf9k9nNbym2NFcQhDAjonwZ39jtWLYp6qk5UHotj13p2y7w1ZhhvvyV5eCcaPUrKofs9CXQ9\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcFsrzES8RWNiD7RxDqT4p8NjvTY9mLi8xdphQ9x1TiY8GnqCpQx4LqJBdcGeXrsAa2b2G7ZcjJcest9wHcqYfTqXmQja6vfV\"},\"seed\":\"seven direct thunder glare prevent please fatal blush buzz artefact gate vendor above\",\"seed_version\":11,\"use_encryption\":false,\"wallet_type\":\"standard\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_0_4_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"use_encryption\":false,\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_0_4_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_0_4_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_0_4_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]]},\"labels\":{\"0\":\"Main account\",\"1\":\"acc1\"},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_0_4_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"03c3a8549f35d7842192e7e00afa25ef1c779d05f1c891ba7c30de968fb29e3e78\",\"02e191e105bccf1b4562d216684632b9ec22c87e1457b537eb27516afa75c56831\"],[\"03793397f02b3bd3d0f6f0dafc7d42b9701234a269805d89efbbc2181683368e4b\",\"02153705b8e4df41dc9d58bc0360c79a9209b3fc289ec54118f0b149d5a3b3546d\"],[\"02511e8cfb39c8ce1c790f26bcab68ba5d5f79845ec1c6a92b0ac9f331648d866a\",\"02c29c1ea70e23d866204a11ec8d8ecd70d6f51f58dd8722824cacb1985d4d1870\"]],\"receiving\":[[\"0283ce4f0f12811e1b27438a3edb784aeb600ca7f4769c9c49c3e704e216421d3e\",\"03a1bbada7401cade3b25a23e354186c772e2ba4ac0d9c0447627f7d540eb9891d\"],[\"0286b45a0bcaa215716cbc59a22b6b1910f9ebad5884f26f55c2bb38943ee8fdb6\",\"02799680336c6bd19005588fad12256223cb8a416649d60ea5d164860c0872b931\"],[\"039e2bf377709e41bba49fb1f3f873b9b87d50ae3b574604cd9b96402211ea1f36\",\"02ef9ceaaf754ba46f015e1d704f1a06157cc4441da0cfaf096563b22ec225ca5f\"],[\"025220baaca5bff1a5ffbf4d36e9fcc6f5d05f4af750ef29f6d88d9b5f95fef79a\",\"02350c81bebfa3a894df69302a6601175731d443948a12d8ec7860981988e3803e\"],[\"028fd6411534d722b625482659de54dd609f5b5c935ae8885ca24bfd3266210527\",\"03b9c7780575f17e64f9dfd5947945b1dbdb65aecef562ac076335fd7aa09844e4\"],[\"0353066065985ec06dbef33e7a081d9240023891a51c4e9eda7b3eb1b4af165e04\",\"028c3fa7622e4c8bac07a2c549885a045532e67a934ca10e20729d0fdfe3a75339\"],[\"02253b4eabf2834af86b409d5ca8e671de9a75c3937bff2dac9521c377ca195668\",\"02d5e83c445684eb502049f48e621e1ca16e07e5dc4013c84d661379635f58877b\"],[\"030d38e4c7a5c7c9551adcace3b70dcaa02bf841febd6dc308f3abd7b7bf2bdc49\",\"0375a0b50cd7f3af51550207a766c5db326b2294f5a4b456a90190e4fbeb720d97\"],[\"0327280215ba4a0d8c404085c4f6091906a9e1ada7ce4202a640ac701446095954\",\"037cd9b5e6664d28a61e01626056cdb7e008815b365c8b65fa50ac44d6c1ad126e\"],[\"02f80a80146674da828fc67a062d1ab47fb0714cf40ec5c517ee23ea71d3033474\",\"03fd8ab9bc9458b87e0b7b2a46ea6b46de0a5f6ecaf1a204579698bfa881ff93ce\"],[\"034965bd56c6ca97e0e5ffa79cdc1f15772fa625b76da84cc8adb1707e2e101775\",\"033e13cb19d930025bfc801b829e64d12934f9f19df718f4ea6160a4fb61320a9c\"],[\"034de271009a06d733de22601c3d3c6fe8b3ec5a44f49094ac002dc1c90a3b096d\",\"023f0b2f653c0fdbdc292040fee363ceaa5828cfd8e012abcf6cd9bad2eaa3dc72\"],[\"022aec8931c5b17bdcdd6637db34718db6f267cb0a55a611eb6602e15deb6ed4df\",\"021de5d4bbb73b6dfab2c0df6970862b08130902ff3160f31681f34aecf39721f6\"],[\"02a0e3b52293ec73f89174ff6e5082fcfebc45f2fdd9cfe12a6981aa120a7c1fa7\",\"0371d41b5f18e8e1990043c1e52f998937bc7e81b8ace4ddfc5cd0d029e4c81894\"],[\"030bc1cbe4d750067254510148e3af9bc84925cdd17db3b54d9bbf4a409b83719a\",\"0371c4800364a8a32bfbda7ea7724c1f5bdbd794df8a6080a3bd3b52c52cf32402\"],[\"0318c5cd5f19ff037e3dec3ce5ac1a48026f5a58c4129271b12ae22f8542bcd718\",\"03b5c70db71d520d04f810742e7a5f42d810e94ec6cbf4b48fa6dd7b4d425e76c1\"],[\"0213f68b86a8c4a0840fa88d9a06904c59292ec50172813b8cca62768f3b708811\",\"0353037209eb400ba7fcfa9f296a8b2745e1bbcbfb28c4adebf74de2e0e6a58c00\"],[\"028decff8a7f5a7982402d95b050fbc9958e449f154990bbfe0f553a1d4882fd03\",\"025ecd14812876e885d8f54cab30d1c2a8ae6c6ed0847e96abd65a3700148d94e2\"],[\"0267f8dab8fdc1df4231414f31cfeb58ce96f3471ba78328cd429263d151c81fed\",\"03e0d01df1fd9e958a7324d29afefbc76793a40447a2625c494355c577727d69ba\"],[\"03de3c4d173b27cdfdd8e56fbf3cd6ee8729b94209c20e5558ddd7a76281a37e2e\",\"0218ccb595d7fa559f0bae1ea76d19526980b027fb9be009b6b486d8f8eb0e00d5\"]],\"xpub\":\"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e\",\"xpub2\":\"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA\"}},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K2zA5ozHsbqT4BgTD45vGhx1edUg7tN4qp4LFbwCwEAGK3ZVaBaCRQnuy7AJ7qbPGxKiynNtGd7CzjBXEV4mEwStnPo98Xve\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcFUEYv1psxyPnjiHhTYe85AwFRs5jShbpgrfQ9UXBmxantqgGT3oAVLiHDYoR3ruT3xRGcxsmBMJxyg94FGcxF86QnzYDc6e\",\"x2/\":\"xpub661MyMwAqRbcGFd5DccFn4YW2HEdPhVZ2NEBAn416bvDFBi8HN5udmB6DkWpuXFtXaXZdq9UvMoiHxaauk6R1CZgKUR8vpng4LoudP4YVXA\"},\"seed\":\"start accuse bounce inhale crucial infant october radar enforce stage dumb spot account\",\"seed_version\":11,\"use_encryption\":false,\"wallet_type\":\"2of2\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_1_1_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"03cbd39265f007d39045ccab5833e1ae16c357f9d35e67099d8e41940bf63ec330\",\"03c94e9590d9bcd579caae15d062053e2820fe2a405c153dd4dca4618b7172ea6f\",\"028a875b6f7e56f8cba66a1cec5dc1dfca9df79b7c92702d0a551c6c1b49d0f59b\"],\"receiving\":[\"02fa100994f912df3e9538c244856828531f84e707f4d9eccfdd312c2e3ef7cf10\",\"02fe230740aa27ace4f4b2e8b330cd57792051acf03652ae1622704d7eb7d4e5e4\",\"03e3f65a991f417d69a732e040090c8c2f18baf09c3a9dc8aa465949aeb0b3271f\",\"0382aa34a9cb568b14ebae35e69b3be6462d9ed8f30d48e0a6983e5af74fa441d3\",\"03dfd8638e751e48fd42bf020874f49fbb5f54e96eff67d72eeeda3aa2f84f01c6\",\"033904139de555bdf978e45931702c27837312ed726736eeff340ca6e0a439d232\",\"03c6ca845d5bd9055f8889edcd53506cf714ac1042d9e059db630ec7e1af34133d\",\"030b3bafc8a4ff8822951d4983f65b9bc43552c8181937188ba8c26e4c1d1be3ab\",\"03828c371d3984ca5a248997a3e096ce21f9aeeb2f2a16457784b92a55e2aef288\",\"033f42b4fbc434a587f6c6a0d10ac401f831a77c9e68453502a50fe278b6d9265c\",\"0384e2c23268e2eb88c674c860519217af42fd6816273b299f0a6c39ddcc05bfa2\",\"0257c60adde9edca8c14b6dd804004abc66bac17cc2acbb0490fcab8793289b921\",\"02e2a67b1618a3a449f45296ea72a8fa9d8be6c58759d11d038c2fe034981efa73\",\"02a9ef53a502b3a38c2849b130e2b20de9e89b023274463ea1a706ed92719724eb\",\"037fc8802a11ba7ef06682908c24bcaedca1e2240111a1dd229bf713e2aa1d65a1\",\"03ea0685fbd134545869234d1f219fff951bc3ec9e3e7e41d8b90283cd3f445470\",\"0296bbe06cdee522b6ee654cc3592fce1795e9ff4dc0e2e2dea8acaf6d2d6b953b\",\"036beac563bc85f9bc479a15d1937ea8e2c20637825a134c01d257d43addab217a\",\"03389a4a6139de61a2e0e966b07d7b25b0c5f3721bf6fdcad20e7ae11974425bd9\",\"026cffa2321319433518d75520c3a852542e0fa8b95e2cf4af92932a7c48ee9dbd\"],\"xpub\":\"xpub661MyMwAqRbcGDxKhL5YS1kaB5B7q8H6xPZwCrgZ1iE2XXaiUeqD9MFEYRAuX7UNfdAED9yhAZdCB4ZS8dFrGDVU3x9ZK8uej8u8Pa2DLMq\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K3jsrbJYY4soqd3LdRfZFbAeLQUGwTNh3ejFZw7WxbYvkhAmPM88Swt1JwFX6DVGjPXeUcGcqa1XFuJPeiQaC9wiZ16PTKgQ\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcGDxKhL5YS1kaB5B7q8H6xPZwCrgZ1iE2XXaiUeqD9MFEYRAuX7UNfdAED9yhAZdCB4ZS8dFrGDVU3x9ZK8uej8u8Pa2DLMq\"},\"pruned_txo\":{},\"seed\":\"flat toe story egg tide casino leave liquid strike cat busy knife absorb\",\"seed_version\":11,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"standard\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_1_1_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_1_1_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_1_1_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"pruned_txo\":{},\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_1_1_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"labels\":{},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"pruned_txo\":{},\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"txi\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{}},\"txo\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[0,5000,false]]}},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_1_1_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"03b5ca15f87baa1bb9d2508a9cf7cb596915a2749a6932bd71a5f353d72e2ff51e\",\"03069d12bb7dc9fe7b8dab9ab2c7828173a4a4a5bacb10b9004854aef2ada2e440\"],[\"036d7aeef82d50520f7d30d20a6b58a5e61c40949af4c147a105a8724478ba6339\",\"021208a4a6c76934fbc2eed72a4a71713a5a093fb203ec3197edd1e4be8d9fb342\"],[\"03ee5bd2bc7f9800b85f6f0a3fe8c23c797fa90d832f0332dfc72532e298dce54e\",\"03474b76f33036673e1df73800b06d2df4b3617768c2b6a4f8a7f7d17c2b08cec3\"]],\"receiving\":[[\"0288d4cc7e83b7028b8d2197c4efb490cb3dd248ee8683c715d9c59eb1884b2696\",\"02c8ffee4ef168237f4a303dfe4957e328a8163c827cbe8ad07dcc24304b343869\"],[\"022770e608e45981a31bad39a747a827ff4ce1eb28348fbe29ab776bdbf39346b4\",\"03ebd247971aced7e2f49c495658ac5c32f764ebc4df5d033505e665f8d3f87b56\"],[\"0256ede358326a99878d9de6c2c6a156548c266195fecea7906ddbb170da740f8d\",\"02a500e7438d672c374713a9179fef03cbf075dd4c854566d6d9f4d899c01a4cf4\"],[\"03fe2f59f10f6703bd3a43d0ae665ab72fb8b73b14f3a389b92e735e825fffdbe9\",\"0255dd91624ba62481e432b9575729757b046501b8310b1dee915df6c4472f7979\"],[\"0262c7c02f83196f6e3b9dd29e1bcad4834891b69ece12f628eea4379af6e701f8\",\"0319ce2894fdf42bc87d45167a64b24ee2acdb5d45b6e4aadce4154a1479c8c58a\"],[\"03bfb9ca9edab6650a908ffdcc0514f784aaccac466ba26c15340bc89a158d0b4c\",\"03bcce80eed7b494f793b38b55cc25ae62e462ec7bf4d8ff6e4d583e8d04a4ac6d\"],[\"0301dc9a41a44189e40c786048a0b6c13cc8865f3674fdf8e6cb2ab041eb71c0c7\",\"020ded564880e7298068cf1498efcfb0f2306c6003e3de09f89030477ff7d02e18\"],[\"03baffd970ecba170c31f48a95694a1063d14c834ccf2fdce0df46c3b81ab8edfb\",\"0243ec650fc7c6642f7fb3b98e1df62f8b28b2e8722e79ccb271badba3545e8fc2\"],[\"024be204a4bd321a727fb4a427189ae2f761f2a2c9898e9c37072e8a01026736d4\",\"0239dc233c3e9e7c32287fdd7932c248650a36d8ab033875d272281297fadf292a\"],[\"02197190b214c0215511d17e54e3e82cbe09f08e5ba2fb47aeafe01d8a88a8cb25\",\"034a13cf01e26e9aa574f9ba37e75f6df260958154b0f6425e0242eacd5a3979c5\"],[\"0226660fce4351019be974959b6b7dcf18d5aa280c6315af362ab60374b5283746\",\"0304e49d2337a529ed8a647eceb555cd82e7e2546073568e30254530a61c174100\"],[\"0324bb7d892dbe30930eb8de4b021f6d5d7e7da0c4ac9e3b95e1a2c684258d5d6c\",\"02487aa272f0d3a86358064e080daf209ee501654e083f0917ad2aff3bbeb43424\"],[\"03678b52056416da4baa8d51dca8eea534e38bd1d9328c8d01d5774c7107a0f9c1\",\"0331deff043d709fc8171e08625a9adffba1bb614417b589a206c3a80eff86eddd\"],[\"023a94d91c08c8c574199bc16e12789630c97cb990aeb5a54d938ff3c86786aabf\",\"02d139837e34858f733e7e1b7d61b51d2730c57c274ed644ab80aff6e9e2fdef73\"],[\"032f92dc11020035cd16995cfdc4bc6bef92bc4a06eb70c43474e6f7a782c9c0e1\",\"0307d2c32713f010a0d0186e47670c6e46d7a7e623026f9ed99eb27cdae2ae4b49\"],[\"02f66a91a024628d6f6969af2ed9ded087a88e9be86e4b3e5830868643244ec1ae\",\"02f2a83ebb1fbbd04e59a93284e35320c74347176c0592512411a15efa7bf5fa44\"],[\"03585bae6f04f2d3f927d79321b819cccf2bcd1d28d616aac9407c6c13d590dfbd\",\"021f48f02b485b9b3223fca4fbc4dd823a8151053b8640b3766c37dfa99ba78006\"],[\"02b28e2d6f1ac3fde4b34c938e83c0ef0d85fd540d8c33b33a109f4ebbc4a36a4d\",\"030a25a960e28e751a95d3c0167fad496f9ec4bc307637c69b3bd6682930532736\"],[\"03782c0dee8d279c547d26853e31d90bc7d098e16015c2cc334f2cc2a2964f2118\",\"021fe4d6392dba40f1aa35fa9ec3ebfde710423f036482f6a5b3c47d0e149dfe47\"],[\"0379b464b4f9cced0c71ee66c4fca1e61190bac9a6294242aabd4108f6a986a029\",\"030a5802c5997ebae590147cb5eeba1690455c5d2a87306345586e808167072b50\"]],\"xpub\":\"xpub661MyMwAqRbcErzzVC45mcZaZM7gpxh4iwfsQVuyTma3qpWuRi9ZRdL8ACqu25LP2jssmKmpEbnGohH9XnoZ1etW3TKaiy5dWnUuiN6LvD9\",\"xpub2\":\"xpub661MyMwAqRbcH4DqLo2tRYzSnnqjXk21aqNz3oAuYkr66YxucWgc2X8oLdS2KLPSKqrfZwStQYEpUp5jVxQfTBmEwtw3JaPRf6mq6JLD3Qr\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K2NvXPAX5QUcr1KHCRVyDMikGc7WMuS34y2BktAqJsq1eJvk7JWroKM8PdGa2FHWiTpAvH9nj6BkQos5XhJU5mfS12tdtBYy\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcErzzVC45mcZaZM7gpxh4iwfsQVuyTma3qpWuRi9ZRdL8ACqu25LP2jssmKmpEbnGohH9XnoZ1etW3TKaiy5dWnUuiN6LvD9\",\"x2/\":\"xpub661MyMwAqRbcH4DqLo2tRYzSnnqjXk21aqNz3oAuYkr66YxucWgc2X8oLdS2KLPSKqrfZwStQYEpUp5jVxQfTBmEwtw3JaPRf6mq6JLD3Qr\"},\"pruned_txo\":{},\"seed\":\"snack oxygen clock very envelope staff table bus sense fiscal cereal pilot abuse\",\"seed_version\":11,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_2_0_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"038f4bae4a901fe5f2a30a06a09681fff6678e8efda4e881f71dcdc0fdb36dd1b8\",\"032c628bec66fe98c3921b4fea6f18d241e6b23f4baf9e56c78b7a5262cd4cc412\",\"0232b68a11cde50a49fb3155fe2c9e9cf7aa9f4bcb0f51c3963b13c997e40de40d\"],\"receiving\":[\"0237246e68c6916c43c7c5aca1031df0c442439b80ceda07eaf72645a0597ed6aa\",\"03f35bee973012909d839c9999137b7f2f3296c02791764da3f55561425bb1d53c\",\"02fdbe9f95e2279045e6ef5f04172c6fe9476ba09d70aa0a8483347bfc10dee65e\",\"026bc52dc91445594bb639c7a996d682ac74a4564381874b9d36cc5feea103d7a4\",\"0319182796c6377447234eeee9fe62ce6b25b83a9c46965d9a02c579a23f9fa57a\",\"02e23d202a45515ce509c8b9548a251de3ad8e64c92b24bb74b354c8d4d0dc85af\",\"0307d7ccb51aa6860606bcbe008acc1aae5b53d19d0752a20a327b6ec164399b52\",\"038a2362fde711e1a4b9c5f8fe1090a0a38aec3643c0c3d69b00660b213dc4bfb8\",\"0396255ef7b75e5d8ffc18d01b9012a98141ee5458a68cde8b25c492c569a22ab8\",\"02c7edf03d215b7d3478fb26e9375d541440f4a8b5c562c0eb98fab6215dbea731\",\"024286902b95da3daf6ffb571d5465537dae5b4e00139e6465e440d6a26892158e\",\"03aa0d3fa1fe190a24e14d6aabd9c163c7fe70707b00f7e0f9fa6b4d3a4e441149\",\"03995d433093a2ae9dc305fe8664f6ab9143b2f7eaf6f31bc5fefdacb183699808\",\"033c5da7c4c7a3479ddb569fecbcbb8725867370746c04ff5d2a84d1706607bbab\",\"036a097331c285c83c4dab7d454170b60a94d8d9daa152b0af6af81dbd7f0cc440\",\"033ed002ddf99c1e21cb8468d0f5512d71466ac5ba4003b33d71a181e3a696e3c5\",\"02a6a0f30d1a341063a57a0549a3d16d9487b1d4e0d4bffadabdc62d1ad1a43f8f\",\"02dcae71fc2e31013cf12ad78f9e16672eeb7c75e536f4f7d36adb54f9682884eb\",\"028ef32bc57b95697dacdb29b724e3d0fa860ffdc33c295962b680d31b23232090\",\"0314afd1ac2a4bf324d6e73f466a60f511d59088843f93c895507e7af1ccdb5a3b\"],\"xpub\":\"xpub661MyMwAqRbcEuc5dCRqgPpGX2bKStk4g2cbZ96SSmKsQmLUrhaQEtrfnBMsXSUSyKWuCtjLiZ8zXrxcNeC2LR8gnZPrQJdmUEeofS2yuux\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K2RXcXAtqKFsXxzkq3S2DJogzkkgptRntXy1LKAG9h6YBvw8JjSUogF1UNneyYgS5uYshMBemqr41XsC7bTr8Fjx1uAyLbPC\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcEuc5dCRqgPpGX2bKStk4g2cbZ96SSmKsQmLUrhaQEtrfnBMsXSUSyKWuCtjLiZ8zXrxcNeC2LR8gnZPrQJdmUEeofS2yuux\"},\"pruned_txo\":{},\"seed\":\"agree tongue gas total hollow clip wasp slender dolphin rebel ozone omit achieve\",\"seed_version\":11,\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"standard\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_2_0_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"stored_height\":489714,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_2_0_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_2_0_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"pruned_txo\":{},\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_2_0_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"labels\":{},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"pruned_txo\":{},\"stored_height\":490006,\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"txi\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{}},\"txo\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[0,5000,false]]}},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_2_0_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"037ba2d9d7446d54f1b46c902427e58a4b63915745de40f31db52e95e2eb8c559c\",\"03aab9d4cb98fec92e1a9fc93b93f439b30cdb47cb3fae113779d0d26e85ceca7b\"],[\"036c6cb5ed99f4d3c8d2dd594c0a791e266a443d57a51c3c7320e0e90cf040dad0\",\"03f777561f36c795911e1e42b3b4babe473bcce32463eb9340b48d86fded8a226a\"],[\"03de4acea515b1b3b6a2b574d08539ced475f86fdf00b43bff16ec43f6f8efc8b7\",\"036ebfdd8ba75c94e0cb1819ecba464d04a77bab11c8fc2b7e90dd952092c01f0e\"]],\"receiving\":[[\"03e768d9de027e4edaf0685abb240dde9af1188f5b5d2aa08773b0083972bdec74\",\"0280eccb8edec0e6de521abba3831f51900e9d0655c59cddf054b72a70b520ddae\"],[\"02f9c0b7e8fe426a45540027abca63c27109db47b5c86886b99db63450444bb460\",\"03cb5cdcc26b0aa326bc895fcc38b63416880cdc404efbeab3ff14f849e4f4bd63\"],[\"024d6267b9348a64f057b8e094649de36e45da586ef8ca5ecb7137f6294f6fd9e3\",\"034c14b014eb28abfeaa0676b195bde158ab9b4c3806428e587a8a3c3c0f2d38bb\"],[\"02bc3d5456aa836e9a155296be6a464dfa45eb2164dd0691c53c8a7a05b2cb7c42\",\"03a374129009d7e407a5f185f74100554937c118faf3bbe4fe1cac31547f46effa\"],[\"024808c2d17387cd6d466d13b278f76d4d04a7d31734f0708a8baf20ae8c363f9a\",\"02e18dfc7f5ea9e8b6afe0853a9aba55861208b32f22c81aa4be0e6aee7951963d\"],[\"0331bef7adca60ae484a12cc3c4b788d4296e0b52500731bf5dff1b935973d4768\",\"025774c45aeac2ae87b7a67e79517ffb8264bdf1b56905a76e7e7579f875cbed55\"],[\"020566e7351b4bfe6c0d7bda3af24267245a856af653dd00c482555f305b71a8e3\",\"036545f66ad2fe95eeb0ec1feb501d552773e0910ec6056d6b827bc0bb970a1ecc\"],[\"038dc34e68a49d2205f4934b739e510dca95961d0f8ab6f6cd9279d68048cfd93b\",\"03810c50d1e2ff0e39179788e8506784bc214768884f6f71dc4323f6c29e25c888\"],[\"035059ff052ab044fd807905067ec79b19177edcf1b1b969051dc0e6957b1e1eab\",\"03d790376a0144860017bea5b5f2f0a9f184a55623e9a1e8f3670bf6aba273f4fb\"],[\"02bb730d880b90e421d9ac97313b3c0eec6b12a8c778388d52a188af7dc026db43\",\"030ae3ae865b805c3c11668b46ec4f324d50f6b5fbc2bb3a9ae0ddc4aea0d1487a\"],[\"0306eeb93a37b7dcbb5c20146cfd3036e9a16e5b35ecfe77261a6e257ee0a7b178\",\"03fb49f5f1d843ca6d62cee86fd4f79b6cc861f692e54576a9c937fdff13714be9\"],[\"03f4c358e03bd234055c1873e77f451bea6b54167d36c005abeb704550fbe7bee1\",\"03fc36f11d726fd4321f99177a0fff9b924ec6905d581a16436417d2ea884d3c80\"],[\"024d68322a93f2924d6a0290ebe7481e29215f1c182bd8fdeb514ade8563321c87\",\"02aa5502de7b402e064dfebc28cb09316a0f90eec333104c981f571b8bc69279e2\"],[\"03cbda5b33a72be05b0e50ef7a9872e28d82d5a883e78a73703f53e40a5184f7a5\",\"02ebf10a631436aa0fdef9c61e1f7d645aa149c67d3cb8d94d673eb3a994c36f86\"],[\"0285891a0f1212efff208baf289fd6316f08615bee06c0b9385cc0baad60ebc08a\",\"0356a6c4291f26a5b0c798f3d0b9837d065a50c9af7708f928c540017f150c40b6\"],[\"02403988346d00e9b949a230647edbe5c03ce36b06c4c64da774a13aca0f49ce92\",\"02717944f0bb32067fb0f858f7a7b422984c33d42fd5de9a055d00c33b72731426\"],[\"02161a510f42bcc7cdd24e7541a0bdbcac08b1c63b491df1974c6d5cd977d57750\",\"03006d73c0ab9fdd8867690d9282031995cfd094b5bdc3ff66f3832c5b8a9ca7f9\"],[\"03d80ea710e1af299f1079dd528d6cdc5797faa310bafa90ca7c45ea44d5ba64f3\",\"02b29e1170d6bec16ace70536565f1dff1480cba2a7545cfec7b522568a6ab5c38\"],[\"02c3f6e8dea3cace7aab89d8258751827cb5791424c71fa82ae30192251ca11a28\",\"02a43d2d952e1f3fb58c56dadabb39cf5ed437c566f504a79f2ade243abd2c9139\"],[\"0308e96e38eb89ca5abaa6776a1968a1cbb33197ec91d40bb44bede61cb11a517f\",\"034d0545444e5a5410872a3384cedd3fb198a8211bb391107e8e2c0b0b67932b20\"]],\"xpub\":\"xpub661MyMwAqRbcFCKg479EAwb6KLrQNcFSQKNjQRJpRFSiFRnp87cpntXkDUEvRtFTEARirm9584ML8sLBkF3gDBcyYgknnxCCrBMwPDDMQwC\",\"xpub2\":\"xpub661MyMwAqRbcFaEDoCANCiY9dhXvA8GgXFSLXYADmxmatLidGTxnVL6vuoFAMg9ugX8MTKjZPiP9uUPXusUji11LnWWLCw8Lzgx7pM5sg1s\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K2iFCx5cDooeMmK1uy9Xb36T8c2uCruujNdTfaaJaF6DGNDcDKkX1U4V1XiEcvCqoNsQhMQUnp8ZvMgxDBDErtMACo2HtGgQ\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcFCKg479EAwb6KLrQNcFSQKNjQRJpRFSiFRnp87cpntXkDUEvRtFTEARirm9584ML8sLBkF3gDBcyYgknnxCCrBMwPDDMQwC\",\"x2/\":\"xpub661MyMwAqRbcFaEDoCANCiY9dhXvA8GgXFSLXYADmxmatLidGTxnVL6vuoFAMg9ugX8MTKjZPiP9uUPXusUji11LnWWLCw8Lzgx7pM5sg1s\"},\"pruned_txo\":{},\"seed\":\"such duck column calm verb sock used message army suffer humble olive abstract\",\"seed_version\":11,\"stored_height\":490033,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_3_2_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"03b37d18c0c52da686e8fd3cc5d242e62036ac2b38f101439227f9e15b46f88c42\",\"026f946e309e64dcb4e62b00a12aee9ee14d26989880e690d8c307f45385958875\",\"03c75552e48d1d44f966fb9cfe483b9479cc882edcf81e2faf92fba27c7bbecbc1\",\"020965e9f1468ebda183fea500856c7e2afcc0ccdc3da9ccafc7548658d35d1fb3\",\"03da778470ee52e0e22b34505a7cc4a154e67de67175e609a6466db4833a4623ed\",\"0243f6bbb6fea8e0da750645b18973bc4bd107c224d136f26c7219aab6359c2705\"],\"receiving\":[\"0376bf85c1bf8960947fe575adc0a3f3ba08f6172336a1099793efd0483b19e089\",\"03f0fe0412a3710a5a8a1c2e01fe6065b7a902f1ccbf38cd7669806423860ad111\",\"03eacb81482ba01a741b5ee8d52bb6e48647107ef9a638ca9a7b09f6d98964a456\",\"03c8b598f6153a87fc37f693a148a7c1d32df30597404e6a162b3b5198d0f2ba33\",\"03fefef3ee4f918e9cd3e56501018bcededc48090b33c15bf1a4c3155c8059610a\",\"0390562881078a8b0d54d773d6134091e2da43c8a97f4f3088a92ca64d21fcf549\",\"0366a0977bb35903390e6b86bbb6faa818e603954042e98fe954a4b8d81d815311\",\"025d176af6047d959cfdd9842f35d31837034dd4269324dc771c698d28ad9ae3d6\",\"02667adce009891ee872612f31cd23c5e94604567140b81d0eae847f5539c906d6\",\"03de40832017ba85e8131c2af31079ab25a72646d28c8d2b6a39c98c4d1253ae2f\",\"02854c17fdef156b1681f494dfc7a10c6a8033d0c577b287947b72ecada6e6386b\",\"0283ff8f775ba77038f787b9bf667f538f186f861b003833600065b4ad8fd84362\",\"03b0a4e9a6ffecd955bd0e2b169113b544a7cba1688dca6fce204552403dc28391\",\"02445465cf40603506dbe7fa853bc1aae0d79ca90e57b6a7af6ffc1341c4ca8e2d\",\"0220ea678e2541f809da75552c07f9e64863a254029446d6270e433a4434be2bd7\",\"02640e87aab83bd84fe964eac72657b34d5ad924026f8d2222557c56580607808e\",\"020fa9a0c3b335c6cdc6588b14c596dfae242547dd68e5c6bce6a9347152ff4021\",\"03f7f052076dc35483c91033edef2cc93b54fb054fe3b36546800fa1a76b1d321a\",\"030fd12243e1ffe1fc6ec3cdb7e020a467d3146d55d52af915552f2481a91657cd\",\"02dd1a2becbc344a297b104e4bb41f7de4f5fcff1f3244e4bb124fbb6a70b5eb18\"],\"xpub\":\"xpub661MyMwAqRbcEnd8FGgkz7V8iJZ2FvDcg669i7NSS7h7nmq5k5WeHohNqosRSjx9CKiRxMgTidPWA5SJYsjrXhr1azR3boubNp24gZHUeY4\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K2JYf9F9kcyYQAGiXrTVmJsAYuixpsnA8uyVwCYCPk1NtzYuNmeLRLKcMYb3UoPgTocYsHsAje3mSjX4jp3Ci17VhuESjsBU\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcEnd8FGgkz7V8iJZ2FvDcg669i7NSS7h7nmq5k5WeHohNqosRSjx9CKiRxMgTidPWA5SJYsjrXhr1azR3boubNp24gZHUeY4\"},\"pruned_txo\":{},\"seed\":\"scheme grape nephew hen song purity pizza syrup must dentist bright grit accuse\",\"seed_version\":11,\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"standard\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_3_2_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"stored_height\":489715,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_3_2_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_3_2_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\",\"029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156\",\"034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e\",\"036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"pruned_txo\":{},\"stored_height\":0,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_3_2_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\",\"021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8\",\"031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4\",\"033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\",\"03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff\",\"03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7\",\"022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"labels\":{},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"pruned_txo\":{},\"stored_height\":490008,\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"txi\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{}},\"txo\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[0,5000,false]]}},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_3_2_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"03083942fe75c1345833faa4d31a635e088ca173047ddd6ef5b7f1395892ef339d\",\"03c02f486ed1f0e6d1aefbdea293c8cb44b34a3c719849c45e52ef397e6540bbda\"],[\"0326d9adb5488c6aba8238e26c6185f4d2f1b072673e33fb6b495d62dc800ff988\",\"023634ebe9d7448af227be5c85e030656b353df81c7cf9d23bc2c7403b9af7509b\"],[\"0223728d8dd019e2bd2156754c2136049a3d2a39bf2cb65965945f4c598fdb6db6\",\"037b6d4df2dde500789f79aa2549e8a6cb421035cda485581f7851175e0c95d00e\"],[\"03c47ade02def712ebbf142028d304971bec99ca53be8e668e9cf15ff0ef186e19\",\"02e212ad25880f2c9be7dfd1966e4b6ae8b3ea40e09d482378b942ca2e716397b0\"],[\"03dab42b0eaee6b0e0d982fbf03364b378f39a1b3a80e980460ae96930a10bff6c\",\"02baf8778e83fbad7148f3860ce059b3d27002c323eab5957693fb8e529f2d757f\"],[\"02fc3019e886b0ce171242ddedb5f8dcde87d80ad9f707edb8e6db66a4389bea49\",\"0241b4e9394698af006814acf09bf301f79d6feb2e1831a7bc3e8097311b1a96dd\"]],\"receiving\":[[\"023e2bf49bc40aeed95cb1697d8542354df8572a8f93f5abe1bcec917778cc9fc6\",\"03cf4e80c4bf3779e402b85f268ada2384932651cc41e324e51fc69d6af55ae593\"],[\"02d9ba257aa3aba2517bb889d1d5a2e435d10c9352b2330600decab8c8082db242\",\"03de9e91769733f6943483167602dd3d439e34b7078186066af8e90ec58076c2a7\"],[\"02ccdd5b486cefa658af0c49d85aefa3ab62f808335ffcd4b8d4197a3c50ab073c\",\"03e80dbbd0fb93d01d6446d0af1c18c16d26bdbb2538d8bf7f2f68ce95ba857667\"],[\"031605867287fe3b1fee55e07b2f513792374bb5baf30f316970c5bc095651a789\",\"02c0802b96cee67d6acec5266eb3b491c303cea009d57a6bb7aee83cc602206ad5\"],[\"037d07d30dec97da4ea09d568f96f0eb6cd86d02781a7adff16c1647e1bcd23260\",\"03d856a53bc90be84810ce94c8aac0791c9a63379fd61790c11dae926647aa4eec\"],[\"028887f2d54ffefc98e5a605c83bedba79367c2a4fe11b98ec6582896ffad79216\",\"0259dab6dafe52306fe6e3686f27a36e0650c99789bb19cbcd0907db00957030a9\"],[\"039d83064dd37681eaf7babe333b210685ba9fe63627e2f2d525c1fb9c4d84d772\",\"03381011299678d6b72ff82d6a47ed414b9e35fcf97fc391b3ff1607fb0bf18617\"],[\"03ace6ceb95c93a446ae9ff5211385433c9bbf5785d52b4899e80623586f354004\",\"0369de6b20b87219b3a56ea8007c33091f090698301b89dd6132cf6ef24b7889a0\"],[\"031ec2b1d53da6a162138fb8f4a1ec27d62c45c13dddecebbd55ad8a5d05397382\",\"02417a3320e15c2a5f0345ac927a10d7218883170a9e64837e629d14f8f3de7c78\"],[\"02b85c8b2f33b6a8a882c383368be8e0a91491ea57595b6a690f01041be5bef4fb\",\"0383ad57c7899284e9497e9dccb1de5bf8559b87157f13fee5677dcf2fbeb7b782\"],[\"03eaa9e3ea81b2fa6e636373d860c0014e67ac6363c9284e465384986c2ec77ee2\",\"03b1bd0d6355d99e8cab6d177f10f05eb8ddd3e762871f176d78a79f14ae037826\"],[\"03ecd1b458e7c2b71a6542f8e64c750358c1421542ffe7630cc3ecc6866d379dfe\",\"02d5c5432ca5e4243430f73a69c180c23bda8c7c269d7b824a4463e3ac58850984\"],[\"028098ae6e772460047cdd6694230dcfc44da8ceabcae0624225f2452be7ae26c4\",\"02add86858446c8a59ed3132264a8141292cd4ece6653bf3605895cceb00ba30b9\"],[\"02f580882255cda6fae954294164b26f2c4b6b2744c0930daaa7a9953275f2f410\",\"02c09c5e369910d84057637157bdf1fb721387bb2867c3c2adb2d91711498bbe5e\"],[\"025e628f78c95135669ab8b9178f4396b0b513cbeae9ca631ba5e5e8321a4a05bc\",\"03476f35b4defcc67334a0ff2ce700fb55df39b0f7f4ff993907e21091f6a29a31\"],[\"026fa6f3214dce2ad2325dae3cd8d6728ce62af1903e308797ff071129fe111eca\",\"03d07eb26749caceca56ffe77d9837aaf2f657c028bd3575724b7e2f1a8b3261a5\"],[\"03894311c920ef03295c3f1c8851f5dc9c77e903943940820b084953a0a92efcc3\",\"0368b0b3774f9de81b9f10e884d819ccf22b3c0ed507d12ce2a13efc36d06cdc17\"],[\"024f8a61c23aa4a13a3a9eb9519ed3ec734f54c5e71d55f1805e873c31a125c467\",\"039e9c6708767bd563fcdca049c4d8a1acab4a051d4f804ae31b5e9de07942570f\"],[\"038f9b8f4b9fe6af5ced879a16bb6d56d81831f11987d23b32716ca4331f6cbabf\",\"035453374f020646f6eda9528543ec0363923a3b7bbb40bc9db34740245d0132e7\"],[\"02e30cd68ae23b3b3239d4e98745660b08d7ce30f2f6296647af977268a23b6c86\",\"02ee5e33d164f0ad6b63f0c412734c1960507286ad675a343df9f0479d21a86ecc\"]],\"xpub\":\"xpub661MyMwAqRbcGAPwDuNBFdPguAcMFDrUFznD8RsCFkjQqtMPE66H5CDpecMJ9giZ1GVuZUpxhX4nFh1R3fzzr4hjDoxDSHymXVXQa7H1TjG\",\"xpub2\":\"xpub661MyMwAqRbcFMKuZtmYryCNiNvHAki74TizX3b6dxaREtjLMoqnLJbd1zQKjWwKEThmB4VRtwePAWHNk9G5nHvAEvMHDYemERPQ7bMjQE3\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K3gKU7sqAtVSxM8mrqm8ctmrcL3TahRCRy62EgYn2XPuLoJAGbBGvL4ArbPoAay5jo7L1UbBv15SsmrSKdTQSgDE351WSkm6\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcGAPwDuNBFdPguAcMFDrUFznD8RsCFkjQqtMPE66H5CDpecMJ9giZ1GVuZUpxhX4nFh1R3fzzr4hjDoxDSHymXVXQa7H1TjG\",\"x2/\":\"xpub661MyMwAqRbcFMKuZtmYryCNiNvHAki74TizX3b6dxaREtjLMoqnLJbd1zQKjWwKEThmB4VRtwePAWHNk9G5nHvAEvMHDYemERPQ7bMjQE3\"},\"pruned_txo\":{},\"seed\":\"brick huge enforce behave cabin cram okay friend sketch actor casual barrel abuse\",\"seed_version\":11,\"stored_height\":490033,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_4_3_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"02707eb483e51d859b52605756aee6773ea74c148d415709467f0b2a965cd78648\",\"0321cddfb60d7ac41fdf866b75e4ad0b85cc478a3a84dc2e8db17d9a2b9f61c3b5\",\"0368b237dea621f6e1d580a264580380da95126e46c7324b601c403339e25a6de9\",\"02334d75548225b421f556e39f50425da8b8a36960cce564db8001f7508fef49f6\",\"02990b264de812802743a378e7846338411c3afab895cff35fb24a430fa6b43733\",\"02bc3b39ca00a777e95d89f773428bad5051272b0df582f52eb8d6ebb5bb849383\"],\"receiving\":[\"0286c9d9b59daa3845b2d96ce13ac0312baebaf318251bac6d634bcac5ff815d9d\",\"0220b65829b3a030972be34559c4bb1fc91f8dfd7e1703ddb43da9aa28aa224864\",\"02fe34b26938c29faee00d8d704eae92b7c97d487825892290309073dc85ae5374\",\"03ea255ae2ba7169802543cf7af135783f4fca91924fd0285bdbe386d78a0ab87e\",\"027115aeea786e2745812f2ec2ae8fee3d038d96c9556b1324ac50c913b83a9e6a\",\"03627439bb701352e35d0cf8e00617d8e9bf329697e430b0a5d999370097e025b4\",\"034120249c6b15d051525156845aefaa83988adf9ed1dd18b796217dcf9824b617\",\"02dfeb0c89eee66026d7650ee618c2172551f97fdd9ed249e696c54734d26e39a3\",\"037e031bb4e51beb5c739ba6ab64aa696e85457ea63cc56698b7d9b731fd1e8e61\",\"0302ea6818525492adc5ed8cfd2966efd704915199559fe1c06d6651fd36533012\",\"0349394140560d685d455595f697d17b44e832ec453b5a2f02a3f5ed66205f3d30\",\"036815bf2437df00440b15cfa7123544648cf266247989e82540d6b1cae1589892\",\"02f98568e8f0f4b780f005e538a7452a60b2c06a5d2e3a23fa26d88459d118ef56\",\"02e36ccb8b05a2762a08f60541d1a5a136afd6a73119eea8c7c377cc8b07eb2e2f\",\"031566539feb6f0a212cca2604906b1c1f5cfc5bf5d5206e0c695e37ef3a141fd2\",\"025754e770bedeef6f4e932fa231b858b49d28183e1be6da23e597c67dd7785f19\",\"03a29961f5fb9c197cffe743081a761442a3cf9ded0be2fa07ab67023a74c08d28\",\"023184c1995a9f51af566c9c0b4da92d7fd4a5c59ff93c34a323e94671ddbe414a\",\"029efdb15d3aec708b3af2aee34a9157ff731bec94e4f19f634ab43d3101e47bd8\",\"03e16b13fe6bb9aa6dc4e331e19ab4d3d291a2670b97e6040e87a7c7309b243af9\"],\"xpub\":\"xpub661MyMwAqRbcF1KGEGXxFTupKQHTTUan1qZMTp4yUxiwF2uRRum7u1TCnaJRjaSBW4d42Fwfi6xfLvfRfgtDixekGDWK9CPWguR7YzXKKeV\"}},\"accounts_expanded\":{},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K2XEo8EzwtKy5mNSy41rvecdkfRfMvdBxNEaGtNSsMD8iwHsc91UxKtSrDHXex53NkMRRDwnm4PmqS7N35K8BR1KCD2qm5iE\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcF1KGEGXxFTupKQHTTUan1qZMTp4yUxiwF2uRRum7u1TCnaJRjaSBW4d42Fwfi6xfLvfRfgtDixekGDWK9CPWguR7YzXKKeV\"},\"seed\":\"smart fish version ocean category disagree hospital mystery survey chef kid latin about\",\"seed_version\":11,\"use_encryption\":false,\"wallet_type\":\"standard\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_4_3_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"stored_height\":477636,\"use_encryption\":false,\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_4_3_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"pruned_txo\":{},\"stored_height\":490038,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_4_3_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\",\"029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156\",\"034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e\",\"036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"pruned_txo\":{},\"stored_height\":485855,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_4_3_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\",\"021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8\",\"031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4\",\"033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\",\"03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff\",\"03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7\",\"022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]]},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"pruned_txo\":{},\"stored_height\":490009,\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"txi\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{}},\"txo\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[0,5000,false]]}},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\"}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_4_3_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"03467a8bae231aff83aa01999ee4d3834894969df7a3b0753e23ae7a3aae089f6b\",\"02180c539980494b4e59edbda5e5340be2f5fbf07e7c3898b0488950dda04f3476\"],[\"03d8e18a428837e707f35d8e2da106da2e291b8acbf40ca0e7bf1ac102cda1de11\",\"03fad368e3eb468a7fe721805c89f4405581854a58dcef7205a0ab9b903fd39c23\"],[\"0331c9414d3eee5bee3c2dcab911537376148752af83471bf3b623c184562815d9\",\"02dcd25d2752a6303f3a8366fae2d62a9ff46519d70da96380232fc9818ee7029e\"],[\"03bb18a304533086e85782870413688eabef6a444a620bf679f77095b9d06f5a16\",\"02f089ed84b0f7b6cb0547741a18517f2e67d7b5d4d4dd050490345831ce2aef9e\"],[\"02dc6ebde88fdfeb2bcd69fce5c5c76db6409652c347d766b91671e37d0747e423\",\"038086a75e36ac0d6e321b581464ea863ab0be9c77098b01d9bc8561391ed0c695\"],[\"02a0b30b12f0c4417a4bef03cb64aa55e4de52326cf9ebe0714613b7375d48a22e\",\"02c149adda912e8dc060e3bbe4020c96cff1a32e0c95098b2573e67b330e714df0\"]],\"m\":2,\"receiving\":[[\"0254281a737060e919b071cb58cc16a3865e36ea65d08a7a50ba2e10b80ff326d5\",\"0257421fa90b0f0bc75b67dd54ffa61dc421d583f307c58c48b719dd59078023e4\"],[\"03854ce9bbc7813d535099658bcc6c671a2c25a269fdb044ee0ed5deb95da0d7e0\",\"025379ca82313dde797e5aa3f222dddf0f7223cb271f79ecce2c8178bea3e33c62\"],[\"03ae6ad5ffc75d71adc2ab87e3adc63fa8696a8656e1135adb5ae88ddb6d39089f\",\"025ed8821f8b37aef69b1aabf89e4e405f09206c330c78e94206b21139ddafcc4f\"],[\"033ea4d8b88d36d14a52983ae30d486254af2dfa1c7f8e04bc9d8e34b3ffe4b32a\",\"02b441a3e47a338d89027755b81724219362b8d9b66142d32fcb91c9c7829d8c9f\"],[\"029195704b9bbc3014452bbf07baa7bf6277dfefd9721aea8438f2671ba57b898b\",\"022264503140f99b41c0269666ab6d16b2dad72865dbd2bf6153d45f5d11978e4d\"],[\"037e3caa2d151123821dff34fd8a76ac0d56fa97c41127e9b330a115bf12d76674\",\"02a4ae28e2011537de4cce0c47af4ac0484b38d408befcb731c3d752922fcd3c5b\"],[\"02226853ca32e72b4771ccc47c0aae27c65ed0d25c525c1f673b913b97dca46cc5\",\"027a9c855fc4e6b3f8495e77347a1e03c0298c6a86bd5a89800195bd445ae3e3bd\"],[\"02890f7eee0766d2dde92f3146cd461ae0fa9caf07e1f3559d023a20349bae5e44\",\"0380249f30829b3656c32064ddf657311159cecb36f9dbbf8e50e3d7279b70c57e\"],[\"02ab9613fd5a67a3fdf6b6241d757ce92b2640d9d436e968742cb7c4ec4bb3e6e9\",\"0204b29cc980b18dfb3a4f9ca6796c6be3e0aee2462719b4a787e31c8c5d79c8cf\"],[\"029103b50ecc0cc818c1c97e8acb8ce3e1d86f67e49f60c8496683f15e753c3eed\",\"0247abb2c5e4cde22eb59a203557c0bbe87e9c449e6c2973e693ac14d0d9cf3f28\"],[\"02817c935c971e6e318ba9e25402df26ca016a4e532459be5841c2d83a5aa8a967\",\"03331fe3a2e4aa3e2dc1d8d4afc5a88c57350806b905e593b5876c6b9cef71fd4d\"],[\"03023c6797af5c9c3d7db2fbeb9d7236601fe5438036200f2f59d9b997d29ec123\",\"023b1084f008cf2e9632967095958bb0bbd59e60a0537e6003d780c7ebccb2d4f5\"],[\"0245e0bdebe483fef984e4e023eb34641e65909cd566eb6bd6c0bce592296265a1\",\"0363bad4b477d551f46b19afcc10decf6a4c1200becb5b22c032c62e6d90b373b8\"],[\"0379ba2f8c5e8e5e3f358615d230348fe8d7855ef9c0e1cf97aac4ec09dfe690aa\",\"02ecda86ff40b286a3faadf9a5b361ab7a5beb50426296a8c0e3d222f404ae4380\"],[\"02e090227c22efa7f60f290408ce9f779e27b39d4acec216111cc3a8b9594ab451\",\"02144954ddabb55abcfe49ea703a4e909ab86db2f971a2e85fc006dffbdf85af52\"],[\"025dc4bd1c4809470b5a14cf741519ad7f5f2ccd331b42e0afd2ce182cdf25f82d\",\"03d292524190af850665c2255a785d66c59fea2b502d4037bb31fdde10ad9b043f\"],[\"027e7c549f613ae9ba1d806c8c8256f870e1c7912e3e91cbb326d61fb20ac3a096\",\"03fbbf15ee2b49878c022d0b30478b6a3acb61f24af6754b3f8bcb4d2e71968099\"],[\"02c188eaf5391e52fdcd66f8522df5ae996e20c524577ac9ffa7a9a9af54508f7c\",\"03fe28f1ea4a0f708fa2539988758efd5144a128cc12aed28285e4483382a6636a\"],[\"03bea51abacd82d971f1ef2af58dcbd1b46cdfa5a3a107af526edf40ca3097b78d\",\"02267d2c8d43034d03219bb5bc0af842fb08f028111fc363ec43ab3b631134228a\"],[\"03c3a0ecdbf8f0a162434b0db53b3b51ce02886cbc20c52e19a42b5f681dac6ffb\",\"02d1ede70e7b1520a6ccabd91488af24049f1f1cf2661c07d8d87aee31d5aec7c9\"]],\"xpubs\":[\"xpub661MyMwAqRbcFafkG2opdo3ou3zUEpFK3eKpWWYkdA5kfvootPkZzqvUV1rtLYRLdUxvXBZApzZpwyR2mXBd1hRtnc4LoaLTQWDFcPKnKiQ\",\"xpub661MyMwAqRbcFrxPbuWkHdMeaZMjb4jKpm51RHxQ3czEDmyK3Qa3Z43niVzVjFyhJs6SrdXgQg56DHMDcC94a7MCtn9Pwh2bafhHGJbLWeH\"]}},\"accounts_expanded\":{},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K3NsvVsyjvVQv2XXFBc1UTY9QcuYnVHTFLyeAVsFo1FjJsBk48XK16jZLqRs1B5Sa6SCqYdA2XFvB9riBca2GyGccYGKKP6t\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcFrxPbuWkHdMeaZMjb4jKpm51RHxQ3czEDmyK3Qa3Z43niVzVjFyhJs6SrdXgQg56DHMDcC94a7MCtn9Pwh2bafhHGJbLWeH\",\"x2/\":\"xpub661MyMwAqRbcFafkG2opdo3ou3zUEpFK3eKpWWYkdA5kfvootPkZzqvUV1rtLYRLdUxvXBZApzZpwyR2mXBd1hRtnc4LoaLTQWDFcPKnKiQ\"},\"pruned_txo\":{},\"seed\":\"angry work entry banana taste climb script fold level rate organ edge account\",\"seed_version\":11,\"stored_height\":490033,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\"}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_5_4_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"0253e61683b66ebf5a4916334adf1409ffe031016717868c9600d313e87538e745\",\"021762e47578385ecedc03c7055da1713971c82df242920e7079afaf153cc37570\",\"0303a8d6a35956c228aa95a17aab3dee0bca255e8b4f7e8155b23acef15cf4a974\",\"02e881bc60018f9a6c566e2eb081a670f48d89b4a6615466788a4e2ce20246d4c6\",\"02f0090e29817ef64c17f27bf6cdebc1222f7e11d7112073f45708e8d218340777\",\"035b9c53b85fd0c2b434682675ac862bfcc7c5bb6993aee8e542f01d96ff485d67\"],\"receiving\":[\"024fbc610bd51391794c40a7e04b0e4d4adeb6b0c0cc84ac0b3dad90544e428c47\",\"024a2832afb0a366b149b6a64b648f0df0d28c15caa77f7bbf62881111d6915fe9\",\"028cd24716179906bee99851a9062c6055ec298a3956b74631e30f5239a50cb328\",\"039761647d7584ba83386a27875fe3d7715043c2817f4baca91e7a0c81d164d73d\",\"02606fc2f0ce90edc495a617329b3c5c5cc46e36d36e6c66015b1615137278eabd\",\"02191cc2986e33554e7b155f9eddcc3904fdba43a5a3638499d3b7b5452692b740\",\"024b5bf755b2f65cab1f7e5505febc1db8b91781e5aac352902e79bc96ad7d9ad0\",\"0309816cb047402b84133f4f3c5e56c215e860204513278beef54a87254e44c14a\",\"03f53d34337c12ddb94950b1fee9e4a9cf06ad591db66194871d31a17ec7b59ac7\",\"0325ede4b08073d7f288741c2c577878919fd5d832a9e6e04c9eac5563ae13aa83\",\"02eca43081b04f68d6c8b81781acd59e5b8d2ba44dba195369afc40790fd9edef7\",\"029a8ca96c64d3a98345be1594208908f2be5e6af6bcc6ff3681f271e75fcf232e\",\"02fbe0804980750163a216cc91cfe86e907addf0e80797a8ea5067977eb4897c1b\",\"0344f32fc1ee8b2eb08f419325529f495d77a3b5ea683bbce7a44178705ab59302\",\"021dd62bdf18256bd5316ce3cbcca58785378058a41ba2d1c58f4cc76449b3c424\",\"035e61cdbdb4306e58a816a19ad92c7ca3a392b67ac6d7257646868ffe512068c5\",\"0326a4db82f21787d0246f8144abe6cda124383b7d93a6536f36c05af530ea262a\",\"02b352a27a8f9c57b8e5c89b357ba9d0b5cb18bf623509b34cd881fcf8b89a819a\",\"02a59188edef1ed29c158a0adb970588d2031cfe53e72e83d35b7e8dd0c0c77525\",\"02e8b9e42a54d072c8887542c405f6c99cfabf41bdde639944b44ba7408837afd1\"],\"xpub\":\"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG\"}},\"accounts_expanded\":{},\"addr_history\":{\"12LXoVHUnAXn6BVBpshjwd7sSTwp5nsd7W\":[],\"12iXPYBErR6ZMESB9Nv74S4pVxdGMNLiW2\":[],\"13jmb5Vc2qh29tPhg637BwCJN7hStGWYXE\":[],\"14dHBBbwFVC7niSCqrb5HCHRK5K8rrgaW6\":[],\"14xsHuYGs4gKpRK3deuYwhMBTAwUeu2dpB\":[],\"15MpWMUasNVPTpzC5hK2AuVFwQ3AHd8fkv\":[],\"17nmvao3F84ebPrcv1LUxPUSS94U9EvCUt\":[],\"17yotEc8oUgJVQUnkjZSQjcqqZEbFFnXx8\":[],\"1A3c1rCCS2MYYobffyUHwPqkqE5ZpvG8Um\":[],\"1AtCzmcth79q6HgeyDnM3NLfr29hBHcfcg\":[],\"1AufJhUsMbqwbLK9JzUGQ9tTwphCQiVCwD\":[],\"1B77DkhJ8qHcwPQC2c1HyuNcYu5TzxxaJ7\":[],\"1D4bgjc4MDtEPWNTVfqG5bAodVu3D1Gjft\":[],\"1DefMPXdeCSQC5ieu8kR7hNGAXykNzWXpm\":[],\"1E673RESY1SvTWwUr5hQ1E7dGiRiSgkYFP\":[],\"1Ex6hnmpgp3FQrpR5aYvp9zpXemFiH7vky\":[],\"1FH2iAc5YgJKj1KcpJ1djuW3wJ2GbQezAv\":[],\"1GpjShJMGrLQGP6nZFDEswU7qUUgJbNRKi\":[],\"1H4BtV4Grfq2azQgHSNziN7MViQMDR9wxd\":[],\"1HnWq29dPuDRA7gx9HQLySGdwGWiNx4UP1\":[],\"1LMuebyhm8vnuw5qX3tqU2BhbacegeaFuE\":[],\"1LTJK8ffwJzRaNR5dDEKqJt6T8b4oVbaZx\":[],\"1LtXYvRr4j1WpLLA398nbmKhzhqq4abKi8\":[],\"1NfsUmibBxnuA3ir8GJvPUtY5czuiCfuYK\":[],\"1Q3cZjzADnnx5pcc1NN2ekJjLijNjXMXfr\":[],\"1okpBWorqo5WsBf5KmocsfhBCEDhNstW2\":[]},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K4D3WqM7zpQrWeqJHJRJhRhpkk5tr2fKBdoTTPDYUL88T12Ad9RHwViugcMbngkMDY626vD5syaFDoUB2cpLeraBaHvZHWFn\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcGh7ywNf1BYoFCs8mht2YnvkMYUJTazrAWbnbvkrisvSvrKGjRTDtw324xzprbDgphsmPv2pB6K5Sux3YNHC8pnJANCBY6vG\"},\"pruned_txo\":{},\"seed\":\"tent alien genius panic stage below spoon swap merge hammer gorilla squeeze ability\",\"seed_version\":11,\"stored_height\":489715,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"standard\",\"winpos-qt\":[100,100,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_5_4_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"addr_history\":{},\"pruned_txo\":{},\"stored_height\":489716,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"imported\",\"winpos-qt\":[595,261,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_5_4_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"addr_history\":{},\"pruned_txo\":{},\"stored_height\":490038,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\",\"winpos-qt\":[406,393,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_5_4_trezor_singleacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"033608f89d381bcb9964df9da428d706d3eb30c14433af8de21bee2601e7392a80\",\"0295c3905730d987ae9a9c09ad85c9c22c28aa414448f9d3450d8afb3da0d78890\",\"038cf10bcf2bd3384f05974295fc83fc4e9cb48c0105995ad86d3ea237edb7e1d1\",\"029b76e98f87c537165f016cf6840fe40c172ca0dba10278fb10e49a2b718cd156\",\"034f08127c3651e5c5a65803e22dcbb1be10a90a79b699173ed0de82e0ceae862e\",\"036013206a41aa6f782955b5a3b0e67f9a508ecd451796a2aa4ee7a02edef9fb7e\"],\"receiving\":[\"020be78fa1a35e44fb1ee3141b40bd8d68330f12f98fdef5ba249b4d8c52a6a1ae\",\"03f23e9a3b5337f322f720f533653349f6e97228d1c4a6feca36d4d1554aa19f74\",\"03d3e7cfde0117561856e6e43d87852480c512910bfd1988c2ff1e6f6d795f7046\",\"02ec56fc0bfe6a1466a783737919edbe83c8907af29a5ae672919ffcb1bb96303f\",\"031b1d151f6584f9926614a7c335ee61606ff7a9769ca6e175ad99f9c7b5e9fb4d\",\"03d782be0ace089e02529029b08ca9107b0e58302306de30bd9f9a3a1ed40c3717\",\"0325784a4290eeeea1f99a928cf6c75c33417659dbd50a3a2850136dc3138ba631\",\"035b7c1176926a54cdeb0342df5ecc7bb3fe1820fce99491fb50c091e3093f200f\",\"02e0a2d615bff26a57754afa0e8ac8b692a79b399f6d04647398f377dcac4116be\",\"026c7cee5bce1ae9e2fa930001ece81c35442a461fc9ef1266ac3d41b9f13e3bd5\",\"0217b1d5066708e0cdaee99087c407db684131e34578adc7800dc66f329576c457\",\"03ec0ed891b0ead00f1eaca7a4736d6816e348731d995bd4e77acbc8c582f68429\",\"028cb4c682dde9692de47f71f3b16755cc440d722b84eed68db2b3d80bce83d50a\",\"03d5d770a58d32b5d59b12861bbda37560fe7b789181b3349abf56223ea61b39c4\",\"0250b6aee8338ac0497f2106b0ed014f5a2419c7bf429eb2b17a70bec77e6ff482\",\"02565da9be6fc66a1e354638dcd8a4244e8733f38599c91c4f1ab0fb8d5d94fd2f\",\"02e6c88509ff676b686afc2326370684bbc6edc0b31e09f312df4f7a17fe379e31\",\"02224fef0921e61adcb2cd14ef45dbe4b859f1fcdc62eba26c6a7ce386c0a8f4b1\",\"034c63da9c2a20132d9fd1088028de18f7ccd72458f9eb07a72452bd9994d28b1f\",\"032bfe2fc88a55e19ba2338155b79e67b7d061d5fd1844bc8edc1808d998f8ba2c\"],\"xpub\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\"}},\"accounts_expanded\":{},\"addr_history\":{},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6D77dkWgEcSNBq7xDA1RUysGvD64QNy2TykC9UuRK6fEzqy3512HR2p2spstKCybkhDqkNStPWZKcnhwdD6kDYWJxsTQJhg9RCwifzcfJN9\",\"x/1'\":\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\"},\"next_account2\":[\"1\",\"xpub6D77dkWgEcSNFtXV2CQgsbfG33VyGMaUtUdpbdfMMHsS4WDzLtRapchQWcVBMFFjdRYjhkvQwGnJeKWPP3C2e1DevATAEUzL258Lhfkd7KG\",\"03571f041921078b153a496638d703dfd1cee75e73c42653bbe0650ab6168d6a5b\",\"18i2zqeCh6Gjto81KvVaeSM8YBUAkmgjRG\"],\"pruned_txo\":{},\"stored_height\":490046,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"trezor\",\"winpos-qt\":[522,328,840,400]}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_5_4_trezor_multiacc(self):\n        wallet_str = '''{\"accounts\":{\"0\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\",\"021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8\",\"031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4\",\"033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"],\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"1\":{\"change\":[\"03b0df486b4e1baa03ad565622820d692089b059c8f9fefa3567c3fa26d0cbaa34\",\"0294c76c062c865873dccab84d51682f880e0197b64789c61bff85e1be2506925e\",\"036f900d0c6bafbbcac0fbc95bed44954007faa182655cf69dc84d50c22e6edce8\",\"03d1be74f1360ecede61ad1a294b2e53d64d44def67848e407ec835f6639d825ff\",\"03a6a526cfadd510a47da95b074be250f5bb659b857b8432a6d317e978994c30b7\",\"022216da9e351ae57174f93a972b0b09d788f5b240b5d29985174fbd2119a981a9\"],\"receiving\":[\"02106878f6aefd9a81e1ca4a5f30ea0e1851aa36404fb62d19bd2325e180112b58\",\"039e95f369e8d65aa7a7bf6a5d7d3259b827c1549c77c9b502b75a18f7708a9aa9\",\"0273197861097be131542f8b7e03bc912934da51bc957d425be5bc7c1b69fb44ec\",\"02b4c829b6a20815c5e1eef7ffd5d55c99505a7afeac5135ec2c97cfaae3483604\",\"0312b1285272f1005c5834de2eec830ce9f9163c842d728c3921ae790716d8503f\",\"0354059948c709c777a49a37e150271a3377f7aaee17798253d5240e4119f2a1c6\",\"03800d87cc3878912d22a42a79db7ddbff3efec727d29ae1c0165730e5314483cd\",\"03cafa35ad9adb41cff39e3bc2e0592d88c8b91981e73f068397e6c863c42c7b00\",\"028668f734a4927e03621e319ab385919e891d248c86aea07ab922492d3d414ad3\",\"02e42d46823893978ae7be9e032be21ce3e613cecb5ffe687b534795f90dc8ef85\",\"03b86914af797e7b68940bc4ee2dec134036781a8e23ffaf4189ca7637e0afe898\",\"021221ae9be51a9747aa7ebc2213a42a2364ce790ee86255277dc5f9beeb0bf6b4\",\"03c8d58183f5d8102f8eb5f6db0f60add0a51ec6737097c46fc8a6b7c840d7571f\",\"0304de0806b299cef4be3a162bac78f811d4adacc6a229ffdaeb7333bce72d88ff\",\"03e08262e18616a3a9b9aecbfb8a860ccee147820a3c60050695ef72ff2cedc4a7\",\"02caf4d61bb5deec29a39e5a1cc6d5987ec71d61d57c57bb5c2a47dd9266130bec\",\"0252d429002d9c06f0befbef6c389bdd021969b416dd83d220394e414bd5d83c0a\",\"024e23ce58533163df3e1d5766295144beb8f9729b1ac41e80ba485f39c483dfe6\",\"026de9e7e6b11fbecd88b7b49915b5df64d672ef900aa043a8cac3bc79eb414089\",\"02aaac08fc100014ec692efa0f3b408bf741e1dc68ebe28ce41837662810f40986\",\"03e0d2b426705dcc5cb62c6113b10153f10624c926a3fe86142fd9020e7d6a2129\"],\"xpub\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\"}},\"accounts_expanded\":{},\"addr_history\":{\"12bBPWWDwvtXrR9ntSgaQ7AnGyVJr16m5q\":[],\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\",490002]],\"13853om3ye5c8x6K1LfT3uCWEnG14Z82ML\":[],\"13BGVmizH8fk3qNm1biNZxAaQY3vPwurjZ\":[],\"13Tvp2DLQFpUxvc7JxAD3TXfAUWvjhwUiL\":[],\"15EQcTGzduGXSaRihKy1FY99EQQco8k2UW\":[],\"15paDwtQ33jJmJhjoBJhpWYGJDFCZppEF9\":[],\"17X8K766zBYLTjSNvHB9hA6SWRPMTcT556\":[],\"17zSo4aveNaE5DiTmwNZtxrJmS5ymzvwqj\":[],\"19BRVkUFfrAcxW9poaBSEUA2yv7SwN3SXh\":[],\"19gPT2mb9FQCiiPdAmMAaberShzNRiAtTB\":[],\"1A3vopoUcrWn7JbiAzGZactQz8HbnC1MoD\":[],\"1D1bn2Jzcx4D2GXbxzrJ1GwP4eNq98Q948\":[],\"1DvytpRGLJujPtSLYTRABzpy2r6hKJBYQd\":[],\"1EGg2acXNhJfv1bU3ixrbrmgxFtAUWpdY\":[],\"1Ev3S9YWxS7KWT8kyLmEuKV5sexNKcMUKV\":[],\"1FfpRnukxbfBnoudWvw9sdmc86YbVs7eGb\":[],\"1GBxNE82WLgd38CzoFTEkz6QS9EwLj1ym7\":[],\"1JFDe97zENNUiKeizcFUHss13vS2AcrVdE\":[],\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\":[],\"1JQqX3yg6VYxL6unuRArDQaBZYo3ktSCCP\":[],\"1JUbrr4grE71ZgWNqm9z9ZHHJDcCzFYM4V\":[],\"1JuHUVbYfBLDUhTHx5tkDDyDbCnMsF8C9w\":[],\"1KZu7p244ETkdB5turRP4vhG2QJskARYWS\":[],\"1LE7jioE7y24m3MMZayRKpvdCy2Dz2LQae\":[],\"1LVr2pTU7LPQu8o8DqsxcGrvwu5rZADxfi\":[],\"1LmugnVryiuMbgdUAv3LucnRMLvqg8AstU\":[],\"1MPN5vptDZCXc11fZjpW1pvAgUZ5Ksh3ky\":[]},\"labels\":{\"0\":\"Main account\"},\"master_public_keys\":{\"x/0'\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\",\"x/1'\":\"xpub6BycoSLDNcWjFs4B6T82q4zCbJBJdzQLwburAtBAwTLPyDPtkotGUWbef1t8D6XuCs6Yz5FUgFaL2hNzCTGe8F1bf9vNyXFMgLyKV65C9BH\",\"x/2'\":\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\"},\"next_account2\":[\"2\",\"xpub6BycoSLDNcWjHWrJyJJYmq9dDwBxSkFbWeaFFcrB6zBH9JTvyRVbAoWcmbPRmxicUkiutGQWqfsom9CbKSVG8Zh5HqHyR25xHE1xxmHeNYa\",\"031b68cff8114df7677c4fe80619b701ea966428ecbeba55c9224cd8149cc5f05e\",\"1JGek3B8b3Nt3p39x27QK5UnFtNnZ2ZdGJ\"],\"pruned_txo\":{},\"stored_height\":490009,\"transactions\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":\"01000000018394dfaba83ca6f510f622ecf95b445e856eab3193cb0dad53e1262841149d5f00000000da0047304402207761cdbf009c0bd3864c6a457288cadfa565601f782cc09f0046926d54a1b68b022060b73a7babb5dfd5188c4697cfcab6c15c4dd3de8507d39722e3a6b728f697dc01483045022100a540921229b02c4cfbf2d57222a455cbb4a5bd09bff063749fb71292f720850a02204dd18369213ec4cb033cbf222e8439eb8a9dd0a1b864bfeefa44cfe0c0066ee401475221025966a0193194a071e71501f9f8987111f7364bd8105a006f908b1f743da8d353210397c83f4963bdf333f129ab8000d89536bfea0971fc7578fdff5c2104b296c4d252aefdffffff0288130000000000001976a9141516b5e9653ab1fb09180186077fc2d7dfa07e5788aca0ba09000000000017a9148132c19d6b9abba9ec978ca5269d577ae104541e8700000000\"},\"txi\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{}},\"txo\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":{\"12vWPzJtGLKRZjnYVtWSufjRuoE8pHLpmi\":[[0,5000,false]]}},\"verified_tx3\":{\"a242aeff746aa481c5d8a496111039262f2a3fbde6038124301522539fa06837\":[490002,1508090436,607]},\"wallet_type\":\"trezor\",\"winpos-qt\":[757,469,840,400]}'''\n        self._upgrade_storage(wallet_str, accounts=2)\n\n    def test_upgrade_from_client_2_5_4_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"02a63209b49df0bb98d8a262e9891fe266ffdce4be09d5e1ffaf269a10d7e7a17c\",\"02a074035006ed8ee8f200859c004c073b687140f7d40bd333cdbbe43bad1e50bc\"],[\"0280e2367142669e08e27fb9fd476076a7f34f596e130af761aef54ec54954a64d\",\"02719a66c59f76c36921cf7b330fca7aaa4d863ee367828e7d89cd2f1aad98c3ac\"],[\"0332083e80df509d3bd8a06538ca20030086c9ed3313300f7313ed98421482020f\",\"032f336744f53843d8a007990fa909e35e42e1e32460fae2e0fc1aef7c2cff2180\"],[\"03fe014e5816497f9e27d26ce3ae8d374edadec410227b2351e9e65eb4c5d32ab7\",\"0226edd8c3af9e339631145fd8a9f6d321fdc52fe0dc8e30503541c348399dd52a\"],[\"03e6717b18d7cbe264c6f5d0ad80f915163f6f6c08c121ac144a7664b95aedfdf3\",\"03d69a074eba3bc2c1c7b1f6f85822be39aee20341923e406c2b445c255545394a\"],[\"023112f87a5b9b2eadc73b8d5657c137b50609cd83f128d130172a0ed9e3fea9bc\",\"029a81fd5ba57a2c2c6cfbcb34f369d87af8759b66364d5411eddd28e8a65f67fa\"]],\"m\":2,\"receiving\":[[\"03c35c3da2c864ee3192a847ffd3f67fa59c095d8c2c0f182ed9556308ec37231e\",\"03cfcb6d1774bfd916bd261232645f6c765da3401bf794ab74e84a6931d8318786\"],[\"03973c83f84a4cf5d7b21d1e8b29d6cbd4cb40d7460166835cd1e1fd2418cfcf2e\",\"03596801e66976959ac1bdb4025d65a412d95d320ed9d1280ac3e89b041e663cf4\"],[\"02b78ac89bfdf90559f24313d7393af272092827efc33ba3a0d716ee8b75fd08ff\",\"038e21fae8a033459e15a700551c1980131eb555bbb8b23774f8851aa10dcac6b8\"],[\"0288e9695bb24f336421d5dcf16efb799e7d1f8284413fe08e9569588bc116567e\",\"027123ba3314f77a8eb8bb57ba1015dd6d61b709420f6a3320ba4571b728ef2d91\"],[\"0312e1483f7f558aef1a14728cc125bb4ee5cff0e7fa916ba8edd25e3ebceb05e9\",\"02dad92a9893ad95d3be5ebc40828cef080e4317e3a47af732127c3fee41451356\"],[\"03a694e428a74d37194edc9e231e68399767fdb38a20eca7b72caf81b7414916a8\",\"03129a0cef4ed428031972050f00682974b3d9f30a571dc3917377595923ac41d8\"],[\"026ed41491a6d0fb3507f3ca7de7fb2fbfdfb28463ae2b91f2ab782830d8d5b32c\",\"03211b3c30c41d54734b3f13b8c9354dac238d82d012839ee0199b2493d7e7b6fc\"],[\"03480e87ffa55a96596be0af1d97bca86987741eb5809675952a854d59f5e8adc2\",\"0215f04df467d411e2a9ed8883a21860071ab721314503019a10ed30e225e522e7\"],[\"0389fce63841e9231d5890b1a0c19479f8f40f4f463ef8e54ef306641abe545ac8\",\"02396961d498c2dcb3c7081b50c5a4df15fda31300285a4c779a59c9abc98ea20d\"],[\"03d4a3053e9e08dc21a334106b5f7d9ac93e42c9251ceb136b83f1a614925eb1fb\",\"025533963c22b4f5fbfe75e6ee5ad7ee1c7bff113155a7695a408049e0b16f1c52\"],[\"038a07c8d2024b9118651474bd881527e8b9eb85fc90fdcb04c1e38688d498de4b\",\"03164b188eb06a3ea96039047d0db1c8f9be34bfd454e35471b1c2f429acd40afb\"],[\"0214070cd393f39c062ce1e982a8225e5548dbbbd654aeba6d36bfcc7a685c7b12\",\"029c6a9fb61705cc39bef34b09c684a362d4862b16a3b0b39ca4f94d75cd72290c\"],[\"027b3497f72f581fea0a678bc20482b6fc7b4b507f7263d588001d73fdf5fe314e\",\"021b80b159d19b6978a41c2a6bf7d3448bc73001885f933f7854f450b5873091f3\"],[\"0303e9d76e4fe7336397c760f6fdfd5fb7500f83e491efb604fa2442db6e1da417\",\"03a8d1b22a73d4c181aecd8cfe8bb2ee30c5dd386249d2a5a3b071b7a25b9da73a\"],[\"0298e472b74832af856fb68eed02ff00a235fd0424d833bc305613e9f44087d0ee\",\"03bb9bc2e4aaa9b022b35c8d122dfccb6c28ae8f0996a8fb4a021af8ec96a7beaf\"],[\"02e933a4afb354500da03373514247e1be12e67cc4683e0cb82f508878cc3cc048\",\"02c07a57b071bc449a95dd80308e53b26e4ebf4d523f620eecb17f96ae3aa814e9\"],[\"03f73476951078b3ccc549bc7e6362797aaaacb1ea0edc81404b4d16cb321255a3\",\"03b3a825fb9fc497e568fba69f70e2c3dcdc793637e242fce578546fcbd33cb312\"],[\"03bbdf99fddeea64a96bbb9d1e6d7ced571c9c7757045dcbd8c40137125b017dc5\",\"03aedf4452afefb1c3da25e698f621cb3a3a0130aa299488e018b93a45b5e6c21d\"],[\"03b85891edb147d43c0a5935a20d6bbf8d32c542bfecccf3ae0158b65bd639b34e\",\"03b34713c636a1c103b82d6cec917d442c59522ddc5a60bf7412266dd9790e7760\"],[\"028ddf53b85f6c01122a96bd6c181ee17ca222ee9eca85bdeeb25c4b5315005e3b\",\"02f4821995bfd5d0adb7a78d6e3a967ac72ace9d9a4f9392aff2711533893e017b\"]],\"xpubs\":[\"xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b\",\"xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp\"]}},\"accounts_expanded\":{},\"addr_history\":{\"32JvbwfEGJwZHGm3nwYiXyfsnGCb3L8hMX\":[],\"32pWy5sKkQsjyDz45tog47cA8vQyzC3UUZ\":[],\"334yqX1WtS6mY2vize7znTaL64HspwVkGF\":[],\"33GY9w6a4XmLAWxNgNFFRXTTRxbu3Nz8ip\":[],\"33geBcyW8Bw53EgAv3qwMVkVnvxZWj5J1X\":[],\"35BneogkCNxSiSN1YLmhKLP8giDbGkZiTX\":[],\"37U4J5b9B7rQnQXYstMoQnb6i9aWpptnLi\":[],\"37gqbHdbrCcGyrNF21AiDkofVCie5LpFmQ\":[],\"37t1Q5R92co4by2aagtLcqdWTDEzFuAuwZ\":[],\"37z3ruAHCxnzeJeLz96ZpkbwS3CLbtXtPc\":[],\"39qePsKaeviFEMC6CWX37DqaQda4jA2E6A\":[],\"3A5eratrDWu4SqsoHpuqswNsQmp9k8TXR2\":[],\"3B1N3PG5dNPYsTAuHFbVfkwXeZqqNS1CuP\":[],\"3BABbvd3eAuwiqJwppm54dJauKnRUieQU8\":[],\"3CAsH7BJnNT4kmwrbG8XZMMwW6ue8w4auJ\":[],\"3CX2GLCTfpFHSgAmbGRmuDKGHMbWY8tCp7\":[],\"3CrLUTVHuG1Y3swny9YDmkfJ89iHHU93NB\":[],\"3CxRa6yAQ2N2rpDHyUTaViGG4XVASAqwAN\":[],\"3DLTrsdYabso7QpxoLSW5ZFjLxBwrLEqqW\":[],\"3GG3APgrdDCTmC9tTwWu3sNV9aAnpFcddA\":[],\"3JDWpTxnsKoKut9WdG4k933qmPE5iJ8hRR\":[],\"3LdHoahj7rHRrQVe38D4iN43ySBpW5HQRZ\":[],\"3Lt56BqiJwZ1um1FtXJXzbY5uk32GVBa8K\":[],\"3MM9417myjN7ubMDkaK1wQ9RbjEc1zHCRH\":[],\"3NTivFVXva4DCjPmsf5p5Gt1dmuV39qD2v\":[],\"3QCwtjMywMtT3Vg6BwS146LcQjJnZPAPHZ\":[]},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K29YeVxd7jCexomdRiuw8UPSnHbbrAecbrQ6FgTKPyVcZqp2256L5DSTdb8UepPVaDwJecswTrEhdyZiaNGERJpfzWV5FcN5\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcEdd7bzA86LbhMoTv8NeyqcNP5z1Tiz9ajCRQDzdeXHw3h5ucDNGWr6mPFCZBcBE31VNKyR3vWM7WEeisu5m4VsCyuA6H8fp\",\"x2/\":\"xpub661MyMwAqRbcGHtCYBSGGVgMSihroMkuyE25GPyzfQvS2vSFG7SgJYf7rtXJjMh7srBJj8WddLtjapHnUQLwJ7kxsy5HiNZnGvF9pm2du7b\"},\"pruned_txo\":{},\"seed\":\"park dash merit trend life field acid wrap dinosaur kit bar hotel abuse\",\"seed_version\":11,\"stored_height\":490034,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\",\"winpos-qt\":[564,329,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_6_4_seeded(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[\"03236a8ce6fd3d343358f92d3686b33fd6e7301bf9f635e94c21825780ab79c93d\",\"0393e39f6b4a3651013fca3352b89f1ae31751d4268603f1423c71ff79cbb453a1\",\"033d9722ecf50846527037295736708b20857b4dd7032fc02317f9780d6715e8ff\",\"03f1d56d2ade1daae5706ea945cab2af719060a955c8ad78153693d8d08ed6b456\",\"029260d935322dd3188c3c6b03a7b82e174f11ca7b4d332521740c842c34649137\",\"0266e8431b49f129b892273ab4c8834a19c6432d5ed0a72f6e88be8c629c731ede\"],\"receiving\":[\"0350f41cfac3fa92310bb4f36e4c9d45ec39f227a0c6e7555748dff17e7a127f67\",\"02f997d3ed0e460961cdfa91dec4fa09f6a7217b2b14c91ed71d208375914782ba\",\"029a498e2457744c02f4786ac5f0887619505c1dae99de24cf500407089d523414\",\"03b15b06044de7935a0c1486566f0459f5e66c627b57d2cda14b418e8b9017aca1\",\"026e9c73bdf2160630720baa3da2611b6e34044ad52519614d264fbf4adc5c229a\",\"0205184703b5a8df9ae622ea0e8326134cbeb92e1f252698bc617c9598aff395a1\",\"02af55f9af0e46631cb7fde6d1df6715dc6018df51c2370932507e3d6d41c19eec\",\"0374e0c89aa4ecf1816f374f6de8750b9c6648d67fe0316a887a132c608af5e7c0\",\"0321bb62f5b5c393aa82750c5512703e39f4824f4c487d1dc130f690360c0e5847\",\"0338ea6ebb2ed80445f64b2094b290c81d0e085e6000367eb64b1dc5049f11c2e9\",\"020c3371a9fd283977699c44a205621dea8abfc8ebc52692a590c60e22202fa49b\",\"0395555e4646f94b10af7d9bc57e1816895ad2deddef9d93242d6d342cea3d753b\",\"02ffa4495d020d17b54da83eaf8fbe489d81995577021ade3a340a39f5a0e2d45c\",\"030f0e16b2d55c3b40b64835f87ab923d58bcdbb1195fadc2f05b6714d9331e837\",\"02f70041fc4b1155785784a7c23f35d5d6490e300a7dd5b7053f88135fc1f14dfd\",\"03b39508c6f9c7b8c3fb8a1b91e61a0850c3ac76ccd1a53fbc5b853a94979cffa8\",\"03b02aa869aa14b0ec03c4935cc12f221c3f204f44d64146d468e07370c040bfe7\",\"02b7d246a721e150aaf0e0e60a30ad562a32ef76a450101f3f772fef4d92b212d9\",\"037cd5271b31466a75321d7c9e16f995fd0a2b320989c14bee82e161c83c714321\",\"03d4ad77e15be312b29987630734d27ca6e9ee418faa6a8d6a50581eca40662829\"],\"xpub\":\"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE\"}},\"accounts_expanded\":{},\"addr_history\":{\"12qKnKuhCZ1Q9XBi1N6SnxYEUtb5XZXuY5\":[],\"1321ddunxShHmF4cjh3v5yqR7uatvSNndK\":[],\"13Ji3kGWn9qxLcWGhd46xjV6hg8SRw8x2P\":[],\"145q5ZDXuFi6v9dA2t8HyD8ysorfb81NRt\":[],\"14gB2wLy2DMkBVtuU6HHP3kQYNFYPzAguU\":[],\"16VGRwtZwp4yapQN5fS8CprK6mmnEicCEj\":[],\"16ahKVzCviRi24rwkoKgiSVSkvRNiQudE1\":[],\"16wjKZ1CWAMEzSR4UxQTWqXRm9jcJ9Dbuf\":[],\"18ReWGJBq1XkJaPAirVdT6RqDskcFeD5Ho\":[],\"1A1ECMMJU4NicWNwfMBn3XJriB4WHAcPUC\":[],\"1Bvxbfc2wXB8z8kyz2uyKw2Ps8JeGQM9FP\":[],\"1EDWUz4kPq8ZbCdQq8rLhFc3qSZ6Fpt1TD\":[],\"1EsvTarawMm5BfF44hpRtE4GfZFfZZ1JG3\":[],\"1JgaekD2ETMJm6oRNnwTWRK9ZxXeUcbi18\":[],\"1KHdLodsSWj1LrrD9d1RbApfqzpxRs5sxu\":[],\"1KgGwpKhruHWpMNtrpRExDWLLk5qHCHBdg\":[],\"1LFf8d3XD9atZvMVMAiq9ygaeZbphbKzSo\":[],\"1N3XncDQsWE2qff1EVyQEmR6JLLzD3mEL7\":[],\"1NUtLcVQNmY5TJCieM1cUmBmv18AafY1vq\":[],\"1NYFsm7PpneT65byRtm8niyvtzKsbEeuXA\":[],\"1NvEcSvfCe8LPvPkK4ZxhjzaUncTPqe9jX\":[],\"1PV8xdkYKxeMpnzeeA4eYEpL24j1G9ApV2\":[],\"1PdiGtznaW1mok6ETffeRvPP5f4ekBRAfq\":[],\"1QApNe4DtK7HAbJrn5kYkYxZMt86U5ChSb\":[],\"1QnH7F6RBXFe7LtszQ6KTRUPkQKRtXTnm\":[],\"1ekukhMNSWCfnRsmpkuTRuLMbz6cstkrq\":[]},\"master_private_keys\":{\"x/\":\"xprv9s21ZrQH143K4TCkhu7bE82GbtTB6ZUzXkjRfBu8ccAGe51Q7jyJ4QTsGbWxpHxnatKeYV7Ad83m7KC81THBm2xmyxA1q8BuuRXSGnmhhR8\"},\"master_public_keys\":{\"x/\":\"xpub661MyMwAqRbcGwHDovebbFy19vHfW2Cqtyf2TaJkAwhFWsLYfHHYcCnM7smpvntxJP1YMVT5triFbWiCGXPRPhqdCxFumA77MuQB1CeWHpE\"},\"pruned_txo\":{},\"seed\":\"heart cabbage scout rely square census satoshi home purpose legal replace move able\",\"seed_version\":11,\"stored_height\":489716,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"standard\",\"winpos-qt\":[582,394,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_6_4_importedkeys(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\"],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\",\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\"]}}},\"accounts_expanded\":{},\"addr_history\":{},\"pruned_txo\":{},\"stored_height\":489716,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"imported\",\"winpos-qt\":[510,338,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_6_4_watchaddresses(self):\n        wallet_str = '{\"accounts\":{\"/x\":{\"imported\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[null,null],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[null,null],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[null,null]}}},\"accounts_expanded\":{},\"addr_history\":{},\"pruned_txo\":{},\"stored_height\":490038,\"transactions\":{},\"txi\":{},\"txo\":{},\"wallet_type\":\"imported\",\"winpos-qt\":[582,425,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_6_4_multisig(self):\n        wallet_str = '{\"accounts\":{\"0\":{\"change\":[[\"03d0bcdc86a64cc2024c84853e88985f6f30d3dc3f219b432680c338a3996a89ed\",\"024f326d48aa0a62310590b10522b69d250a2439544aa4dc496f7ba6351e6ebbfe\"],[\"03c0416928528a9aaaee558590447ee63fd33fa497deebefcf363b1af90d867762\",\"03db7de16cd6f3dcd0329a088382652bc3e6b21ee1a732dd9655e192c887ed88a7\"],[\"0291790656844c9d9c24daa344c0b426089eadd3952935c58ce6efe00ef1369828\",\"02c2a5493893643102f77f91cba709f11aaab3e247863311d6fc3d3fc82624c3cc\"],[\"023dc976bd1410a7e9f34c230051db58a3f487763f00df1f529b10f55ee85b931c\",\"036c318a7530eedf3584fd8b24c4024656508e35057a0e7654f21e89e121d0bd30\"],[\"02c8820711b39272e9730a1c5c5c78fe39a642b8097f8724b2592cc987017680ce\",\"0380e3ebe0ea075e33acb3f796ad6548fde86d37c62fe8e4f6ab5d2073c1bb1d43\"],[\"0369a32ddd213677a0509c85af514537d5ee04c68114da3bc720faeb3adb45e6f8\",\"0370e85ac01af5e3fd5a5c3969c8bca3e4fc24efb9f82d34d5790e718a507cecb6\"]],\"m\":2,\"receiving\":[[\"0207739a9ff4a643e1d4adb03736ec43d13ec897bdff76b40a25d3a16e19e464aa\",\"02372ea4a291aeb1fadb26f36976348fc169fc70514797e53b789a87c9b27cc568\"],[\"0248ae7671882ec87dd6bacf7eb2ff078558456cf5753952cddb5dde08f471f3d6\",\"035bac54828b383545d7b70824a8be2f2d9584f656bfdc680298a38e9383ed9e51\"],[\"02cb99ba41dfbd510cd25491c12bd0875fe8155b5a6694ab781b42bd949252ff26\",\"03b520feba42149947f8b2bbc7e8c03f9376521f20ac7b7f122dd44ab27309d7c6\"],[\"0395902d5ebb4905edd7c4aedecf17be0675a2ffeb27d85af25451659c05cc5198\",\"02b4a01d4bd25cadcbf49900005e8d5060ed9cdc35eb33f2cd65cc45cc7ebc00c5\"],[\"02f9d06c136f05acc94e4572399f17238bb56fa15271e3cb816ae7bb9be24b00b6\",\"035516437612574b2b563929c49308911651205e7cebb621940742e570518f1c50\"],[\"0376a7de3abaee6631bd4441658987c27e0c7eee2190a86d44841ae718a014ee43\",\"03cb702364ffd59cb92b2e2128c18d8a5a255be2b95eb950641c5f17a5a900eecb\"],[\"03240c5e868ecb02c4879ae5f5bad809439fdbd2825769d75be188e34f6e533a67\",\"026b0d05784e4b4c8193443ce60bea162eee4d99f9dfa94a53ae3bc046a8574eeb\"],[\"02d087cccb7dc457074aa9decc04de5a080757493c6aa12fa5d7d3d389cfdb5b8e\",\"0293ab7d0d8bbb2d433e7521a1100a08d75a32a02be941f731d5809b22d86edb33\"],[\"03d1b83ab13c5b35701129bed42c1f1fbe86dd503181ad66af3f4fb729f46a277e\",\"0382ec5e920bc5c60afa6775952760668af42b67d36d369cd0e9acc17e6d0a930d\"],[\"03f1737db45f3a42aebd813776f179d5724fce9985e715feb54d836020b8517bfe\",\"0287a9dfb8ee2adab81ef98d52acd27c25f558d2a888539f7d583ef8c00c34d6dc\"],[\"038eb8804e433023324c1d439cd5fbbd641ca85eadcfc5a8b038cb833a755dac21\",\"0361a7c80f0d9483c416bc63d62506c3c8d34f6233b6d100bb43b6fe8ec39388b9\"],[\"0336437ada4cd35bec65469afce298fe49e846085949d93ef59bf77e1a1d804e4a\",\"0321898ed89df11fcfb1be44bb326e4bb3272464f000a9e51fb21d25548619d377\"],[\"0260f0e59d6a80c49314d5b5b857d1df64d474aba48a37c95322292786397f3dc6\",\"03acd6c9aeac54c9510304c2c97b7e206bbf5320c1e268a2757d400356a30c627b\"],[\"0373dc423d6ee57fac3b9de5e2b87cf36c21f2469f17f32f5496e9e7454598ba8e\",\"031ddc1f40c8b8bf68117e790e2d18675b57166e9521dff1da44ba368be76555b3\"],[\"031878b39bc6e35b33ceac396b429babd02d15632e4a926be0220ccbd710c7d7b9\",\"025a71cc5009ae07e3e991f78212e99dd5be7adf941766d011197f331ce8c1bed0\"],[\"032d3b42ed4913a134145f004cf105b66ae97a9914c35fb73d37170d37271acfcd\",\"0322adeb83151937ddcd32d5bf2d3ed07c245811d0f7152716f82120f21fb25426\"],[\"0312759ff0441c59cb477b5ec1b22e76a794cd821c13b8900d72e34e9848f088c2\",\"02d868626604046887d128388e86c595483085f86a395d68920e244013b544ef3b\"],[\"038c4d5f49ab08be619d4fed7161c339ea37317f92d36d4b3487f7934794b79df4\",\"03f4afb40ae7f4a886f9b469a81168ad549ad341390ff91ebf043c4e4bfa05ecc1\"],[\"02378b36e9f84ba387f0605a738288c159a5c277bbea2ea70191ade359bc597dbb\",\"029fd6f0ee075a08308c0ccda7ace4ad9107573d2def988c2e207ac1d69df13355\"],[\"02cfecde7f415b0931fc1ec06055ff127e9c3bec82af5e3affb15191bf995ffc1a\",\"02abb7481504173a7aa1b9860915ef62d09a323425f680d71746be6516f0bb4acf\"]],\"xpubs\":[\"xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV\",\"xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW\"]}},\"accounts_expanded\":{},\"addr_history\":{\"329Ju5tiAr4vHZExAT4KydYEkfKiHraY2N\":[],\"32HJ13iTVh3sCWyXzipcGb1e78ZxcHrQ7v\":[],\"32cAdiAapUzNVRYXmDud5J5vEDcGsPHjD8\":[],\"33fKLmoCo8oFfeV987P6KrNTghSHjJM251\":[],\"34cE6ZcgXvHEyKbEP2Jpz5C3aEWhvPoPG2\":[],\"36xsnTKKBojYRHEApVR6bCFbDLp9oqNAxU\":[],\"372PG6D3chr8tWF3J811dKSpPS84MPU6SE\":[],\"378nVF8daT4r3jfX1ebKRheUVZX5zaa9wd\":[],\"392ZtXKp2THrk5VtbandXxFLB8yr2g14aA\":[],\"39cCrU3Zz3SsHiQUDiyPS1Qd5ZL3Rh1GhQ\":[],\"3A2cRoBdem5tdRjq514Pp7ZvaxydgZiaNG\":[],\"3Ceoi3MKdh2xiziHDAzmriwjDx4dvxxLzm\":[],\"3FcXdG8mh1YeQCYVib8Aw7zwnKpComimLH\":[],\"3J4b31yAbQkKhejSW7Qz54qNJDEy3t9uSe\":[],\"3JpJrSxE1GP1X5h82zvLA2TbMZ8nUsGW6z\":[],\"3K1dzpbcop1MotuqyFQyEuXbvQehaKnGVM\":[],\"3L8Us8SN22Hj6GnZPRCLaowA1ZtbptXxxL\":[],\"3LANyoJyShQ8w55tvopoGiZ2BTVjLfChiP\":[],\"3LoJGQdXTzVaDYudUguP4jNJYy4gNDaRpN\":[],\"3MD8jVH7Crp5ucFomDnWqB6kQrEQ9VF5xv\":[],\"3ME8DemkFJSn2tHS23yuk2WfaMP86rd3s7\":[],\"3MFNr17oSZpFtH16hGPgXz2em2hJkd3SZn\":[],\"3QHRTYnW2HWCWoeisVcy3xsAFC5xb6UYAK\":[],\"3QKwygVezHFBthudRUh8V7wwtWjZk3whpB\":[],\"3QNPY3dznFwRv6VMcKgmn8FGJdsuSRRjco\":[],\"3QNwwD8dp6kvS8Fys4ZxVJYZAwCXdXQBKo\":[]},\"master_private_keys\":{\"x1/\":\"xprv9s21ZrQH143K3oPcB2UmMA6Cy9W49HLyW6CDNhQuRcn7tGu1tQ2bn6TLw8HFWbu5oP38Z2fFCo5Q4n3fog4DTqywYqfSDWhYbDgVD1TGZoP\"},\"master_public_keys\":{\"x1/\":\"xpub661MyMwAqRbcGHU5H41miJ2wXBLYYk4psK7pB5pWyxK6m5EARwLrKtmpnMzP52qGsKZEtjJCyohVEaZTFXbohjVdfpDFifgMBT82EvkFpsW\",\"x2/\":\"xpub661MyMwAqRbcF4mZnFnBRYGBaiD9aQRp9w2jaPUrDg3Eery5gywV7eFMzQKmNyY1W4m4fUwsinMw1tFhMNEZ9KjNtkUSBHPXdcXBwCg5ctV\"},\"pruned_txo\":{},\"seed\":\"turkey weapon legend tower style multiply tomorrow wet like frame leave cash achieve\",\"seed_version\":11,\"stored_height\":490035,\"transactions\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"wallet_type\":\"2of2\",\"winpos-qt\":[610,418,840,400]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_7_18_seeded(self):\n        wallet_str = '{\"addr_history\":{\"12nzqpb4vxiFmcvypswSWK1f4cvGwhYAE8\":[],\"13sapXcP5Wq25PiXh5Zr9mLhyjdfrppWyi\":[],\"14EzC5y5eFCXg4T7cH4hXoivzysEpGXBTM\":[],\"15PUQBi2eEzprCZrS8dkfXuoNv8TuqwoBm\":[],\"16NvXzjxHbiNAULoRRTBjSmecMgF87FAtb\":[],\"16oyPjLM4R96aZCnSHqBBkDMgbE2ehDWFe\":[],\"1BfhL8ZPcaZkXTZKASQYcFJsPfXNwCwVMV\":[],\"1Bn3vun14mDWBDkx4PvK2SyWK1nqB9MSmM\":[],\"1BrCEnhf763JhVNcZsjGcNmmisBfRkrdcn\":[],\"1BvXCwXAdaSTES4ENALv3Tw6TJcZbMzu5o\":[],\"1C2vzgDyPqtvzFRYUgavoLvk3KGujkUUjg\":[],\"1CN22zUHuX5SxGTmGvPTa2X6qiCJZjDUAW\":[],\"1CUT9Su42c4MFxrfbrouoniuhVuvRjsKYS\":[],\"1DLaXDPng4wWXW7AdDG3cLkuKXgEUpjFHq\":[],\"1DTLcXN6xPUVXP1ZQmt2heXe2KHDSdvRNv\":[],\"1F1zYJag8yXVnDgGGy7waQT3Sdyp7wLZm3\":[],\"1Fim67c46NHTcSUu329uF8brTmkoiz6Ej8\":[],\"1Go6JcgkfZuA7fyQFKuLddee9hzpo31uvL\":[],\"1J6mhetXo9Eokq7NGjwbKnHryxUCpgbCDn\":[],\"1K9sFmS7qM2P5JpVGQhHMqQgAnNiujS5jZ\":[],\"1KBdFn9tGPYEqXnHyJAHxBfCQFF9v3mq95\":[],\"1LRWRLWHE2pdMviVeTeJBa8nFbUTWSCvrg\":[],\"1LpXAktoSKbRx7QFkyb2KkSNJXSGLtTg9T\":[],\"1LtxCQLTqD1q5Q5BReP932t5D7pKx5wiap\":[],\"1MX5AS3pA5jBhmg4DDuDQEuNhPGS4cGU4F\":[],\"1Pz9bYFMeqZkXahx9yPjXtJwL69zB3xCp2\":[]},\"keystore\":{\"seed\":\"giraffe tuition frog desk airport rural since dizzy regular victory mind coconut\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K28Jvnpm7hU3xPt18neaDpcpoMKTyi9ewNRg6puJ2RAE5gZNPQ73bbmU9WsagxLQ3a6i2t1M9W289HY9Q5sEzFsLaYq3ZQf3\",\"xpub\":\"xpub661MyMwAqRbcEcPPtrJ84bzgwuqdC7J5BqkQ9hsbGVBvFE1FNScGxxYZXpC9ncowEe7EZVbAerSypw3wCjrmLmsHeG3RzySw5iEJhAfZaZT\"},\"pruned_txo\":{},\"pubkeys\":{\"change\":[\"033e860b0823ed2bf143594b07031d9d95d35f6e4ad6093ddc3071b8d2760f133f\",\"03f51e8798a1a46266dee899bada3e1517a7a57a8402deeef30300a8918c81889a\",\"0308168b05810f62e3d08c61e3c545ccbdce9af603adbdf23dcc366c47f1c5634c\",\"03d7eddff48be72310347efa93f6022ac261cc33ee0704cdad7b6e376e9f90f574\",\"0287e34a1d3fd51efdc83f946f2060f13065e39e587c347b65a579b95ef2307d45\",\"02df34e258a320a11590eca5f0cb0246110399de28186011e8398ce99dd806854a\"],\"receiving\":[\"031082ff400cbe517cc2ae37492a6811d129b8fb0a8c6bd083313f234e221527ae\",\"03fac4d7402c0d8b290423a05e09a323b51afebd4b5917964ba115f48ab280ef07\",\"03c0a8c4ab604634256d3cfa350c4b6ca294a4374193055195a46626a6adea920f\",\"03b0bc3112231a9bea6f5382f4324f23b4e2deb5f01a90b0fe006b816367e43958\",\"03a59c08c8e2d66523c888416e89fa1aaec679f7043aa5a9145925c7a80568e752\",\"0346fefc07ab2f38b16c8d979a8ffe05bc9f31dd33291b4130797fa7d78f6e4a35\",\"025eb34724546b3c6db2ee8b59fbc4731bafadac5df51bd9bbb20b456d550ef56e\",\"02b79c26e2eac48401d8a278c63eec84dc5bef7a71fa7ce01a6e333902495272e2\",\"03a3a212462a2b12dc33a89a3e85684f3a02a647db3d7eaae18c029a6277c4f8ac\",\"02d13fc5b57c4d057accf42cc918912221c528907a1474b2c6e1b9ca24c9655c1a\",\"023c87c3ca86f25c282d9e6b8583b0856a4888f46666b413622d72baad90a25221\",\"030710e320e9911ebfc89a6b377a5c2e5ae0ab16b9a3df54baa9dbd3eb710bf03c\",\"03406b5199d34be50725db2fcd440e487d13d1f7611e604db81bb06cdd9077ffa5\",\"0378139461735db84ff4d838eb408b9c124e556cfb6bac571ed6b2d0ec671abd0c\",\"030538379532c476f664d8795c0d8e5d29aea924d964c685ea5c2343087f055a82\",\"02d1b93fa37b824b4842c46ef36e5c50aadbac024a6f066b482be382bec6b41e5a\",\"02d64e92d12666cde831eb21e00079ecfc3c4f64728415cc38f899aca32f1a5558\",\"0347480bf4d321f5dce2fcd496598fbdce19825de6ed5b06f602d66de7155ac1c0\",\"03242e3dfd8c4b6947b0fbb0b314620c0c3758600bb842f0848f991e9a2520a81c\",\"021acadf6300cb7f2cca11c6e1c7e59e3cf923a786f6371c3b85dd6f8b65c68470\"]},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[709,314,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_7_18_importedkeys(self):\n        wallet_str = '{\"addr_history\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[]},\"keystore\":{\"keypairs\":{\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\":\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\",\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\":\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\",\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\":\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"},\"type\":\"imported\"},\"pruned_txo\":{},\"pubkeys\":{\"change\":[],\"receiving\":[\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\",\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\",\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\"]},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[420,312,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_7_18_watchaddresses(self):\n        wallet_str = '{\"addr_history\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[]},\"addresses\":[\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\",\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\",\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\"],\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"verified_tx3\":{},\"wallet_type\":\"imported\",\"winpos-qt\":[553,402,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_7_18_trezor_singleacc(self):\n        wallet_str = '''{\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"keystore\":{\"derivation\":\"m/44'/0'/0'\",\"hw_type\":\"trezor\",\"label\":\"trezor1\",\"type\":\"hardware\",\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"pruned_txo\":{},\"pubkeys\":{\"change\":[\"03143bc04f007c454e03caf9d59b61e27f527b5e6723e167b50197ce45e2071902\",\"03157710459a8213a79060e2f2003fe0eb7a7ed173ac3f846309de52269dd44740\",\"028ec4bbbf4ac9edfabb704bd82acb0840f2166312929ce01af2b2e99059b16dee\",\"021a9f1201968bd835029daf09ae98745a75bcb8c6143b80610cfc2eb2eee94dd8\",\"031fe8323703fee4a1f6c59f27ceed4e227f5643b1cb387b39619b6b5499a971b4\",\"033199fc62b72ce98e3780684e993f31d520f1da0bf2880ed26153b2efcc86ac1d\"],\"receiving\":[\"03d27c0f5594d8df0616d64a735c909098eb867d01c6f1588f04ca2cf353837ec0\",\"024d299f21e9ee9cc3eb425d04f45110eff46e45abcab24a3e594645860518fb97\",\"03f6bc650e5f118ab4a63359a9cde4ab8382fe16e7d1b36b0a459145a79bef674b\",\"028bed00a2fbd03f1ff43e0150ec1018458f7b39f3e4e602e089b1f47f8f607136\",\"02108b15014d53f2e4e1b5b2d8f5eaf82006bbc4f273dbfbaef91eff08f9d10ea5\",\"02a9a59a529818f3ba7a37ebe34454eac2bcbe4da0e8566b13f369e03bb020c4c4\",\"023fde4ecf7fbdffb679d92f58381066cf2d840d34cb2d8bef63f7c5182d278d53\",\"02ad8bf6dc0ff3c39bd20297d77fbd62073d7bf2fa44bf716cdd026db0819bb2b4\",\"029c8352118800beaef1f3fa9c12afe30d329e7544bea9b136b717b88c24d95d92\",\"02c42c251392674e2c2768ccd6224e04298bd5479436f02e9867ecc288dd2eb066\",\"0316f3c82d9fce97e267b82147d56a4b170d39e6cf01bfaff6c2ae6bcc79a14447\",\"0398554ee8e333061391b3e866505bbc5e130304ae09b198444bcd31c4ba7846ea\",\"02e69d21aadb502e9bd93c5536866eff8ca6b19664545ccc4e77f3508e0cbe2027\",\"0270fb334407a53a23ad449389e2cb055fae5017ca4d79ec8e082038db2d749c50\",\"03d91a8f47453f9da51e0194e3aacff88bf79a625df82ceee73c71f3a7099a5459\",\"0306b2d3fd06c4673cc90374b7db0c152ba7960be382440cecc4cdad7642e0537c\",\"028020dd6062f080e1e2b49ca629faa1407978adab13b74875a9de93b16accf804\",\"03907061c5f6fde367aafe27e1d53b39ff9c2babffe8ab7cf8c3023acba5c39736\",\"029749462dba9af034455f5e0f170aac67fe9365ce7126092b4d24ced979b5381f\",\"02f001d35308833881b3440670d25072256474c6c4061daf729055bf9563134105\"]},\"seed_version\":13,\"stored_height\":490013,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[631,410,840,405]}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_7_18_multisig(self):\n        wallet_str = '{\"addr_history\":{\"32WKXQ6BWtGJDVTpdcUMhtRZWzgk5eKnhD\":[],\"33rvo2pxaccCV7jLwvth36sdLkdEqhM8B8\":[],\"347kG9dzt2M1ZPTa2zzcmVrAE75LuZs9A2\":[],\"34BBeAVEe5AM6xkRebddFG8JH6Vx1M5hHH\":[],\"34MAGbxxCHPX8ASfKsyNkzpqPEUTZ5i1Kx\":[],\"36uNpoPSgUhN5Cc1wRQyL77aD1RL3a9X6f\":[],\"384xygkfYsSuXN478zhN4jmNcky1bPo7Cq\":[],\"39GBGaGpp1ePBsjjaw8NmbZNZkMzhfmZ3W\":[],\"3BRhw13g9ShGcuHbHExxtFfvhjrxiSiA7J\":[],\"3BboKZc2VgjKVxoC5gndLGpwEkPJuQrZah\":[],\"3C3gKJ2UQNNHY2SG4h43zRS1faSLhnqQEr\":[],\"3CEY1V5WvCTxjHEPG5BY4eXpcYhakTvULJ\":[],\"3DJyQ94H9g18PR6hfzZNxwwdU6773JaYHd\":[],\"3Djb7sWog5ANggPWHm4xT5JiTrTSCmVQ8N\":[],\"3EfgjpUeJBhp3DcgP9wz3EhHNdkCbiJe2L\":[],\"3FWgjvaL8xN6ne19WCEeD5xxryyKAQ5tn1\":[],\"3H4ZtDFovXxwWXCpRo8mrCczjTrtbT6eYL\":[],\"3HvnjPzpaE3VGWwGTALZBguT8p9fyAcfHS\":[],\"3JGuY9EpzuZkDLR7vVGhqK7zmX9jhYEfmD\":[],\"3JvrP4gpCUeQzqgPyDt2XePXn3kpqFTo9i\":[],\"3K3TVvsfo52gdwz7gk84hfP77gRmpc3hkf\":[],\"3K5uh5viV4Dac267Q3eNurQQBnpEbYck5G\":[],\"3KaoWE1m3QrtvxTQLFfvNs8gwQH8kQDpFM\":[],\"3Koo71MC4wBfiDKTsck7qCrRjtGx2SwZqT\":[],\"3L8XBt8KxwqNX1vJprp6C9YfNW4hkYrC6d\":[],\"3QmZjxPwcsHZgVUR2gQ6wdbGJBbFro8KLJ\":[]},\"pruned_txo\":{},\"pubkeys\":{\"change\":[[\"031bfbbfb36b5e526bf4d94bfc59f170177b2c821f7d4d4c0e1ee945467fe031a0\",\"03c4664d68e3948e2017c5c55f7c1aec72c1c15686b07875b0f20d5f856ebeb703\"],[\"03c515314e4b695a809d3ba08c20bef00397a0e2df729eaf17b8e082825395e06b\",\"032391d8ab8cad902e503492f1051129cee42dc389231d3cdba60541d70e163244\"],[\"035934f55c09ecec3e8f2aa72407ee7ba3c2f077be08b92a27bc4e81b5e27643fe\",\"0332b121ed13753a1f573feaf4d0a94bf5dd1839b94018844a30490dd501f5f5fb\"],[\"02b1367f7f07cbe1ef2c75ac83845c173770e42518da20efde3239bf988dbff5ac\",\"03f3a8b9033b3545fbe47cab10a6f42c51393ed6e525371e864109f0865a0af43c\"],[\"02e7c25f25ecc17969a664d5225c37ec76184a8843f7a94655f5ed34b97c52445d\",\"030ae4304923e6d8d6cd67324fa4c8bc44827918da24a05f9240df7c91c8e8db8f\"],[\"02deb653a1d54372dbc8656fe0a461d91bcaec18add290ccaa742bdaefdb9ec69b\",\"023c1384f90273e3fc8bc551e71ace8f34831d4a364e56a6e778cd802b7f7965a6\"]],\"receiving\":[[\"02d978f23dc1493db4daf066201f25092d91d60c4b749ca438186764e6d80e6aa1\",\"02912a8c05d16800589579f08263734957797d8e4bc32ad7411472d3625fd51f10\"],[\"024a4b4f2553d7f4cc2229922387aad70e5944a5266b2feb15f453cedbb5859b13\",\"03f8c6751ee93a0f4afb7b2263982b849b3d4d13c2e30b3f8318908ad148274b4b\"],[\"03cd88a88aabc4b833b4631f4ffb4b9dc4a0845bb7bc3309fab0764d6aa08c4f25\",\"03568901b1f3fb8db05dd5c2092afc90671c3eb8a34b03f08bcfb6b20adf98f1cd\"],[\"030530ffe2e4a41312a41f708febab4408ca8e431ce382c1eedb837901839b550d\",\"024d53412197fc609a6ca6997c6634771862f2808c155723fac03ea89a5379fdcc\"],[\"02de503d2081b523087ca195dbae55bafb27031a918a1cfedbd2c4c0da7d519902\",\"03f4a27a98e41bddb7543bf81a9c53313bf9cfb2c2ebdb6bf96551221d8aecb01a\"],[\"03504bc595ac0d947299759871bfdcf46bcdd8a0590c44a78b8b69f1b152019418\",\"0291f188301773dbc7c1d12e88e3aa86e6d4a88185a896f02852141e10e7e986ab\"],[\"0389c3ab262b7994d2202e163632a264f49dd5f78517e01c9210b6d0a29f524cd4\",\"034bdfa9cc0c6896cb9488329d14903cfe60a2879771c5568adfc452f8dba1b2cb\"],[\"02c55a517c162aae2cb5b36eef78b51aa15040e7293033a5b55ba299e375da297d\",\"027273faf29e922d95987a09c2554229becb857a68112bd139409eb111e7cdb45e\"],[\"02401e62d645dc64d43f77ba1f360b529a4c644ed3fc15b35932edafbaf741e844\",\"02c44cbffc13cb53134354acd18c54c59fa78ec61307e147fa0f6f536fb030a675\"],[\"02194a538f37b388b2b138f73a37d7fbb9a3e62f6b5a00bad2420650adc4fb44d9\",\"03e5cc15d47fcdcf815baa0e15227bc5e6bd8af6cae6add71f724e95bc29714ce5\"],[\"037ebf7b2029c8ea0c1861f98e0952c544a38b9e7caebbf514ff58683063cd0e78\",\"022850577856c810dead8d3d44f28a3b71aaf21cdc682db1beb8056408b1d57d52\"],[\"02aea7537611754fdafd98f341c5a6827f8301eaf98f5710c02f17a07a8938a30e\",\"032fa37659a8365fdae3b293a855c5a692faca687b0875e9720219f9adf4bdb6c2\"],[\"0224b0b8d200238495c58e1bc83afd2b57f9dbb79f9a1fdb40747bebb51542c8d3\",\"03b88cd2502e62b69185b989abb786a57de27431ece4eabb26c934848d8426cbd6\"],[\"032802b0be2a00a1e28e1e29cfd2ad79d36ef936a0ef1c834b0bbe55c1b2673bff\",\"032669b2d80f9110e49d49480acf696b74ecca28c21e7d9c1dd2743104c54a0b13\"],[\"03fcfa90eac92950dd66058bbef0feb153e05a114af94b6843d15200ef7cf9ea4a\",\"023246268fbe8b9a023d9a3fa413f666853bbf92c4c0af47731fdded51751e0c3a\"],[\"020cf5fffe70b174e242f6193930d352c54109578024677c1a13ffce5e1f9e6a29\",\"03cb996663b9c895c3e04689f0cf1473974023fa0d59416be2a0b01ccdaa3cc484\"],[\"03467e4fff9b33c73b0140393bde3b35a3f804bce79eccf9c53a1f76c59b7452bd\",\"03251c2a041e953c8007d9ee838569d6be9eacfbf65857e875d87c32a8123036d8\"],[\"02192e19803bfa6f55748aada33f778f0ebb22a1c573e5e49cba14b6a431ef1c37\",\"02224ce74f1ee47ba6eaaf75618ce2d4768a041a553ee5eb60b38895f3f6de11dc\"],[\"032679be8a73fa5f72d438d6963857bd9e49aef6134041ca950c70b017c0c7d44f\",\"025a8463f1c68e85753bd2d37a640ab586d8259f21024f6173aeed15a23ad4287b\"],[\"03ab0355c95480f0157ae48126f893a6d434aa1341ad04c71517b104f3eda08d3d\",\"02ba4aadba99ae8dc60515b15a087e8763496fcf4026f5a637d684d0d0f8a5f76c\"]]},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"2of2\",\"winpos-qt\":[523,230,840,405],\"x1/\":{\"seed\":\"pudding sell evoke crystal try order supply chase fine drive nurse double\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K2MK5erSSgeaPA1H7gENYS6grakohkaK2M4tzqo6XAjLoRPcBRW9NbGNpaZN3pdoSKLeiQJwmqdSi3GJWZLnK1Txpbn3zinV\",\"xpub\":\"xpub661MyMwAqRbcEqPYksyT3nX7i37c5h6PoKcTP9DKJur1DsE9PLQmiXfHGe8RmN538Pj8t3qUQcZXCMrkS5z1uWJ6jf9EptAFbC4Z2nKaEQE\"},\"x2/\":{\"type\":\"bip32\",\"xprv\":null,\"xpub\":\"xpub661MyMwAqRbcGYXvLgWjW91feK49GajmPdEarB3Ny8JDduUhzTcEThc8Xs1GyqMR4S7xPHvSq4sbDEFzQh3hjJJFEksUzvnjYnap5RX9o4j\"}}'\n        self._upgrade_storage(wallet_str)\n\n    # seed_version 13 is ambiguous\n    # client 2.7.18 created wallets with an earlier \"v13\" structure\n    # client 2.8.3 created wallets with a later \"v13\" structure\n    # client 2.8.3 did not do a proper clean-slate upgrade\n    # the wallet here was created in 2.7.18 with a couple privkeys imported\n    # then opened in 2.8.3, after which a few other new privkeys were imported\n    # it's in some sense in an \"inconsistent\" state\n    def test_upgrade_from_client_2_8_3_importedkeys_flawed_previous_upgrade_from_2_7_18(self):\n        wallet_str = '{\"addr_history\":{\"15VBrfYwoXvDWyXHq1myxDv4h36qUmCHcE\":[],\"179vRrzjT9k7k5oCNCx6eodYCaLKPy9UQn\":[],\"18o6WCBWdAaM5kjKnyEL4HysoT324rvJu7\":[],\"1A9F6ZEqmfKeuLeEq5eWFxajgiJfGCc7ar\":[],\"1BTjGNUmeMSPBTuXTdwD3DLyCugAZaFb7w\":[],\"1CjW4KM38acCRw3spiFKiZsj7xmmQqqwd8\":[],\"1EaDNLPwHRraX1N3ecPWJ2mm7NRgdtvpCj\":[],\"1PYtQBkjXHQX6YtMzEgehN638o784pK3ce\":[],\"1yT2T4ha3i1GZoK2iP8EpcgSNG34R2ufM\":[]},\"addresses\":{\"change\":[],\"receiving\":[\"1PYtQBkjXHQX6YtMzEgehN638o784pK3ce\",\"1yT2T4ha3i1GZoK2iP8EpcgSNG34R2ufM\",\"1CjW4KM38acCRw3spiFKiZsj7xmmQqqwd8\",\"1A9F6ZEqmfKeuLeEq5eWFxajgiJfGCc7ar\",\"18o6WCBWdAaM5kjKnyEL4HysoT324rvJu7\",\"1EaDNLPwHRraX1N3ecPWJ2mm7NRgdtvpCj\",\"179vRrzjT9k7k5oCNCx6eodYCaLKPy9UQn\",\"1BTjGNUmeMSPBTuXTdwD3DLyCugAZaFb7w\",\"15VBrfYwoXvDWyXHq1myxDv4h36qUmCHcE\"]},\"keystore\":{\"keypairs\":{\"0206b77fd06f212ad7d85f4a054c231ba4e7894b1773dcbb449671ee54618ff5e9\":\"L52LWS2hB5ev9JYiisFewJH9Q16U7yYcSNt3M8UKLmL5p1q3v2H2\",\"028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42\":\"KzRhkN9Psm9BobcPx3X3VykVA8yhCBrVvE4tTyq6NE283sL6uvYG\",\"02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f\":\"KySXfvidmMBf8iw6m3R9WtdfKcQPWXenwMZtpno5XpfLMNHH8PMn\",\"031bb44462038b97010624a8f8cb15a10fd0d277f12aba3ccf5ce0d36fc6df3112\":\"KxmcmCvNrZFgy2jyz9W353XbMwCYWHzYTQVzbaDfZM4FLxemgmKh\",\"0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042\":\"L53Ks569m3H1dRzua3nGzBE3AaEV8dMvBoHDeSJGnWEDeL775mJ5\",\"0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643\":\"KwHDUpfvnSC58bs3nGy7YpducXkbmo6UUHrydBHy6sT1mRJcVvBo\",\"04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba\":\"5JECca5E7r1eNgME7NsPdE29XiVCVwXSzEihnhAQXuMdsJ4VL8S\",\"04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed\":\"5Jt9rGLWgxoJUo4eoYEECskLmRA4BkZqHPHg7DdghKBaWarKuxW\",\"04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b\":\"5KRjCNThRDP8aQTJ3Hq9HUSVNRNUB2e69xwLfMUsrXYLXT7U8b9\"},\"type\":\"imported\"},\"pruned_txo\":{},\"pubkeys\":{\"change\":[],\"receiving\":[\"04e9ad0bf70c51c06c2459961175c47cfec59d58ebef4ffcd9836904ef11230afce03ab5eaac5958b538382195b5aea9bf057c0486079869bb72ef9c958f33f1ed\",\"0339081c4a0ce22c01aa78a5d025e7a109100d1a35ef0f8f06a0d4c5f9ffefc042\",\"0339ea71aba2805238e636c2f1b3e5a8308b1dbdbb335787c51f2f6bf3f6218643\",\"02ba4117a24d7e38ae14c429fce0d521aa1fb6bb97558a13f1ef2bc0a476a1741f\",\"028cda4a0f03cbcbc695d9cac0858081fd5458acfd29564127d329553245afca42\",\"04e7dc460c87267cf0958d6904d9cd99a4af0d64d61858636aec7a02e5f9a578d27c1329d5ddc45a937130ed4a59e4147cb4907724321baa6a976f9972a17f79ba\",\"04f8cbd67830ab37138c92898a64a4edf836a60aa5b36956547788bd205c635d6a3056fa6a079961384ae336e737d4c45835821c8915dbc5e18a7def88df83946b\"]},\"seed_version\":13,\"stored_height\":492756,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[100,100,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_8_3_seeded(self):\n        wallet_str = '{\"addr_history\":{\"13sNgoAhqDUTB3YSzWYcKKvP2EczG5JGmt\":[],\"14C6nXs2GRaK3o5U5e8dJSpVRCoqTsyAkJ\":[],\"14fH7oRM4bqJtJkgJEynTShcUXQwdxH6mw\":[],\"16FECc7nP2wor1ijXKihGofUoCkoJnq6XR\":[],\"16cMJC5ZAtPnvLQBzfHm9YR9GoDxUseMEk\":[],\"17CbQhK3gutqgWt2iLX69ZeSCvw8yFxPLz\":[],\"17jEaAyekE8BHPvPmkJqFUh1v1GSi6ywoV\":[],\"19F5SjaWYVCKMPWR8q1Freo4RGChSmFztL\":[],\"19snysSPZEbgjmeMtuT7qDMTLH2fa7zrWW\":[],\"1AFgvLGNHP3nZDNrZ4R2BZKnbwDVAEUP4q\":[],\"1AwWgUbjQfRhKVLKm1o7qfpXnqeN3cu7Ms\":[],\"1B4FU2WEd2NQzd2MkWBLHw87uJhBxoVghh\":[],\"1BEBouVJFihDmEQMTAv4bNV2Q7dZh5iJzv\":[],\"1BdB7ahc8TSR9RJDmWgGSgsWji2BgzcVvC\":[],\"1DGhQ1up6dMieEwFdsQQFHRriyyR59rYVq\":[],\"1HBAAqFVndXBcWdWQNYVYSDK9kdUu8ZRU3\":[],\"1HMrRJkTayNRBZdXZKVb7oLZKj24Pq65T6\":[],\"1HiB2QCfNem8b4cJaZ2Rt9T4BbUCPXvTpT\":[],\"1HkbtbyocwHWjKBmzKmq8szv3cFgSGy7dL\":[],\"1K5CWjgZEYcKTsJWeQrH6NcMPzFUAikD8z\":[],\"1KMDUXdqpthH1XZU4q5kdSoMZmCW9yDMcN\":[],\"1KmHNiNmeS7tWRLYTFDMrTbKR6TERYicst\":[],\"1NQwmHYdxU1pFTTWyptn8vPW1hsSWJBRTn\":[],\"1NuPofeK8yNEjtVAu9Rc2pKS9kw8YWUatL\":[],\"1Q3eTNJWTnfxPkUJXQkeCqPh1cBQjjEXFn\":[],\"1QEuVTdenchPn9naMhakYx8QwGUXE6JYp\":[]},\"addresses\":{\"change\":[\"1K5CWjgZEYcKTsJWeQrH6NcMPzFUAikD8z\",\"19snysSPZEbgjmeMtuT7qDMTLH2fa7zrWW\",\"1DGhQ1up6dMieEwFdsQQFHRriyyR59rYVq\",\"17CbQhK3gutqgWt2iLX69ZeSCvw8yFxPLz\",\"1Q3eTNJWTnfxPkUJXQkeCqPh1cBQjjEXFn\",\"17jEaAyekE8BHPvPmkJqFUh1v1GSi6ywoV\"],\"receiving\":[\"1KMDUXdqpthH1XZU4q5kdSoMZmCW9yDMcN\",\"1HkbtbyocwHWjKBmzKmq8szv3cFgSGy7dL\",\"1HiB2QCfNem8b4cJaZ2Rt9T4BbUCPXvTpT\",\"14fH7oRM4bqJtJkgJEynTShcUXQwdxH6mw\",\"1NuPofeK8yNEjtVAu9Rc2pKS9kw8YWUatL\",\"16FECc7nP2wor1ijXKihGofUoCkoJnq6XR\",\"19F5SjaWYVCKMPWR8q1Freo4RGChSmFztL\",\"1NQwmHYdxU1pFTTWyptn8vPW1hsSWJBRTn\",\"1HBAAqFVndXBcWdWQNYVYSDK9kdUu8ZRU3\",\"1B4FU2WEd2NQzd2MkWBLHw87uJhBxoVghh\",\"1HMrRJkTayNRBZdXZKVb7oLZKj24Pq65T6\",\"1KmHNiNmeS7tWRLYTFDMrTbKR6TERYicst\",\"1BdB7ahc8TSR9RJDmWgGSgsWji2BgzcVvC\",\"14C6nXs2GRaK3o5U5e8dJSpVRCoqTsyAkJ\",\"1AFgvLGNHP3nZDNrZ4R2BZKnbwDVAEUP4q\",\"13sNgoAhqDUTB3YSzWYcKKvP2EczG5JGmt\",\"1AwWgUbjQfRhKVLKm1o7qfpXnqeN3cu7Ms\",\"1QEuVTdenchPn9naMhakYx8QwGUXE6JYp\",\"1BEBouVJFihDmEQMTAv4bNV2Q7dZh5iJzv\",\"16cMJC5ZAtPnvLQBzfHm9YR9GoDxUseMEk\"]},\"keystore\":{\"seed\":\"novel clay width echo swing blanket absorb salute asset under ginger final\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K2jfFF6ektPj6zCCsDGGjQxhD2FQ21j6yrA1piWWEjch2kf1smzB2rzm8rPkdJuHf3vsKqMX9ogtE2A7JF49qVUHrgtjRymM\",\"xpub\":\"xpub661MyMwAqRbcFDjiM8BmFXfqYE3McizanBcopdoda4dxixLyG3pVHR1WbwgjLo9RL882KRfpfpxh7a7zXPogDdR4xj9TpJWJGsbwaodLSKe\"},\"pruned_txo\":{},\"seed_type\":\"standard\",\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[100,100,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_8_3_importedkeys(self):\n        wallet_str = '{\"addr_history\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[]},\"addresses\":{\"change\":[],\"receiving\":[\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\",\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\",\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\"]},\"keystore\":{\"keypairs\":{\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\":\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\",\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\":\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\",\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\":\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"},\"type\":\"imported\"},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[100,100,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_8_3_watchaddresses(self):\n        wallet_str = '{\"addr_history\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[]},\"addresses\":[\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\",\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\",\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\"],\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"verified_tx3\":{},\"wallet_type\":\"imported\",\"winpos-qt\":[535,380,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_8_3_trezor_singleacc(self):\n        wallet_str = '''{\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"addresses\":{\"change\":[\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\",\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\",\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\",\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\",\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\",\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\"],\"receiving\":[\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\",\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\",\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\",\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\",\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\",\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\",\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\",\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\",\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\",\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\",\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\",\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\",\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\",\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\",\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\",\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\",\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\",\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\",\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\",\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\"]},\"keystore\":{\"derivation\":\"m/44'/0'/0'\",\"hw_type\":\"trezor\",\"label\":\"trezor1\",\"type\":\"hardware\",\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[744,390,840,405]}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_8_3_multisig(self):\n        wallet_str = '{\"addr_history\":{\"32Qk6Q7XYD2v3et9g5fA97ky8XRAJNDZCS\":[],\"339axnadPaQg3ngChNBKap2dndUWrSwjk6\":[],\"34FG8qzA6UYLxrnkpVkM9mrGYix3ZyePJZ\":[],\"35CR3h2dFF3EkRX5yK47NGuF2FcLtJvpUM\":[],\"35zrocLBQbHfEqysgv2v5z3RH7BRGQzSMJ\":[],\"36uBJPkgiQwav23ybewbgkQ2zEzJDY2EX1\":[],\"37nSiBvGXm1PNYseymaJn5ERcU4mSMueYc\":[],\"39r4XCmfU4J3N98YQ8Fwvm8VN1Fukfj7QW\":[],\"3BDqFoYMxyy7nWCpRChYV6YCGh9qnWDmav\":[],\"3CGCLSHU8ZjeXv6oukJ3eAQN4fqEQ7wuyX\":[],\"3DCNnfh7oWLsnS3p5QdWfW3hvcFF8qAPFq\":[],\"3DPheE9uany9ET2qBnWF1wh3zDtptGP6Ts\":[],\"3EeNJHgSYVJPxYR2NaYv2M2ZnXkPRWSHQh\":[],\"3FWZ7pJPxZhGr8p6HNr9LLsHA8sABcP7cF\":[],\"3FZbzEF9HdRqzif2cKUFnwW9AFTJcibjVK\":[],\"3GEhQHTrWykC6Jfu923qtpxJECsEGVdhUc\":[],\"3HJ95uxwW6rMoEhYgUfcgpd3ExU3fjkfNb\":[],\"3HbdMVgKRqadNiHRNGizUCyTQYpJ1aXFav\":[],\"3J6xRF9d16QNsvoXkYkeTwTU8L5N3Y8f7c\":[],\"3JBbS3GvhvoLgtLcuMvHCtqjE7dnbpTMkz\":[],\"3KNWZasWDBuVzzp5Y5cbEgjeYn3NKHZKso\":[],\"3KQ5tTEbkQSkKiccKFDPrhLnBjSMey6CQM\":[],\"3KrFHcAzNJYjukGDDZm2HeV5Mok4NGQaD6\":[],\"3LNZbX9wenL3bLxJTQnPidSvVt3EtDrnUg\":[],\"3LzjAqqfiN8w4TSiW8Up7bKLD5CicBUC3a\":[],\"3Nro51wauHugv72NMtY9pmLnwX3FXWU1eE\":[]},\"addresses\":{\"change\":[\"34FG8qzA6UYLxrnkpVkM9mrGYix3ZyePJZ\",\"3LzjAqqfiN8w4TSiW8Up7bKLD5CicBUC3a\",\"3GEhQHTrWykC6Jfu923qtpxJECsEGVdhUc\",\"3Nro51wauHugv72NMtY9pmLnwX3FXWU1eE\",\"3JBbS3GvhvoLgtLcuMvHCtqjE7dnbpTMkz\",\"3CGCLSHU8ZjeXv6oukJ3eAQN4fqEQ7wuyX\"],\"receiving\":[\"35zrocLBQbHfEqysgv2v5z3RH7BRGQzSMJ\",\"3FWZ7pJPxZhGr8p6HNr9LLsHA8sABcP7cF\",\"3DPheE9uany9ET2qBnWF1wh3zDtptGP6Ts\",\"3HbdMVgKRqadNiHRNGizUCyTQYpJ1aXFav\",\"3KQ5tTEbkQSkKiccKFDPrhLnBjSMey6CQM\",\"35CR3h2dFF3EkRX5yK47NGuF2FcLtJvpUM\",\"3HJ95uxwW6rMoEhYgUfcgpd3ExU3fjkfNb\",\"3FZbzEF9HdRqzif2cKUFnwW9AFTJcibjVK\",\"39r4XCmfU4J3N98YQ8Fwvm8VN1Fukfj7QW\",\"3LNZbX9wenL3bLxJTQnPidSvVt3EtDrnUg\",\"32Qk6Q7XYD2v3et9g5fA97ky8XRAJNDZCS\",\"339axnadPaQg3ngChNBKap2dndUWrSwjk6\",\"3EeNJHgSYVJPxYR2NaYv2M2ZnXkPRWSHQh\",\"3BDqFoYMxyy7nWCpRChYV6YCGh9qnWDmav\",\"3DCNnfh7oWLsnS3p5QdWfW3hvcFF8qAPFq\",\"3KNWZasWDBuVzzp5Y5cbEgjeYn3NKHZKso\",\"37nSiBvGXm1PNYseymaJn5ERcU4mSMueYc\",\"3KrFHcAzNJYjukGDDZm2HeV5Mok4NGQaD6\",\"36uBJPkgiQwav23ybewbgkQ2zEzJDY2EX1\",\"3J6xRF9d16QNsvoXkYkeTwTU8L5N3Y8f7c\"]},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":0,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"2of2\",\"winpos-qt\":[671,238,840,405],\"x1/\":{\"seed\":\"property play install hill hunt follow trash comic pulse consider canyon limit\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K46tCjDh5i4H9eSJpnMrYyLUbVZheTbNjiamdxPiffMEYLgxuYsMFokFrNEZ6S6z5wSXXszXaCVQWf6jzZvn14uYZhsnM9Sb\",\"xpub\":\"xpub661MyMwAqRbcGaxfqFE65CDtCU9KBpaQLZQCHx7G1vuibP6nVw2vD9Z2Bz2DsH43bDZGXjmcvx2TD9wq3CmmFcoT96RCiDd1wMSUB2UH7Gu\"},\"x2/\":{\"type\":\"bip32\",\"xprv\":null,\"xpub\":\"xpub661MyMwAqRbcEncvVc1zrPFZSKe7iAP1LTRhzxuXpmztu1kTtnfj8XNFzzmGH1X1gcGxczBZ3MmYKkxXgZKJCsNXXdasNaQJKJE4KcUjn1L\"}}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_9_3_seeded(self):\n        wallet_str = '{\"addr_history\":{\"12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes\":[],\"12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1\":[],\"13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB\":[],\"13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c\":[],\"14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz\":[],\"14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA\":[],\"15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV\":[],\"17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z\":[],\"18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv\":[],\"18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B\":[],\"19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz\":[],\"19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G\":[],\"1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq\":[],\"1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d\":[],\"1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs\":[],\"1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado\":[],\"1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z\":[],\"1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52\":[],\"1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP\":[],\"1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv\":[],\"1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb\":[],\"1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ\":[],\"1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G\":[],\"1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN\":[],\"1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J\":[],\"1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt\":[]},\"addresses\":{\"change\":[\"1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP\",\"1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z\",\"15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV\",\"1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq\",\"19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G\",\"1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb\"],\"receiving\":[\"14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA\",\"13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB\",\"19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz\",\"1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv\",\"1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt\",\"13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c\",\"1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ\",\"12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes\",\"12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1\",\"14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz\",\"1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN\",\"17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z\",\"1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado\",\"18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv\",\"1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G\",\"18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B\",\"1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d\",\"1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs\",\"1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52\",\"1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J\"]},\"keystore\":{\"seed\":\"cereal wise two govern top pet frog nut rule sketch bundle logic\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K29XjRjUs6MnDB9wXjXbJP2kG1fnRk8zjdDYWqVkQYUqaDtgZp5zPSrH5PZQJs8sU25HrUgT1WdgsPU8GbifKurtMYg37d4v\",\"xpub\":\"xpub661MyMwAqRbcEdcCXm1sTViwjBn28zK9kFfrp4C3JUXiW1sfP34f6HA45B9yr7EH5XGzWuTfMTdqpt9XPrVQVUdgiYb5NW9m8ij1FSZgGBF\"},\"pruned_txo\":{},\"seed_type\":\"standard\",\"seed_version\":13,\"stored_height\":-1,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[619,310,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_9_3_importedkeys(self):\n        wallet_str = '{\"addr_history\":{\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\":[],\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\":[],\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\":[]},\"addresses\":{\"change\":[],\"receiving\":[\"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr\",\"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6\",\"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA\"]},\"keystore\":{\"keypairs\":{\"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5\":\"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM\",\"0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f\":\"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U\",\"04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2\":\"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq\"},\"type\":\"imported\"},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":-1,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[100,100,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_9_3_watchaddresses(self):\n        wallet_str = '{\"addr_history\":{\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\":[],\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\":[],\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\":[]},\"addresses\":[\"1H3mPXHFzA8UbvhQVabcDjYw3CPb3djvxs\",\"1HocPduHmQUJerpdaLG8DnmxvnDCVQwWsa\",\"1DgrwN2JCDZ6uPMSvSz8dPeUtaxLxWM2kf\"],\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":490039,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"verified_tx3\":{},\"wallet_type\":\"imported\",\"winpos-qt\":[499,386,840,405]}'\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_9_3_trezor_singleacc(self):\n        wallet_str = '''{\"addr_history\":{\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\":[],\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\":[],\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\":[],\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\":[],\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\":[],\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\":[],\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\":[],\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\":[],\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\":[],\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\":[],\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\":[],\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\":[],\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\":[],\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\":[],\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\":[],\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\":[],\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\":[],\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\":[],\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\":[],\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\":[],\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\":[],\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\":[],\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\":[],\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\":[],\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\":[],\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\":[]},\"addresses\":{\"change\":[\"1ES8hmtgXFLRex71CZHu85cLFRYDczeTZ\",\"14Co2CRVu67XLCGrD4RVVpadtoXcodUUWM\",\"1rDkHFozR7kC7MxRiakx3mBeU1Fu6BRbG\",\"15sFkiVrGad5QiKgtYjfgi8SSeEfRzxed6\",\"1NZs4y3cJhukVdKSYDhaiMHhP4ZU2qVpAL\",\"1KotB3FVFcYuHAVdRNAe2ZN1MREpVWnBgs\"],\"receiving\":[\"1LpV3F25jiNWV8N2RPP1cnKGgpjZh2r8xu\",\"18TKpsznSha4VHLzpVatnrEBdtWkoQSyGw\",\"17YQXYHoDqcpd7GvWN9BYK8FnDryhYbKyH\",\"12sQvVXgdoy2QDorLgr2t6J8JVzygBGueC\",\"15KDqFhdXP6Zn4XtJVVVgahJ7chw9jGhvQ\",\"1Le4rXQD4kMGsoet4EH8VGzt5VZjdHBpid\",\"1KnQX5D5Tv2u5CyWpuXaeM8CvuuVAmfwRz\",\"1MrA1WS4iWcTjLrnSqNNpXzSq5W92Bttbj\",\"146j6RMbWpKYEaGTdWVza3if3bnCD9Maiz\",\"1NMkEhuUYsxTCkfq9zxxCTozKNNqjHeKeC\",\"1Mdq8bVFSBfaeH5vjaXGjiPiy6qPVtdfUo\",\"1BngGArwhpzWjCREXYRS1uhUGszCTe7vqb\",\"1NTRF8Y7Mu57dQ9TFwUA98EdmzbAamtLYe\",\"1NFhYYBh1zDGdnqD1Avo9gaVV8LvnAH6iv\",\"1J2NdSfFiQLhkHs2DVyBmB47Mk65rfrGPp\",\"15zoPN5rVKDCsKnZUkTYJWFv4gLdYTat8S\",\"1E9wSjSWkFJp3HUaUzUF9eWpCkUZnsNCuX\",\"1FdV7zK6RdRAKqg3ccGHGK51nJLUwpuBFp\",\"1GjFaGxzqK12N2F7Ao49k7ZvMApCmK7Enk\",\"1HkHDREiY3m9UCxaSAZEn1troa3eHWaiQD\"]},\"keystore\":{\"derivation\":\"m/44'/0'/0'\",\"hw_type\":\"trezor\",\"label\":\"trezor1\",\"type\":\"hardware\",\"xpub\":\"xpub6BycoSLDNcWjBQMuYgSaEoinupMjma8Cu2uj4XiRCZkecLHXXmzcxbyR1gdfrZpiZDVSs92MEGGNhF78BEbbYi2b5U2oPnaUPRhjriWz85y\"},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":490014,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"standard\",\"winpos-qt\":[753,486,840,405]}'''\n        self._upgrade_storage(wallet_str)\n\n    def test_upgrade_from_client_2_9_3_multisig(self):\n        wallet_str = '{\"addr_history\":{\"31uiqKhw4PQSmZWnCkqpeh6moB8B1jXEt3\":[],\"32PBjkXmwRoEQt8HBZcAEUbNwaHw5dR5fe\":[],\"33FQMD675LMRLZDLYLK7QV6TMYA1uYW1sw\":[],\"33MQEs6TCgxmAJhZvUEXYr6gCkEoEYzUfm\":[],\"33vuhs2Wor9Xkax66ucDkscPcU6nQHw8LA\":[],\"35tbMt1qBGmy5RNcsdGZJgs7XVbf5gEgPs\":[],\"36zhHEtGA33NjHJdxCMjY6DLeU2qxhiLUE\":[],\"37rZuTsieKVpRXshwrY8qvFBn6me42mYr5\":[],\"38A2KDXYRmRKZRRCGgazrj19i22kDr8d4V\":[],\"38GZH5GhxLKi5so9Aka6orY2EDZkvaXdxm\":[],\"3AEtxrCwiYv5Y5CRmHn1c5nZnV3Hpfh5BM\":[],\"3AaHWprY1MytygvQVDLp6i63e9o5CwMSN5\":[],\"3DAD19hHXNxAfZjCtUbWjZVxw1fxQqCbY7\":[],\"3GK4CBbgwumoeR9wxJjr1QnfnYhGUEzHhN\":[],\"3H18xmkyX3XAb5MwucqKpEhTnh3qz8V4Mn\":[],\"3JhkakvHAyFvukJ3cyaVgiyaqjYNo2gmsS\":[],\"3JtA4x1AKW4BR5YAEeLR5D157Nd92NHArC\":[],\"3KQosfGFGsUniyqsidE2Y4Bz1y4iZUkGW6\":[],\"3KXe1z2Lfk22zL6ggQJLpHZfc9dKxYV95p\":[],\"3KZiENj4VHdUycv9UDts4ojVRsaMk8LC5c\":[],\"3KeTKHJbkZN1QVkvKnHRqYDYP7UXsUu6va\":[],\"3L5aZKtDKSd65wPLMRooNtWHkKd5Mz6E3i\":[],\"3LAPqjqW4C2Se9HNziUhNaJQS46X1r9p3M\":[],\"3P3JJPoyNFussuyxkDbnYevYim5XnPGmwZ\":[],\"3PgNdMYSaPRymskby885DgKoTeA1uZr6Gi\":[],\"3Pm7DaUzaDMxy2mW5WzHp1sE9hVWEpdf7J\":[]},\"addresses\":{\"change\":[\"31uiqKhw4PQSmZWnCkqpeh6moB8B1jXEt3\",\"3JhkakvHAyFvukJ3cyaVgiyaqjYNo2gmsS\",\"3GK4CBbgwumoeR9wxJjr1QnfnYhGUEzHhN\",\"3LAPqjqW4C2Se9HNziUhNaJQS46X1r9p3M\",\"33MQEs6TCgxmAJhZvUEXYr6gCkEoEYzUfm\",\"3AEtxrCwiYv5Y5CRmHn1c5nZnV3Hpfh5BM\"],\"receiving\":[\"3P3JJPoyNFussuyxkDbnYevYim5XnPGmwZ\",\"33FQMD675LMRLZDLYLK7QV6TMYA1uYW1sw\",\"3DAD19hHXNxAfZjCtUbWjZVxw1fxQqCbY7\",\"3AaHWprY1MytygvQVDLp6i63e9o5CwMSN5\",\"3H18xmkyX3XAb5MwucqKpEhTnh3qz8V4Mn\",\"36zhHEtGA33NjHJdxCMjY6DLeU2qxhiLUE\",\"37rZuTsieKVpRXshwrY8qvFBn6me42mYr5\",\"38A2KDXYRmRKZRRCGgazrj19i22kDr8d4V\",\"38GZH5GhxLKi5so9Aka6orY2EDZkvaXdxm\",\"33vuhs2Wor9Xkax66ucDkscPcU6nQHw8LA\",\"3L5aZKtDKSd65wPLMRooNtWHkKd5Mz6E3i\",\"3KXe1z2Lfk22zL6ggQJLpHZfc9dKxYV95p\",\"3KQosfGFGsUniyqsidE2Y4Bz1y4iZUkGW6\",\"3KZiENj4VHdUycv9UDts4ojVRsaMk8LC5c\",\"32PBjkXmwRoEQt8HBZcAEUbNwaHw5dR5fe\",\"3KeTKHJbkZN1QVkvKnHRqYDYP7UXsUu6va\",\"3JtA4x1AKW4BR5YAEeLR5D157Nd92NHArC\",\"3PgNdMYSaPRymskby885DgKoTeA1uZr6Gi\",\"3Pm7DaUzaDMxy2mW5WzHp1sE9hVWEpdf7J\",\"35tbMt1qBGmy5RNcsdGZJgs7XVbf5gEgPs\"]},\"pruned_txo\":{},\"seed_version\":13,\"stored_height\":485855,\"transactions\":{},\"tx_fees\":{},\"txi\":{},\"txo\":{},\"use_encryption\":false,\"verified_tx3\":{},\"wallet_type\":\"2of2\",\"winpos-qt\":[617,227,840,405],\"x1/\":{\"seed\":\"speed cruise market wasp ability alarm hold essay grass coconut tissue recipe\",\"type\":\"bip32\",\"xprv\":\"xprv9s21ZrQH143K48ig2wcAuZoEKaYdNRaShKFR3hLrgwsNW13QYRhXH6gAG1khxim6dw2RtAzF8RWbQxr1vvWUJFfEu2SJZhYbv6pfreMpuLB\",\"xpub\":\"xpub661MyMwAqRbcGco98y9BGhjxscP7mtJJ4YB1r5kUFHQMNoNZ5y1mptze7J37JypkbrmBdnqTvSNzxL7cE1FrHg16qoj9S12MUpiYxVbTKQV\"},\"x2/\":{\"type\":\"bip32\",\"xprv\":null,\"xpub\":\"xpub661MyMwAqRbcGrCDZaVs9VC7Z6579tsGvpqyDYZEHKg2MXoDkxhrWoukqvwDPXKdxVkYA6Hv9XHLETptfZfNpcJZmsUThdXXkTNGoBjQv1o\"}}'\n        self._upgrade_storage(wallet_str)\n\n##########\n\n    @classmethod\n    def setUpClass(cls):\n        super().setUpClass()\n        from lib.plugins import Plugins\n        from lib.simple_config import SimpleConfig\n\n        cls.electrum_path = tempfile.mkdtemp()\n        config = SimpleConfig({'electrum_path': cls.electrum_path})\n\n        gui_name = 'cmdline'\n        # TODO it's probably wasteful to load all plugins... only need Trezor\n        Plugins(config, True, gui_name)\n\n    @classmethod\n    def tearDownClass(cls):\n        super().tearDownClass()\n        shutil.rmtree(cls.electrum_path)\n\n    def _upgrade_storage(self, wallet_json, accounts=1):\n        storage = self._load_storage_from_json_string(wallet_json, manual_upgrades=True)\n\n        if accounts == 1:\n            self.assertFalse(storage.requires_split())\n            if storage.requires_upgrade():\n                storage.upgrade()\n                self._sanity_check_upgraded_storage(storage)\n        else:\n            self.assertTrue(storage.requires_split())\n            new_paths = storage.split_accounts()\n            self.assertEqual(accounts, len(new_paths))\n            for new_path in new_paths:\n                new_storage = WalletStorage(new_path, manual_upgrades=False)\n                self._sanity_check_upgraded_storage(new_storage)\n\n    def _sanity_check_upgraded_storage(self, storage):\n        self.assertFalse(storage.requires_split())\n        self.assertFalse(storage.requires_upgrade())\n        w = Wallet(storage)\n\n    def _load_storage_from_json_string(self, wallet_json, manual_upgrades=True):\n        with open(self.wallet_path, \"w\") as f:\n            f.write(wallet_json)\n        storage = WalletStorage(self.wallet_path, manual_upgrades=manual_upgrades)\n        return storage\n"
  },
  {
    "path": "lib/tests/test_transaction.py",
    "content": "import unittest\nfrom lib import transaction\nfrom lib.bitcoin import TYPE_ADDRESS\n\nfrom lib.keystore import xpubkey_to_address\n\nfrom lib.util import bh2u\n\nunsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000005701ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'\nsigned_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'\nv2_blob = \"0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700\"\nsigned_segwit_blob = \"01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000\"\n\nclass TestBCDataStream(unittest.TestCase):\n\n    def test_compact_size(self):\n        s = transaction.BCDataStream()\n        values = [0, 1, 252, 253, 2**16-1, 2**16, 2**32-1, 2**32, 2**64-1]\n        for v in values:\n            s.write_compact_size(v)\n\n        with self.assertRaises(transaction.SerializationError):\n            s.write_compact_size(-1)\n\n        self.assertEqual(bh2u(s.input),\n                          '0001fcfdfd00fdfffffe00000100feffffffffff0000000001000000ffffffffffffffffff')\n        for v in values:\n            self.assertEqual(s.read_compact_size(), v)\n\n        with self.assertRaises(transaction.SerializationError):\n            s.read_compact_size()\n\n    def test_string(self):\n        s = transaction.BCDataStream()\n        with self.assertRaises(transaction.SerializationError):\n            s.read_string()\n\n        msgs = ['Hello', ' ', 'World', '', '!']\n        for msg in msgs:\n            s.write_string(msg)\n        for msg in msgs:\n            self.assertEqual(s.read_string(), msg)\n\n        with self.assertRaises(transaction.SerializationError):\n            s.read_string()\n\n    def test_bytes(self):\n        s = transaction.BCDataStream()\n        s.write(b'foobar')\n        self.assertEqual(s.read_bytes(3), b'foo')\n        self.assertEqual(s.read_bytes(2), b'ba')\n        self.assertEqual(s.read_bytes(4), b'r')\n        self.assertEqual(s.read_bytes(1), b'')\n\nclass TestTransaction(unittest.TestCase):\n\n    def test_tx_unsigned(self):\n        expected = {\n            'inputs': [{\n                'type': 'p2pkh',\n                'address': '1446oU3z268EeFgfcwJv6X2VBXHfoYxfuD',\n                'num_sig': 1,\n                'prevout_hash': '3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a',\n                'prevout_n': 0,\n                'pubkeys': ['02e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6'],\n                'scriptSig': '01ff4c53ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000',\n                'sequence': 4294967295,\n                'signatures': [None],\n                'x_pubkeys': ['ff0488b21e03ef2afea18000000089689bff23e1e7fb2f161daa37270a97a3d8c2e537584b2d304ecb47b86d21fc021b010d3bd425f8cf2e04824bfdf1f1f5ff1d51fadd9a41f9e3fb8dd3403b1bfe00000000']}],\n            'lockTime': 0,\n            'outputs': [{\n                'address': '14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs',\n                'prevout_n': 0,\n                'scriptPubKey': '76a914230ac37834073a42146f11ef8414ae929feaafc388ac',\n                'type': TYPE_ADDRESS,\n                'value': 1000000}],\n                'version': 1\n        }\n        tx = transaction.Transaction(unsigned_blob)\n        self.assertEqual(tx.deserialize(), expected)\n        self.assertEqual(tx.deserialize(), None)\n\n        self.assertEqual(tx.as_dict(), {'hex': unsigned_blob, 'complete': False, 'final': True})\n        self.assertEqual(tx.get_outputs(), [('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs', 1000000)])\n        self.assertEqual(tx.get_output_addresses(), ['14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs'])\n\n        self.assertTrue(tx.has_address('14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs'))\n        self.assertTrue(tx.has_address('1446oU3z268EeFgfcwJv6X2VBXHfoYxfuD'))\n        self.assertFalse(tx.has_address('1CQj15y1N7LDHp7wTt28eoD1QhHgFgxECH'))\n\n        self.assertEqual(tx.serialize(), unsigned_blob)\n\n        tx.update_signatures(signed_blob)\n        self.assertEqual(tx.raw, signed_blob)\n\n        tx.update(unsigned_blob)\n        tx.raw = None\n        blob = str(tx)\n        self.assertEqual(transaction.deserialize(blob), expected)\n\n    def test_tx_signed(self):\n        expected = {\n            'inputs': [{\n                'type': 'p2pkh',\n                'address': '1446oU3z268EeFgfcwJv6X2VBXHfoYxfuD',\n                'num_sig': 1,\n                'prevout_hash': '3140eb24b43386f35ba69e3875eb6c93130ac66201d01c58f598defc949a5c2a',\n                'prevout_n': 0,\n                'pubkeys': ['02e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6'],\n                'scriptSig': '493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6',\n                'sequence': 4294967295,\n                'signatures': ['3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501'],\n                'x_pubkeys': ['02e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6']}],\n            'lockTime': 0,\n            'outputs': [{\n                'address': '14CHYaaByjJZpx4oHBpfDMdqhTyXnZ3kVs',\n                'prevout_n': 0,\n                'scriptPubKey': '76a914230ac37834073a42146f11ef8414ae929feaafc388ac',\n                'type': TYPE_ADDRESS,\n                'value': 1000000}],\n            'version': 1\n        }\n        tx = transaction.Transaction(signed_blob)\n        self.assertEqual(tx.deserialize(), expected)\n        self.assertEqual(tx.deserialize(), None)\n        self.assertEqual(tx.as_dict(), {'hex': signed_blob, 'complete': True, 'final': True})\n\n        self.assertEqual(tx.serialize(), signed_blob)\n\n        tx.update_signatures(signed_blob)\n\n        self.assertEqual(tx.estimated_total_size(), 193)\n        self.assertEqual(tx.estimated_base_size(), 193)\n        self.assertEqual(tx.estimated_witness_size(), 0)\n        self.assertEqual(tx.estimated_weight(), 772)\n        self.assertEqual(tx.estimated_size(), 193)\n\n    def test_estimated_output_size(self):\n        estimated_output_size = transaction.Transaction.estimated_output_size\n        self.assertEqual(estimated_output_size('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), 34)\n        self.assertEqual(estimated_output_size('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 32)\n        self.assertEqual(estimated_output_size('bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af'), 31)\n        self.assertEqual(estimated_output_size('bc1qnvks7gfdu72de8qv6q6rhkkzu70fqz4wpjzuxjf6aydsx7wxfwcqnlxuv3'), 43)\n\n    # TODO other tests for segwit tx\n    def test_tx_signed_segwit(self):\n        tx = transaction.Transaction(signed_segwit_blob)\n\n        self.assertEqual(tx.estimated_total_size(), 222)\n        self.assertEqual(tx.estimated_base_size(), 113)\n        self.assertEqual(tx.estimated_witness_size(), 109)\n        self.assertEqual(tx.estimated_weight(), 561)\n        self.assertEqual(tx.estimated_size(), 141)\n\n    def test_errors(self):\n        with self.assertRaises(TypeError):\n            transaction.Transaction.pay_script(output_type=None, addr='')\n\n        with self.assertRaises(BaseException):\n            xpubkey_to_address('')\n\n    def test_parse_xpub(self):\n        res = xpubkey_to_address('fe4e13b0f311a55b8a5db9a32e959da9f011b131019d4cebe6141b9e2c93edcbfc0954c358b062a9f94111548e50bde5847a3096b8b7872dcffadb0e9579b9017b01000200')\n        self.assertEqual(res, ('04ee98d63800824486a1cf5b4376f2f574d86e0a3009a6448105703453f3368e8e1d8d090aaecdd626a45cc49876709a3bbb6dc96a4311b3cac03e225df5f63dfc', '19h943e4diLc68GXW7G75QNe2KWuMu7BaJ'))\n\n    def test_version_field(self):\n        tx = transaction.Transaction(v2_blob)\n        self.assertEqual(tx.txid(), \"b97f9180173ab141b61b9f944d841e60feec691d6daab4d4d932b24dd36606fe\")\n\n    def test_txid_coinbase_to_p2pk(self):\n        tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4103400d0302ef02062f503253482f522cfabe6d6dd90d39663d10f8fd25ec88338295d4c6ce1c90d4aeb368d8bdbadcc1da3b635801000000000000000474073e03ffffffff013c25cf2d01000000434104b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e6537a576782eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c1e0908ef7bac00000000')\n        self.assertEqual('dbaf14e1c476e76ea05a8b71921a46d6b06f0a950f17c5f9f1a03b8fae467f10', tx.txid())\n\n    def test_txid_coinbase_to_p2pkh(self):\n        tx = transaction.Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff25033ca0030400001256124d696e656420627920425443204775696c640800000d41000007daffffffff01c00d1298000000001976a91427a1f12771de5cc3b73941664b2537c15316be4388ac00000000')\n        self.assertEqual('4328f9311c6defd9ae1bd7f4516b62acf64b361eb39dfcf09d9925c5fd5c61e8', tx.txid())\n\n    def test_txid_segwit_coinbase_to_p2pk(self):\n        tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0240be402500000000232103f4e686cdfc96f375e7c338c40c9b85f4011bb843a3e62e46a1de424ef87e9385ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000')\n        self.assertEqual('fb5a57c24e640a6d8d831eb6e41505f3d54363c507da3733b098d820e3803301', tx.txid())\n\n    def test_txid_segwit_coinbase_to_p2pkh(self):\n        tx = transaction.Transaction('020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c3010101ffffffff0240be4025000000001976a9141ea896d897483e0eb33dd6423f4a07970d0a0a2788ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000')\n        self.assertEqual('ed3d100577477d799107eba97e76770b3efa253c7200e9abfb43da5d2b33513e', tx.txid())\n\n    def test_txid_p2pk_to_p2pkh(self):\n        tx = transaction.Transaction('010000000118231a31d2df84f884ced6af11dc24306319577d4d7c340124a7e2dd9c314077000000004847304402200b6c45891aed48937241907bc3e3868ee4c792819821fcde33311e5a3da4789a02205021b59692b652a01f5f009bd481acac2f647a7d9c076d71d85869763337882e01fdffffff016c95052a010000001976a9149c4891e7791da9e622532c97f43863768264faaf88ac00000000')\n        self.assertEqual('90ba90a5b115106d26663fce6c6215b8699c5d4b2672dd30756115f3337dddf9', tx.txid())\n\n    def test_txid_p2pk_to_p2sh(self):\n        tx = transaction.Transaction('0100000001e4643183d6497823576d17ac2439fb97eba24be8137f312e10fcc16483bb2d070000000048473044022032bbf0394dfe3b004075e3cbb3ea7071b9184547e27f8f73f967c4b3f6a21fa4022073edd5ae8b7b638f25872a7a308bb53a848baa9b9cc70af45fcf3c683d36a55301fdffffff011821814a0000000017a9143c640bc28a346749c09615b50211cb051faff00f8700000000')\n        self.assertEqual('172bdf5a690b874385b98d7ab6f6af807356f03a26033c6a65ab79b4ac2085b5', tx.txid())\n\n    def test_txid_p2pk_to_p2wpkh(self):\n        tx = transaction.Transaction('01000000015e5e2bf15f5793fdfd01e0ccd380033797ed2d4dba9498426ca84904176c26610000000049483045022100c77aff69f7ab4bb148f9bccffc5a87ee893c4f7f7f96c97ba98d2887a0f632b9022046367bdb683d58fa5b2e43cfc8a9c6d57724a27e03583942d8e7b9afbfeea5ab01fdffffff017289824a00000000160014460fc70f208bffa9abf3ae4abbd2f629d9cdcf5900000000')\n        self.assertEqual('ca554b1014952f900aa8cf6e7ab02137a6fdcf933ad6a218de3891a2ef0c350d', tx.txid())\n\n    def test_txid_p2pkh_to_p2pkh(self):\n        tx = transaction.Transaction('0100000001f9dd7d33f315617530dd72264b5d9c69b815626cce3f66266d1015b1a590ba90000000006a4730440220699bfee3d280a499daf4af5593e8750b54fef0557f3c9f717bfa909493a84f60022057718eec7985b7796bb8630bf6ea2e9bf2892ac21bd6ab8f741a008537139ffe012103b4289890b40590447b57f773b5843bf0400e9cead08be225fac587b3c2a8e973fdffffff01ec24052a010000001976a914ce9ff3d15ed5f3a3d94b583b12796d063879b11588ac00000000')\n        self.assertEqual('24737c68f53d4b519939119ed83b2a8d44d716d7f3ca98bcecc0fbb92c2085ce', tx.txid())\n\n    def test_txid_p2pkh_to_p2sh(self):\n        tx = transaction.Transaction('010000000195232c30f6611b9f2f82ec63f5b443b132219c425e1824584411f3d16a7a54bc000000006b4830450221009f39ac457dc8ff316e5cc03161c9eff6212d8694ccb88d801dbb32e85d8ed100022074230bb05e99b85a6a50d2b71e7bf04d80be3f1d014ea038f93943abd79421d101210317be0f7e5478e087453b9b5111bdad586038720f16ac9658fd16217ffd7e5785fdffffff0200e40b540200000017a914d81df3751b9e7dca920678cc19cac8d7ec9010b08718dfd63c2c0000001976a914303c42b63569ff5b390a2016ff44651cd84c7c8988acc7010000')\n        self.assertEqual('155e4740fa59f374abb4e133b87247dccc3afc233cb97c2bf2b46bba3094aedc', tx.txid())\n\n    def test_txid_p2pkh_to_p2wpkh(self):\n        tx = transaction.Transaction('0100000001ce85202cb9fbc0ecbc98caf3d716d7448d2a3bd89e113999514b3df5687c7324000000006b483045022100adab7b6cb1179079c9dfc0021f4db0346730b7c16555fcc4363059dcdd95f653022028bcb816f4fb98615fb8f4b18af3ad3708e2d72f94a6466cc2736055860422cf012102a16a25148dd692462a691796db0a4a5531bcca970a04107bf184a2c9f7fd8b12fdffffff012eb6042a010000001600147d0170de18eecbe84648979d52b666dddee0b47400000000')\n        self.assertEqual('ed29e100499e2a3a64a2b0cb3a68655b9acd690d29690fa541be530462bf3d3c', tx.txid())\n\n    def test_txid_p2sh_to_p2pkh(self):\n        tx = transaction.Transaction('01000000000101f9823f87af35d158e7dc81a67011f4e511e3f6cab07ac108e524b0ff8b950b39000000002322002041f0237866eb72e4a75cd6faf5ccd738703193907d883aa7b3a8169c636706a9fdffffff020065cd1d000000001976a9148150cd6cf729e7e262699875fec1f760b0aab3cc88acc46f9a3b0000000017a91433ccd0f95a7b9d8eef68be40bb59c64d6e14d87287040047304402205ca97126a5956c2deaa956a2006d79a348775d727074a04b71d9c18eb5e5525402207b9353497af15881100a2786adab56c8930c02d46cc1a8b55496c06e22d3459b01483045022100b4fa898057927c2d920ae79bca752dda58202ea8617d3e6ed96cbd5d1c0eb2fc02200824c0e742d1b4d643cec439444f5d8779c18d4f42c2c87cce24044a3babf2df0147522102db78786b3c214826bd27010e3c663b02d67144499611ee3f2461c633eb8f1247210377082028c124098b59a5a1e0ea7fd3ebca72d59c793aecfeedd004304bac15cd52aec9010000')\n        self.assertEqual('17e1d498ba82503e3bfa81ac4897a57e33f3d36b41bcf4765ba604466c478986', tx.txid())\n\n    def test_txid_p2sh_to_p2sh(self):\n        tx = transaction.Transaction('01000000000101b58520acb479ab656a3c03263af0567380aff6b67a8db98543870b695adf2b170000000017160014cfd2b9f7ed9d4d4429ed6946dbb3315f75e85f14fdffffff020065cd1d0000000017a91485f5681bec38f9f07ae9790d7f27c2bb90b5b63c87106ab32c0000000017a914ff402e164dfce874435641ae9ac41fc6fb14c4e18702483045022100b3d1c89c7c92151ed1df78815924569446782776b6a2c170ca5d74c5dd1ad9b102201d7bab1974fd2aa66546dd15c1f1e276d787453cec31b55a2bd97b050abf20140121024a1742ece86df3dbce4717c228cf51e625030cef7f5e6dde33a4fffdd17569eac7010000')\n        self.assertEqual('ead0e7abfb24ddbcd6b89d704d7a6091e43804a458baa930adf6f1cb5b6b42f7', tx.txid())\n\n    def test_txid_p2sh_to_p2wpkh(self):\n        tx = transaction.Transaction('010000000001018689476c4604a65b76f4bc416bd3f3337ea59748ac81fa3b3e5082ba98d4e1170100000023220020ae40340707f9726c0f453c3d47c96e7f3b7b4b85608eb3668b69bbef9c7ab374fdffffff0218b2cc1d0000000017a914f2fdd81e606ff2ab804d7bb46bf8838a711c277b870065cd1d0000000016001496ad8959c1f0382984ecc4da61c118b4c8751e5104004730440220387b9e7d402fbcada9ba55a27a8d0563eafa9904ebd2f8f7e3d86e4b45bc0ec202205f37fa0e2bf8cbd384f804562651d7c6f69adce5db4c1a5b9103250a47f73e6b01473044022074903f4dd4fd6b32289be909eb5109924740daa55e79be6dbd728687683f9afa02205d934d981ca12cbec450611ca81dc4127f8da5e07dd63d41049380502de3f15401475221025c3810b37147105106cef970f9b91d3735819dee4882d515c1187dbd0b8f0c792103e007c492323084f1c103beff255836408af89bb9ae7f2fcf60502c28ff4b0c9152aeca010000')\n        self.assertEqual('6f294c84cbd0241650931b4c1be3dfb2f175d682c7a9538b30b173e1083deed3', tx.txid())\n\n    def test_txid_p2wpkh_to_p2pkh(self):\n        tx = transaction.Transaction('0100000000010197e6bf4a70bc118e3a8d9842ed80422e335679dfc29b5ba0f9123f6a5863b8470000000000fdffffff02402bca7f130000001600146f579c953d9e7e7719f2baa20bde22eb5f24119200e87648170000001976a9140cd8fa5fd81c3acf33f93efd179b388de8dd693388ac0247304402204ff33b3ea8fb270f62409bfc257457ca5eb1fec5e4d3a7c11aa487207e131d4d022032726b998e338e5245746716e5cd0b40d32b69d1535c3d841f049d98a5d819b1012102dc3ce3220363aff579eb2c45c973e8b186a829c987c3caea77c61975666e7d1bc8010000')\n        self.assertEqual('c721ed35767a3a209b688e68e3bb136a72d2b631fe81c56be8bdbb948c343dbc', tx.txid())\n\n    def test_txid_p2wpkh_to_p2sh(self):\n        tx = transaction.Transaction('010000000001013c3dbf620453be41a50f69290d69cd9a5b65683acbb0a2643a2a9e4900e129ed0000000000fdffffff02002f68590000000017a914c7c4dcd0ddf70f15c6df13b4a4d56e9f13c49b2787a0429cd000000000160014e514e3ecf89731e7853e4f3a20983484c569d3910247304402205368cc548209303db5a8f2ebc282bd0f7af0d080ce0f7637758587f94d3971fb0220098cec5752554758bc5fa4de332b980d5e0054a807541581dc5e4de3ed29647501210233717cd73d95acfdf6bd72c4fb5df27cd6bd69ce947daa3f4a442183a97877efc8010000')\n        self.assertEqual('390b958bffb024e508c17ab0caf6e311e5f41170a681dce758d135af873f82f9', tx.txid())\n\n    def test_txid_p2wpkh_to_p2wpkh(self):\n        tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000')\n        self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid())\n\n\nclass NetworkMock(object):\n\n    def __init__(self, unspent):\n        self.unspent = unspent\n\n    def synchronous_get(self, arg):\n        return self.unspent\n"
  },
  {
    "path": "lib/tests/test_util.py",
    "content": "import unittest\nfrom lib.util import format_satoshis, parse_URI\n\nclass TestUtil(unittest.TestCase):\n\n    def test_format_satoshis(self):\n        result = format_satoshis(1234)\n        expected = \"0.00001234\"\n        self.assertEqual(expected, result)\n\n    def test_format_satoshis_diff_positive(self):\n        result = format_satoshis(1234, is_diff=True)\n        expected = \"+0.00001234\"\n        self.assertEqual(expected, result)\n\n    def test_format_satoshis_diff_negative(self):\n        result = format_satoshis(-1234, is_diff=True)\n        expected = \"-0.00001234\"\n        self.assertEqual(expected, result)\n\n    def _do_test_parse_URI(self, uri, expected):\n        result = parse_URI(uri)\n        self.assertEqual(expected, result)\n\n    def test_parse_URI_address(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'})\n\n    def test_parse_URI_only_address(self):\n        self._do_test_parse_URI('15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma'})\n\n\n    def test_parse_URI_address_label(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?label=electrum%20test',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'label': 'electrum test'})\n\n    def test_parse_URI_address_message(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?message=electrum%20test',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'message': 'electrum test', 'memo': 'electrum test'})\n\n    def test_parse_URI_address_amount(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 30000})\n\n    def test_parse_URI_address_request_url(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?r=http://domain.tld/page?h%3D2a8628fc2fbe',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'r': 'http://domain.tld/page?h=2a8628fc2fbe'})\n\n    def test_parse_URI_ignore_args(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?test=test',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'test': 'test'})\n\n    def test_parse_URI_multiple_args(self):\n        self._do_test_parse_URI('bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.00004&label=electrum-test&message=electrum%20test&test=none&r=http://domain.tld/page',\n                                {'address': '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 'amount': 4000, 'label': 'electrum-test', 'message': u'electrum test', 'memo': u'electrum test', 'r': 'http://domain.tld/page', 'test': 'none'})\n\n    def test_parse_URI_no_address_request_url(self):\n        self._do_test_parse_URI('bitcoin:?r=http://domain.tld/page?h%3D2a8628fc2fbe',\n                                {'r': 'http://domain.tld/page?h=2a8628fc2fbe'})\n\n    def test_parse_URI_invalid_address(self):\n        self.assertRaises(BaseException, parse_URI, 'bitcoin:invalidaddress')\n\n    def test_parse_URI_invalid(self):\n        self.assertRaises(BaseException, parse_URI, 'notbitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma')\n\n    def test_parse_URI_parameter_polution(self):\n        self.assertRaises(Exception, parse_URI, 'bitcoin:15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma?amount=0.0003&label=test&amount=30.0')\n\n"
  },
  {
    "path": "lib/tests/test_wallet.py",
    "content": "import shutil\nimport tempfile\nimport sys\nimport unittest\nimport os\nimport json\n\nfrom io import StringIO\nfrom lib.storage import WalletStorage, FINAL_SEED_VERSION\n\n\nclass FakeSynchronizer(object):\n\n    def __init__(self):\n        self.store = []\n\n    def add(self, address):\n        self.store.append(address)\n\n\nclass WalletTestCase(unittest.TestCase):\n\n    def setUp(self):\n        super(WalletTestCase, self).setUp()\n        self.user_dir = tempfile.mkdtemp()\n\n        self.wallet_path = os.path.join(self.user_dir, \"somewallet\")\n\n        self._saved_stdout = sys.stdout\n        self._stdout_buffer = StringIO()\n        sys.stdout = self._stdout_buffer\n\n    def tearDown(self):\n        super(WalletTestCase, self).tearDown()\n        shutil.rmtree(self.user_dir)\n        # Restore the \"real\" stdout\n        sys.stdout = self._saved_stdout\n\n\nclass TestWalletStorage(WalletTestCase):\n\n    def test_read_dictionary_from_file(self):\n\n        some_dict = {\"a\":\"b\", \"c\":\"d\"}\n        contents = json.dumps(some_dict)\n        with open(self.wallet_path, \"w\") as f:\n            contents = f.write(contents)\n\n        storage = WalletStorage(self.wallet_path, manual_upgrades=True)\n        self.assertEqual(\"b\", storage.get(\"a\"))\n        self.assertEqual(\"d\", storage.get(\"c\"))\n\n    def test_write_dictionary_to_file(self):\n\n        storage = WalletStorage(self.wallet_path)\n\n        some_dict = {\n            u\"a\": u\"b\",\n            u\"c\": u\"d\",\n            u\"seed_version\": FINAL_SEED_VERSION}\n\n        for key, value in some_dict.items():\n            storage.put(key, value)\n        storage.write()\n\n        contents = \"\"\n        with open(self.wallet_path, \"r\") as f:\n            contents = f.read()\n        self.assertEqual(some_dict, json.loads(contents))\n"
  },
  {
    "path": "lib/tests/test_wallet_vertical.py",
    "content": "import unittest\nfrom unittest import mock\n\nimport lib.bitcoin as bitcoin\nimport lib.keystore as keystore\nimport lib.storage as storage\nimport lib.wallet as wallet\n\nfrom plugins.trustedcoin import trustedcoin\n\n\n# TODO passphrase/seed_extension\nclass TestWalletKeystoreAddressIntegrity(unittest.TestCase):\n\n    gap_limit = 1  # make tests run faster\n\n    def _check_seeded_keystore_sanity(self, ks):\n        self.assertTrue (ks.is_deterministic())\n        self.assertFalse(ks.is_watching_only())\n        self.assertFalse(ks.can_import())\n        self.assertTrue (ks.has_seed())\n\n    def _check_xpub_keystore_sanity(self, ks):\n        self.assertTrue (ks.is_deterministic())\n        self.assertTrue (ks.is_watching_only())\n        self.assertFalse(ks.can_import())\n        self.assertFalse(ks.has_seed())\n\n    def _create_standard_wallet(self, ks):\n        store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')\n        store.put('keystore', ks.dump())\n        store.put('gap_limit', self.gap_limit)\n        w = wallet.Standard_Wallet(store)\n        w.synchronize()\n        return w\n\n    def _create_multisig_wallet(self, ks1, ks2, ks3=None):\n        \"\"\"Creates a 2-of-2 or 2-of-3 multisig wallet.\"\"\"\n        store = storage.WalletStorage('if_this_exists_mocking_failed_648151893')\n        store.put('x%d/' % 1, ks1.dump())\n        store.put('x%d/' % 2, ks2.dump())\n        if ks3 is None:\n            multisig_type = \"%dof%d\" % (2, 2)\n        else:\n            multisig_type = \"%dof%d\" % (2, 3)\n            store.put('x%d/' % 3, ks3.dump())\n        store.put('wallet_type', multisig_type)\n        store.put('gap_limit', self.gap_limit)\n        w = wallet.Multisig_Wallet(store)\n        w.synchronize()\n        return w\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_seed_standard(self, mock_write):\n        seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'\n        self.assertEqual(bitcoin.seed_type(seed_words), 'standard')\n\n        ks = keystore.from_seed(seed_words, '', False)\n\n        self._check_seeded_keystore_sanity(ks)\n        self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))\n\n        self.assertEqual(ks.xprv, 'xprv9s21ZrQH143K32jECVM729vWgGq4mUDJCk1ozqAStTphzQtCTuoFmFafNoG1g55iCnBTXUzz3zWnDb5CVLGiFvmaZjuazHDL8a81cPQ8KL6')\n        self.assertEqual(ks.xpub, 'xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2pkh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '1NNkttn1YvVGdqBW4PR6zvc3Zx3H5owKRf')\n        self.assertEqual(w.get_change_addresses()[0], '1KSezYMhAJMWqFbVFB2JshYg69UpmEXR4D')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_seed_segwit(self, mock_write):\n        seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'\n        self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')\n\n        ks = keystore.from_seed(seed_words, '', False)\n\n        self._check_seeded_keystore_sanity(ks)\n        self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))\n\n        self.assertEqual(ks.xprv, 'zprvAZswDvNeJeha8qZ8g7efN3FXYVJLaEUsE9TW6qXDEbVe74AZ75c2sZFZXPNFzxnhChDQ89oC8C5AjWwHmH1HeRKE1c4kKBQAmjUDdKDUZw2')\n        self.assertEqual(ks.xpub, 'zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2wpkh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], 'bc1q3g5tmkmlvxryhh843v4dz026avatc0zzr6h3af')\n        self.assertEqual(w.get_change_addresses()[0], 'bc1qdy94n2q5qcp0kg7v9yzwe6wvfkhnvyzje7nx2p')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_seed_old(self, mock_write):\n        seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'\n        self.assertEqual(bitcoin.seed_type(seed_words), 'old')\n\n        ks = keystore.from_seed(seed_words, '', False)\n\n        self._check_seeded_keystore_sanity(ks)\n        self.assertTrue(isinstance(ks, keystore.Old_KeyStore))\n\n        self.assertEqual(ks.mpk, 'e9d4b7866dd1e91c862aebf62a49548c7dbf7bcc6e4b7b8c9da820c7737968df9c09d5a3e271dc814a29981f81b3faaf2737b551ef5dcc6189cf0f8252c442b3')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2pkh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '1FJEEB8ihPMbzs2SkLmr37dHyRFzakqUmo')\n        self.assertEqual(w.get_change_addresses()[0], '1KRW8pH6HFHZh889VDq6fEKvmrsmApwNfe')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_seed_2fa(self, mock_write):\n        seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'\n        self.assertEqual(bitcoin.seed_type(seed_words), '2fa')\n\n        xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')\n\n        ks1 = keystore.from_xprv(xprv1)\n        self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))\n        self.assertEqual(ks1.xprv, 'xprv9uraXy9F3HP7i8QDqwNTBiD8Jf4bPD4Epif8cS8qbUbgeidUesyZpKmzfcSeHutsGfFnjgih7kzwTB5UQVRNB5LoXaNc8pFusKYx3KVVvYR')\n        self.assertEqual(ks1.xpub, 'xpub68qvwUg8sewQvcUgwxuTYr9rrgu5nfn6BwajQpYT9p8fXWxdCRHpN86UWruWJAD1ede8Sv8ERrTa22Gyc4SBfm7zFpcyoVWVBKCVwnw6s1J')\n        self.assertEqual(ks1.xpub, xpub1)\n\n        ks2 = keystore.from_xprv(xprv2)\n        self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))\n        self.assertEqual(ks2.xprv, 'xprv9uraXy9F3HP7kKSiRAvLV7Nrjj7YzspDys7dvGLLu4tLZT49CEBxPWp88dHhVxvZ69SHrPQMUCWjj4Ka2z9kNvs1HAeEf3extGGeSWqEVqf')\n        self.assertEqual(ks2.xpub, 'xpub68qvwUg8sewQxoXBXCTLrFKbHkx3QLY5M63EiejxTQRKSFPHjmWCwK8byvZMM2wZNYA3SmxXoma3M1zxhGESHZwtB7SwrxRgKXAG8dCD2eS')\n        self.assertEqual(ks2.xpub, xpub2)\n\n        long_user_id, short_id = trustedcoin.get_user_id(\n            {'x1/': {'xpub': xpub1},\n             'x2/': {'xpub': xpub2}})\n        xpub3 = trustedcoin.make_xpub(trustedcoin.signing_xpub, long_user_id)\n        ks3 = keystore.from_xpub(xpub3)\n        self._check_xpub_keystore_sanity(ks3)\n        self.assertTrue(isinstance(ks3, keystore.BIP32_KeyStore))\n\n        w = self._create_multisig_wallet(ks1, ks2, ks3)\n        self.assertEqual(w.txin_type, 'p2sh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '35L8XmCDoEBKeaWRjvmZvoZvhp8BXMMMPV')\n        self.assertEqual(w.get_change_addresses()[0], '3PeZEcumRqHSPNN43hd4yskGEBdzXgY8Cy')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_bip39_seed_bip44_standard(self, mock_write):\n        seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'\n        self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))\n\n        ks = keystore.from_bip39_seed(seed_words, '', \"m/44'/0'/0'\")\n\n        self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))\n\n        self.assertEqual(ks.xprv, 'xprv9zGLcNEb3cHUKizLVBz6RYeE9bEZAVPjH2pD1DEzCnPcsemWc3d3xTao8sfhfUmDLMq6e3RcEMEvJG1Et8dvfL8DV4h7mwm9J6AJsW9WXQD')\n        self.assertEqual(ks.xpub, 'xpub6DFh1smUsyqmYD4obDX6ngaxhd53Zx7aeFjoobebm7vbkT6f9awJWFuGzBT9FQJEWFBL7UyhMXtYzRcwDuVbcxtv9Ce2W9eMm4KXLdvdbjv')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2pkh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '16j7Dqk3Z9DdTdBtHcCVLaNQy9MTgywUUo')\n        self.assertEqual(w.get_change_addresses()[0], '1GG5bVeWgAp5XW7JLCphse14QaC4qiHyWn')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_bip39_seed_bip49_p2sh_segwit(self, mock_write):\n        seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'\n        self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))\n\n        ks = keystore.from_bip39_seed(seed_words, '', \"m/49'/0'/0'\")\n\n        self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))\n\n        self.assertEqual(ks.xprv, 'yprvAJEYHeNEPcyBoQYM7sGCxDiNCTX65u4ANgZuSGTrKN5YCC9MP84SBayrgaMyZV7zvkHrr3HVPTK853s2SPk4EttPazBZBmz6QfDkXeE8Zr7')\n        self.assertEqual(ks.xpub, 'ypub6XDth9u8DzXV1tcpDtoDKMf6kVMaVMn1juVWEesTshcX4zUVvfNgjPJLXrD9N7AdTLnbHFL64KmBn3SNaTe69iZYbYCqLCCNPZKbLz9niQ4')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2wpkh-p2sh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W')\n        self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_bip39_seed_bip84_native_segwit(self, mock_write):\n        # test case from bip84\n        seed_words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'\n        self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))\n\n        ks = keystore.from_bip39_seed(seed_words, '', \"m/84'/0'/0'\")\n\n        self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore))\n\n        self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE')\n        self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs')\n\n        w = self._create_standard_wallet(ks)\n        self.assertEqual(w.txin_type, 'p2wpkh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu')\n        self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_multisig_seed_standard(self, mock_write):\n        seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'\n        self.assertEqual(bitcoin.seed_type(seed_words), 'standard')\n\n        ks1 = keystore.from_seed(seed_words, '', True)\n        self._check_seeded_keystore_sanity(ks1)\n        self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))\n        self.assertEqual(ks1.xprv, 'xprv9s21ZrQH143K3t9vo23J3hajRbzvkRLJ6Y1zFrUFAfU3t8oooMPfb7f87cn5KntgqZs5nipZkCiBFo5ZtaSD2eDo7j7CMuFV8Zu6GYLTpY6')\n        self.assertEqual(ks1.xpub, 'xpub661MyMwAqRbcGNEPu3aJQqXTydqR9t49Tkwb4Esrj112kw8xLthv8uybxvaki4Ygt9xiwZUQGeFTG7T2TUzR3eA4Zp3aq5RXsABHFBUrq4c')\n\n        # electrum seed: ghost into match ivory badge robot record tackle radar elbow traffic loud\n        ks2 = keystore.from_xpub('xpub661MyMwAqRbcGfCPEkkyo5WmcrhTq8mi3xuBS7VEZ3LYvsgY1cCFDbenT33bdD12axvrmXhuX3xkAbKci3yZY9ZEk8vhLic7KNhLjqdh5ec')\n        self._check_xpub_keystore_sanity(ks2)\n        self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))\n\n        w = self._create_multisig_wallet(ks1, ks2)\n        self.assertEqual(w.txin_type, 'p2sh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '32ji3QkAgXNz6oFoRfakyD3ys1XXiERQYN')\n        self.assertEqual(w.get_change_addresses()[0], '36XWwEHrrVCLnhjK5MrVVGmUHghr9oWTN1')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_electrum_multisig_seed_segwit(self, mock_write):\n        seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'\n        self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')\n\n        ks1 = keystore.from_seed(seed_words, '', True)\n        self._check_seeded_keystore_sanity(ks1)\n        self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))\n        self.assertEqual(ks1.xprv, 'ZprvAjxLRqPiDfPDxXrm8JvcoCGRAW6xUtktucG6AMtdzaEbTEJN8qcECvujfhtDU3jLJ9g3Dr3Gz5m1ypfMs8iSUh62gWyHZ73bYLRWyeHf6y4')\n        self.assertEqual(ks1.xpub, 'Zpub6xwgqLvc42wXB1wEELTdALD9iXwStMUkGqBgxkJFYumaL2dWgNvUkjEDWyDFZD3fZuDWDzd1KQJ4NwVHS7hs6H6QkpNYSShfNiUZsgMdtNg')\n\n        # electrum seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool\n        ks2 = keystore.from_xpub('Zpub6y4oYeETXAbzLNg45wcFDGwEG3vpgsyMJybiAfi2pJtNF3i3fJVxK2BeZJaw7VeKZm192QHvXP3uHDNpNmNDbQft9FiMzkKUhNXQafUMYUY')\n        self._check_xpub_keystore_sanity(ks2)\n        self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))\n\n        w = self._create_multisig_wallet(ks1, ks2)\n        self.assertEqual(w.txin_type, 'p2wsh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], 'bc1qvzezdcv6vs5h45ugkavp896e0nde5c5lg5h0fwe2xyfhnpkxq6gq7pnwlc')\n        self.assertEqual(w.get_change_addresses()[0], 'bc1qxqf840dqswcmu7a8v82fj6ej0msx08flvuy6kngr7axstjcaq6us9hrehd')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_bip39_multisig_seed_bip45_standard(self, mock_write):\n        seed_words = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial'\n        self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True))\n\n        ks1 = keystore.from_bip39_seed(seed_words, '', \"m/45'/0\")\n        self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))\n        self.assertEqual(ks1.xprv, 'xprv9vyEFyXf7pYVv4eDU3hhuCEAHPHNGuxX73nwtYdpbLcqwJCPwFKknAK8pHWuHHBirCzAPDZ7UJHrYdhLfn1NkGp9rk3rVz2aEqrT93qKRD9')\n        self.assertEqual(ks1.xpub, 'xpub69xafV4YxC6o8Yiga5EiGLAtqR7rgNgNUGiYgw3S9g9pp6XYUne1KxdcfYtxwmA3eBrzMFuYcNQKfqsXCygCo4GxQFHfywxpUbKNfYvGJka')\n\n        # bip39 seed: tray machine cook badge night page project uncover ritual toward person enact\n        # der: m/45'/0\n        ks2 = keystore.from_xpub('xpub6B26nSWddbWv7J3qQn9FbwPPQktSBdPQfLfHhRK4375QoZq8fvM8rQey1koGSTxC5xVoMzNMaBETMUmCqmXzjc8HyAbN7LqrvE4ovGRwNGg')\n        self._check_xpub_keystore_sanity(ks2)\n        self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))\n\n        w = self._create_multisig_wallet(ks1, ks2)\n        self.assertEqual(w.txin_type, 'p2sh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '3JPTQ2nitVxXBJ1yhMeDwH6q417UifE3bN')\n        self.assertEqual(w.get_change_addresses()[0], '3FGyDuxgUDn2pSZe5xAJH1yUwSdhzDMyEE')\n\n    @mock.patch.object(storage.WalletStorage, '_write')\n    def test_bip39_multisig_seed_p2sh_segwit(self, mock_write):\n        # bip39 seed: pulse mixture jazz invite dune enrich minor weapon mosquito flight fly vapor\n        # der: m/49'/0'/0'\n        # NOTE: there is currently no bip43 standard derivation path for p2wsh-p2sh\n        ks1 = keystore.from_xprv('YprvAUXFReVvDjrPerocC3FxVH748sJUTvYjkAhtKop5VnnzVzMEHr1CHrYQKZwfJn1As3X4LYMav6upxd5nDiLb6SCjRZrBH76EFvyQAG4cn79')\n        self.assertTrue(isinstance(ks1, keystore.BIP32_KeyStore))\n        self.assertEqual(ks1.xpub, 'Ypub6hWbqA2p47QgsLt5J4nxrR3ngu8xsPGb7PdV8CDh48KyNngNqPKSqertAqYhQ4umELu1UsZUCYfj9XPA6AdSMZWDZQobwF7EJ8uNrECaZg1')\n\n        # bip39 seed: slab mixture skin evoke harsh tattoo rare crew sphere extend balcony frost\n        # der: m/49'/0'/0'\n        ks2 = keystore.from_xpub('Ypub6iNDhL4WWq5kFZcdFqHHwX4YTH4rYGp8xbndpRrY7WNZFFRfogSrL7wRTajmVHgR46AT1cqUG1mrcRd7h1WXwBsgX2QvT3zFbBCDiSDLkau')\n        self._check_xpub_keystore_sanity(ks2)\n        self.assertTrue(isinstance(ks2, keystore.BIP32_KeyStore))\n\n        w = self._create_multisig_wallet(ks1, ks2)\n        self.assertEqual(w.txin_type, 'p2wsh-p2sh')\n\n        self.assertEqual(w.get_receiving_addresses()[0], '35LeC45QgCVeRor1tJD6LiDgPbybBXisns')\n        self.assertEqual(w.get_change_addresses()[0], '39RhtDchc6igmx5tyoimhojFL1ZbQBrXa6')\n"
  },
  {
    "path": "lib/transaction.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\n\n# Note: The deserialization code originally comes from ABE.\n\nfrom .util import print_error, profiler\n\nfrom . import bitcoin\nfrom .bitcoin import *\nimport struct\n\n#\n# Workalike python implementation of Bitcoin's CDataStream class.\n#\nfrom .keystore import xpubkey_to_address, xpubkey_to_pubkey\n\nNO_SIGNATURE = 'ff'\n\n\nclass SerializationError(Exception):\n    \"\"\" Thrown when there's a problem deserializing or serializing \"\"\"\n\n\nclass BCDataStream(object):\n    def __init__(self):\n        self.input = None\n        self.read_cursor = 0\n\n    def clear(self):\n        self.input = None\n        self.read_cursor = 0\n\n    def write(self, _bytes):  # Initialize with string of _bytes\n        if self.input is None:\n            self.input = bytearray(_bytes)\n        else:\n            self.input += bytearray(_bytes)\n\n    def read_string(self, encoding='ascii'):\n        # Strings are encoded depending on length:\n        # 0 to 252 :  1-byte-length followed by bytes (if any)\n        # 253 to 65,535 : byte'253' 2-byte-length followed by bytes\n        # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes\n        # ... and the Bitcoin client is coded to understand:\n        # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string\n        # ... but I don't think it actually handles any strings that big.\n        if self.input is None:\n            raise SerializationError(\"call write(bytes) before trying to deserialize\")\n\n        length = self.read_compact_size()\n\n        return self.read_bytes(length).decode(encoding)\n\n    def write_string(self, string, encoding='ascii'):\n        string = to_bytes(string, encoding)\n        # Length-encoded as with read-string\n        self.write_compact_size(len(string))\n        self.write(string)\n\n    def read_bytes(self, length):\n        try:\n            result = self.input[self.read_cursor:self.read_cursor+length]\n            self.read_cursor += length\n            return result\n        except IndexError:\n            raise SerializationError(\"attempt to read past end of buffer\")\n\n        return ''\n\n    def read_boolean(self): return self.read_bytes(1)[0] != chr(0)\n    def read_int16(self): return self._read_num('<h')\n    def read_uint16(self): return self._read_num('<H')\n    def read_int32(self): return self._read_num('<i')\n    def read_uint32(self): return self._read_num('<I')\n    def read_int64(self): return self._read_num('<q')\n    def read_uint64(self): return self._read_num('<Q')\n\n    def write_boolean(self, val): return self.write(chr(1) if val else chr(0))\n    def write_int16(self, val): return self._write_num('<h', val)\n    def write_uint16(self, val): return self._write_num('<H', val)\n    def write_int32(self, val): return self._write_num('<i', val)\n    def write_uint32(self, val): return self._write_num('<I', val)\n    def write_int64(self, val): return self._write_num('<q', val)\n    def write_uint64(self, val): return self._write_num('<Q', val)\n\n    def read_compact_size(self):\n        try:\n            size = self.input[self.read_cursor]\n            self.read_cursor += 1\n            if size == 253:\n                size = self._read_num('<H')\n            elif size == 254:\n                size = self._read_num('<I')\n            elif size == 255:\n                size = self._read_num('<Q')\n            return size\n        except IndexError:\n            raise SerializationError(\"attempt to read past end of buffer\")\n\n    def write_compact_size(self, size):\n        if size < 0:\n            raise SerializationError(\"attempt to write size < 0\")\n        elif size < 253:\n            self.write(bytes([size]))\n        elif size < 2**16:\n            self.write(b'\\xfd')\n            self._write_num('<H', size)\n        elif size < 2**32:\n            self.write(b'\\xfe')\n            self._write_num('<I', size)\n        elif size < 2**64:\n            self.write(b'\\xff')\n            self._write_num('<Q', size)\n\n    def _read_num(self, format):\n        try:\n            (i,) = struct.unpack_from(format, self.input, self.read_cursor)\n            self.read_cursor += struct.calcsize(format)\n        except Exception as e:\n            raise SerializationError(e)\n        return i\n\n    def _write_num(self, format, num):\n        s = struct.pack(format, num)\n        self.write(s)\n\n\n# enum-like type\n# From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/\nclass EnumException(Exception):\n    pass\n\n\nclass Enumeration:\n    def __init__(self, name, enumList):\n        self.__doc__ = name\n        lookup = { }\n        reverseLookup = { }\n        i = 0\n        uniqueNames = [ ]\n        uniqueValues = [ ]\n        for x in enumList:\n            if isinstance(x, tuple):\n                x, i = x\n            if not isinstance(x, str):\n                raise EnumException(\"enum name is not a string: \" + x)\n            if not isinstance(i, int):\n                raise EnumException(\"enum value is not an integer: \" + i)\n            if x in uniqueNames:\n                raise EnumException(\"enum name is not unique: \" + x)\n            if i in uniqueValues:\n                raise EnumException(\"enum value is not unique for \" + x)\n            uniqueNames.append(x)\n            uniqueValues.append(i)\n            lookup[x] = i\n            reverseLookup[i] = x\n            i = i + 1\n        self.lookup = lookup\n        self.reverseLookup = reverseLookup\n\n    def __getattr__(self, attr):\n        if attr not in self.lookup:\n            raise AttributeError\n        return self.lookup[attr]\n    def whatis(self, value):\n        return self.reverseLookup[value]\n\n\n# This function comes from bitcointools, bct-LICENSE.txt.\ndef long_hex(bytes):\n    return bytes.encode('hex_codec')\n\n# This function comes from bitcointools, bct-LICENSE.txt.\ndef short_hex(bytes):\n    t = bytes.encode('hex_codec')\n    if len(t) < 11:\n        return t\n    return t[0:4]+\"...\"+t[-4:]\n\n\n\nopcodes = Enumeration(\"Opcodes\", [\n    (\"OP_0\", 0), (\"OP_PUSHDATA1\",76), \"OP_PUSHDATA2\", \"OP_PUSHDATA4\", \"OP_1NEGATE\", \"OP_RESERVED\",\n    \"OP_1\", \"OP_2\", \"OP_3\", \"OP_4\", \"OP_5\", \"OP_6\", \"OP_7\",\n    \"OP_8\", \"OP_9\", \"OP_10\", \"OP_11\", \"OP_12\", \"OP_13\", \"OP_14\", \"OP_15\", \"OP_16\",\n    \"OP_NOP\", \"OP_VER\", \"OP_IF\", \"OP_NOTIF\", \"OP_VERIF\", \"OP_VERNOTIF\", \"OP_ELSE\", \"OP_ENDIF\", \"OP_VERIFY\",\n    \"OP_RETURN\", \"OP_TOALTSTACK\", \"OP_FROMALTSTACK\", \"OP_2DROP\", \"OP_2DUP\", \"OP_3DUP\", \"OP_2OVER\", \"OP_2ROT\", \"OP_2SWAP\",\n    \"OP_IFDUP\", \"OP_DEPTH\", \"OP_DROP\", \"OP_DUP\", \"OP_NIP\", \"OP_OVER\", \"OP_PICK\", \"OP_ROLL\", \"OP_ROT\",\n    \"OP_SWAP\", \"OP_TUCK\", \"OP_CAT\", \"OP_SUBSTR\", \"OP_LEFT\", \"OP_RIGHT\", \"OP_SIZE\", \"OP_INVERT\", \"OP_AND\",\n    \"OP_OR\", \"OP_XOR\", \"OP_EQUAL\", \"OP_EQUALVERIFY\", \"OP_RESERVED1\", \"OP_RESERVED2\", \"OP_1ADD\", \"OP_1SUB\", \"OP_2MUL\",\n    \"OP_2DIV\", \"OP_NEGATE\", \"OP_ABS\", \"OP_NOT\", \"OP_0NOTEQUAL\", \"OP_ADD\", \"OP_SUB\", \"OP_MUL\", \"OP_DIV\",\n    \"OP_MOD\", \"OP_LSHIFT\", \"OP_RSHIFT\", \"OP_BOOLAND\", \"OP_BOOLOR\",\n    \"OP_NUMEQUAL\", \"OP_NUMEQUALVERIFY\", \"OP_NUMNOTEQUAL\", \"OP_LESSTHAN\",\n    \"OP_GREATERTHAN\", \"OP_LESSTHANOREQUAL\", \"OP_GREATERTHANOREQUAL\", \"OP_MIN\", \"OP_MAX\",\n    \"OP_WITHIN\", \"OP_RIPEMD160\", \"OP_SHA1\", \"OP_SHA256\", \"OP_HASH160\",\n    \"OP_HASH256\", \"OP_CODESEPARATOR\", \"OP_CHECKSIG\", \"OP_CHECKSIGVERIFY\", \"OP_CHECKMULTISIG\",\n    \"OP_CHECKMULTISIGVERIFY\",\n    (\"OP_SINGLEBYTE_END\", 0xF0),\n    (\"OP_DOUBLEBYTE_BEGIN\", 0xF000),\n    \"OP_PUBKEY\", \"OP_PUBKEYHASH\",\n    (\"OP_INVALIDOPCODE\", 0xFFFF),\n])\n\n\ndef script_GetOp(_bytes):\n    i = 0\n    while i < len(_bytes):\n        vch = None\n        opcode = _bytes[i]\n        i += 1\n        if opcode >= opcodes.OP_SINGLEBYTE_END:\n            opcode <<= 8\n            opcode |= _bytes[i]\n            i += 1\n\n        if opcode <= opcodes.OP_PUSHDATA4:\n            nSize = opcode\n            if opcode == opcodes.OP_PUSHDATA1:\n                nSize = _bytes[i]\n                i += 1\n            elif opcode == opcodes.OP_PUSHDATA2:\n                (nSize,) = struct.unpack_from('<H', _bytes, i)\n                i += 2\n            elif opcode == opcodes.OP_PUSHDATA4:\n                (nSize,) = struct.unpack_from('<I', _bytes, i)\n                i += 4\n            vch = _bytes[i:i + nSize]\n            i += nSize\n\n        yield opcode, vch, i\n\n\ndef script_GetOpName(opcode):\n    return (opcodes.whatis(opcode)).replace(\"OP_\", \"\")\n\n\ndef decode_script(bytes):\n    result = ''\n    for (opcode, vch, i) in script_GetOp(bytes):\n        if len(result) > 0: result += \" \"\n        if opcode <= opcodes.OP_PUSHDATA4:\n            result += \"%d:\"%(opcode,)\n            result += short_hex(vch)\n        else:\n            result += script_GetOpName(opcode)\n    return result\n\n\ndef match_decoded(decoded, to_match):\n    if len(decoded) != len(to_match):\n        return False;\n    for i in range(len(decoded)):\n        if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4 and decoded[i][0]>0:\n            continue  # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.\n        if to_match[i] != decoded[i][0]:\n            return False\n    return True\n\n\ndef parse_sig(x_sig):\n    return [None if x == NO_SIGNATURE else x for x in x_sig]\n\ndef safe_parse_pubkey(x):\n    try:\n        return xpubkey_to_pubkey(x)\n    except:\n        return x\n\ndef parse_scriptSig(d, _bytes):\n    try:\n        decoded = [ x for x in script_GetOp(_bytes) ]\n    except Exception as e:\n        # coinbase transactions raise an exception\n        print_error(\"cannot find address in input script\", bh2u(_bytes))\n        return\n\n    match = [ opcodes.OP_PUSHDATA4 ]\n    if match_decoded(decoded, match):\n        item = decoded[0][1]\n        if item[0] == 0:\n            d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))\n            d['type'] = 'p2wpkh-p2sh' if len(item) == 22 else 'p2wsh-p2sh'\n        else:\n            # payto_pubkey\n            d['type'] = 'p2pk'\n            d['address'] = \"(pubkey)\"\n            d['signatures'] = [bh2u(item)]\n            d['num_sig'] = 1\n            d['x_pubkeys'] = [\"(pubkey)\"]\n            d['pubkeys'] = [\"(pubkey)\"]\n        return\n\n    # non-generated TxIn transactions push a signature\n    # (seventy-something bytes) and then their public key\n    # (65 bytes) onto the stack:\n    match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ]\n    if match_decoded(decoded, match):\n        sig = bh2u(decoded[0][1])\n        x_pubkey = bh2u(decoded[1][1])\n        try:\n            signatures = parse_sig([sig])\n            pubkey, address = xpubkey_to_address(x_pubkey)\n        except:\n            print_error(\"cannot find address in input script\", bh2u(_bytes))\n            return\n        d['type'] = 'p2pkh'\n        d['signatures'] = signatures\n        d['x_pubkeys'] = [x_pubkey]\n        d['num_sig'] = 1\n        d['pubkeys'] = [pubkey]\n        d['address'] = address\n        return\n\n    # p2sh transaction, m of n\n    match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)\n    if not match_decoded(decoded, match):\n        print_error(\"cannot find address in input script\", bh2u(_bytes))\n        return\n    x_sig = [bh2u(x[1]) for x in decoded[1:-1]]\n    m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1])\n    # write result in d\n    d['type'] = 'p2sh'\n    d['num_sig'] = m\n    d['signatures'] = parse_sig(x_sig)\n    d['x_pubkeys'] = x_pubkeys\n    d['pubkeys'] = pubkeys\n    d['redeemScript'] = redeemScript\n    d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript)))\n\n\ndef parse_redeemScript(s):\n    dec2 = [ x for x in script_GetOp(s) ]\n    m = dec2[0][0] - opcodes.OP_1 + 1\n    n = dec2[-2][0] - opcodes.OP_1 + 1\n    op_m = opcodes.OP_1 + m - 1\n    op_n = opcodes.OP_1 + n - 1\n    match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]\n    if not match_decoded(dec2, match_multisig):\n        print_error(\"cannot find address in input script\", bh2u(s))\n        return\n    x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]\n    pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]\n    redeemScript = multisig_script(pubkeys, m)\n    return m, n, x_pubkeys, pubkeys, redeemScript\n\ndef get_address_from_output_script(_bytes):\n    decoded = [x for x in script_GetOp(_bytes)]\n\n    # The Genesis Block, self-payments, and pay-by-IP-address payments look like:\n    # 65 BYTES:... CHECKSIG\n    match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ]\n    if match_decoded(decoded, match):\n        return TYPE_PUBKEY, bh2u(decoded[0][1])\n\n    # Pay-by-Bitcoin-address TxOuts look like:\n    # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG\n    match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]\n    if match_decoded(decoded, match):\n        return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1])\n\n    # p2sh\n    match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ]\n    if match_decoded(decoded, match):\n        return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1])\n\n    # segwit address\n    match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4 ]\n    if match_decoded(decoded, match):\n        return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1])\n\n    return TYPE_SCRIPT, bh2u(_bytes)\n\n\ndef parse_input(vds):\n    d = {}\n    prevout_hash = hash_encode(vds.read_bytes(32))\n    prevout_n = vds.read_uint32()\n    scriptSig = vds.read_bytes(vds.read_compact_size())\n    sequence = vds.read_uint32()\n    d['prevout_hash'] = prevout_hash\n    d['prevout_n'] = prevout_n\n    d['sequence'] = sequence\n    if prevout_hash == '00'*32:\n        d['type'] = 'coinbase'\n        d['scriptSig'] = bh2u(scriptSig)\n    else:\n        d['x_pubkeys'] = []\n        d['pubkeys'] = []\n        d['signatures'] = {}\n        d['address'] = None\n        d['type'] = 'unknown'\n        d['num_sig'] = 0\n        if scriptSig:\n            d['scriptSig'] = bh2u(scriptSig)\n            parse_scriptSig(d, scriptSig)\n        else:\n            d['scriptSig'] = ''\n\n    return d\n\n\ndef parse_witness(vds, txin):\n    n = vds.read_compact_size()\n    if n == 0:\n        return\n    if n == 0xffffffff:\n        txin['value'] = vds.read_uint64()\n        n = vds.read_compact_size()\n    w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))\n    if txin['type'] == 'coinbase':\n        pass\n    elif n > 2:\n        txin['signatures'] = parse_sig(w[1:-1])\n        m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))\n        txin['num_sig'] = m\n        txin['x_pubkeys'] = x_pubkeys\n        txin['pubkeys'] = pubkeys\n        txin['witnessScript'] = witnessScript\n    else:\n        txin['num_sig'] = 1\n        txin['x_pubkeys'] = [w[1]]\n        txin['pubkeys'] = [safe_parse_pubkey(w[1])]\n        txin['signatures'] = parse_sig([w[0]])\n\ndef parse_output(vds, i):\n    d = {}\n    d['value'] = vds.read_int64()\n    scriptPubKey = vds.read_bytes(vds.read_compact_size())\n    d['type'], d['address'] = get_address_from_output_script(scriptPubKey)\n    d['scriptPubKey'] = bh2u(scriptPubKey)\n    d['prevout_n'] = i\n    return d\n\n\ndef deserialize(raw):\n    vds = BCDataStream()\n    vds.write(bfh(raw))\n    d = {}\n    start = vds.read_cursor\n    d['version'] = vds.read_int32()\n    n_vin = vds.read_compact_size()\n    d['inputs'] = [parse_input(vds) for i in range(n_vin)]\n    n_vout = vds.read_compact_size()\n    d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]\n    d['lockTime'] = vds.read_uint32()\n    return d\n\n\n# pay & redeem scripts\n\n\n\ndef multisig_script(public_keys, m):\n    n = len(public_keys)\n    assert n <= 15\n    assert m <= n\n    op_m = format(opcodes.OP_1 + m - 1, 'x')\n    op_n = format(opcodes.OP_1 + n - 1, 'x')\n    keylist = [op_push(len(k)//2) + k for k in public_keys]\n    return op_m + ''.join(keylist) + op_n + 'ae'\n\n\n\n\nclass Transaction:\n\n    def __str__(self):\n        if self.raw is None:\n            self.raw = self.serialize()\n        return self.raw\n\n    def __init__(self, raw):\n        if raw is None:\n            self.raw = None\n        elif isinstance(raw, str):\n            self.raw = raw.strip() if raw else None\n        elif isinstance(raw, dict):\n            self.raw = raw['hex']\n        else:\n            raise BaseException(\"cannot initialize transaction\", raw)\n        self._inputs = None\n        self._outputs = None\n        self.locktime = 0\n        self.version = 1\n\n    def update(self, raw):\n        self.raw = raw\n        self._inputs = None\n        self.deserialize()\n\n    def inputs(self):\n        if self._inputs is None:\n            self.deserialize()\n        return self._inputs\n\n    def outputs(self):\n        if self._outputs is None:\n            self.deserialize()\n        return self._outputs\n\n    @classmethod\n    def get_sorted_pubkeys(self, txin):\n        # sort pubkeys and x_pubkeys, using the order of pubkeys\n        x_pubkeys = txin['x_pubkeys']\n        pubkeys = txin.get('pubkeys')\n        if pubkeys is None:\n            pubkeys = [xpubkey_to_pubkey(x) for x in x_pubkeys]\n            pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys)))\n            txin['pubkeys'] = pubkeys = list(pubkeys)\n            txin['x_pubkeys'] = x_pubkeys = list(x_pubkeys)\n        return pubkeys, x_pubkeys\n\n    def update_signatures(self, raw):\n        \"\"\"Add new signatures to a transaction\"\"\"\n        d = deserialize(raw)\n        for i, txin in enumerate(self.inputs()):\n            pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)\n            sigs1 = txin.get('signatures')\n            sigs2 = d['inputs'][i].get('signatures')\n            for sig in sigs2:\n                if sig in sigs1:\n                    continue\n                pre_hash = Hash(bfh(self.serialize_preimage(i)))\n                # der to string\n                order = ecdsa.ecdsa.generator_secp256k1.order()\n                r, s = ecdsa.util.sigdecode_der(bfh(sig[:-2]), order)\n                sig_string = ecdsa.util.sigencode_string(r, s, order)\n                compressed = True\n                for recid in range(4):\n                    public_key = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve = SECP256k1)\n                    pubkey = bh2u(point_to_ser(public_key.pubkey.point, compressed))\n                    if pubkey in pubkeys:\n                        public_key.verify_digest(sig_string, pre_hash, sigdecode = ecdsa.util.sigdecode_string)\n                        j = pubkeys.index(pubkey)\n                        print_error(\"adding sig\", i, j, pubkey, sig)\n                        self._inputs[i]['signatures'][j] = sig\n                        #self._inputs[i]['x_pubkeys'][j] = pubkey\n                        break\n        # redo raw\n        self.raw = self.serialize()\n\n    def deserialize(self):\n        if self.raw is None:\n            return\n            #self.raw = self.serialize()\n        if self._inputs is not None:\n            return\n        d = deserialize(self.raw)\n        self._inputs = d['inputs']\n        self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]\n        self.locktime = d['lockTime']\n        self.version = d['version']\n        return d\n\n    @classmethod\n    def from_io(klass, inputs, outputs, locktime=0):\n        self = klass(None)\n        self._inputs = inputs\n        self._outputs = outputs\n        self.locktime = locktime\n        return self\n\n    @classmethod\n    def pay_script(self, output_type, addr):\n        if output_type == TYPE_SCRIPT:\n            return addr\n        elif output_type == TYPE_ADDRESS:\n            return bitcoin.address_to_script(addr)\n        elif output_type == TYPE_PUBKEY:\n            return bitcoin.public_key_to_p2pk_script(addr)\n        else:\n            raise TypeError('Unknown output type')\n\n    @classmethod\n    def estimate_pubkey_size_from_x_pubkey(cls, x_pubkey):\n        try:\n            if x_pubkey[0:2] in ['02', '03']:  # compressed pubkey\n                return 0x21\n            elif x_pubkey[0:2] == '04':  # uncompressed pubkey\n                return 0x41\n            elif x_pubkey[0:2] == 'ff':  # bip32 extended pubkey\n                return 0x21\n            elif x_pubkey[0:2] == 'fe':  # old electrum extended pubkey\n                return 0x41\n        except Exception as e:\n            pass\n        return 0x21  # just guess it is compressed\n\n    @classmethod\n    def estimate_pubkey_size_for_txin(cls, txin):\n        pubkeys = txin.get('pubkeys', [])\n        x_pubkeys = txin.get('x_pubkeys', [])\n        if pubkeys and len(pubkeys) > 0:\n            return cls.estimate_pubkey_size_from_x_pubkey(pubkeys[0])\n        elif x_pubkeys and len(x_pubkeys) > 0:\n            return cls.estimate_pubkey_size_from_x_pubkey(x_pubkeys[0])\n        else:\n            return 0x21  # just guess it is compressed\n\n    @classmethod\n    def get_siglist(self, txin, estimate_size=False):\n        # if we have enough signatures, we use the actual pubkeys\n        # otherwise, use extended pubkeys (with bip32 derivation)\n        num_sig = txin.get('num_sig', 1)\n        if estimate_size:\n            pubkey_size = self.estimate_pubkey_size_for_txin(txin)\n            pk_list = [\"00\" * pubkey_size] * len(txin.get('x_pubkeys', [None]))\n            # we assume that signature will be 0x48 bytes long\n            sig_list = [ \"00\" * 0x48 ] * num_sig\n        else:\n            pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)\n            x_signatures = txin['signatures']\n            signatures = list(filter(None, x_signatures))\n            is_complete = len(signatures) == num_sig\n            if is_complete:\n                pk_list = pubkeys\n                sig_list = signatures\n            else:\n                pk_list = x_pubkeys\n                sig_list = [sig if sig else NO_SIGNATURE for sig in x_signatures]\n        return pk_list, sig_list\n\n    @classmethod\n    def serialize_witness(self, txin, estimate_size=False):\n        add_w = lambda x: var_int(len(x)//2) + x\n        if not self.is_segwit_input(txin):\n            return '00'\n        pubkeys, sig_list = self.get_siglist(txin, estimate_size)\n        if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:\n            witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0])\n        elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']:\n            n = len(sig_list) + 2\n            witness_script = multisig_script(pubkeys, txin['num_sig'])\n            witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script)\n        else:\n            raise BaseException('wrong txin type')\n        if self.is_txin_complete(txin) or estimate_size:\n            value_field = ''\n        else:\n            value_field = var_int(0xffffffff) + int_to_hex(txin['value'], 8)\n        return value_field + witness\n\n    @classmethod\n    def is_segwit_input(cls, txin):\n        return cls.is_segwit_inputtype(txin['type'])\n\n    @classmethod\n    def is_segwit_inputtype(cls, txin_type):\n        return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')\n\n    @classmethod\n    def input_script(self, txin, estimate_size=False):\n        _type = txin['type']\n        if _type == 'coinbase':\n            return txin['scriptSig']\n        pubkeys, sig_list = self.get_siglist(txin, estimate_size)\n        script = ''.join(push_script(x) for x in sig_list)\n        if _type == 'p2pk':\n            pass\n        elif _type == 'p2sh':\n            # put op_0 before script\n            script = '00' + script\n            redeem_script = multisig_script(pubkeys, txin['num_sig'])\n            script += push_script(redeem_script)\n        elif _type == 'p2pkh':\n            script += push_script(pubkeys[0])\n        elif _type in ['p2wpkh', 'p2wsh']:\n            return ''\n        elif _type == 'p2wpkh-p2sh':\n            pubkey = safe_parse_pubkey(pubkeys[0])\n            scriptSig = bitcoin.p2wpkh_nested_script(pubkey)\n            return push_script(scriptSig)\n        elif _type == 'p2wsh-p2sh':\n            witness_script = self.get_preimage_script(txin)\n            scriptSig = bitcoin.p2wsh_nested_script(witness_script)\n            return push_script(scriptSig)\n        elif _type == 'address':\n            script += push_script(pubkeys[0])\n        elif _type == 'unknown':\n            return txin['scriptSig']\n        return script\n\n    @classmethod\n    def is_txin_complete(self, txin):\n        num_sig = txin.get('num_sig', 1)\n        x_signatures = txin['signatures']\n        signatures = list(filter(None, x_signatures))\n        return len(signatures) == num_sig\n\n    @classmethod\n    def get_preimage_script(self, txin):\n        # only for non-segwit\n        if txin['type'] == 'p2pkh':\n            return bitcoin.address_to_script(txin['address'])\n        elif txin['type'] in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:\n            pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)\n            return multisig_script(pubkeys, txin['num_sig'])\n        elif txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:\n            pubkey = txin['pubkeys'][0]\n            pkh = bh2u(bitcoin.hash_160(bfh(pubkey)))\n            return '76a9' + push_script(pkh) + '88ac'\n        elif txin['type'] == 'p2pk':\n            pubkey = txin['pubkeys'][0]\n            return bitcoin.public_key_to_p2pk_script(pubkey)\n        else:\n            raise TypeError('Unknown txin type', txin['type'])\n\n    @classmethod\n    def serialize_outpoint(self, txin):\n        return bh2u(bfh(txin['prevout_hash'])[::-1]) + int_to_hex(txin['prevout_n'], 4)\n\n    @classmethod\n    def serialize_input(self, txin, script):\n        # Prev hash and index\n        s = self.serialize_outpoint(txin)\n        # Script length, script, sequence\n        s += var_int(len(script)//2)\n        s += script\n        s += int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)\n        return s\n\n    def set_rbf(self, rbf):\n        nSequence = 0xffffffff - (2 if rbf else 1)\n        for txin in self.inputs():\n            txin['sequence'] = nSequence\n\n    def BIP_LI01_sort(self):\n        # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki\n        self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))\n        self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))\n\n    def serialize_output(self, output):\n        output_type, addr, amount = output\n        s = int_to_hex(amount, 8)\n        script = self.pay_script(output_type, addr)\n        s += var_int(len(script)//2)\n        s += script\n        return s\n\n    def serialize_preimage(self, i):\n        nVersion = int_to_hex(self.version, 4)\n        nHashType = 1 | 0x40\n        nHashType = nHashType | (42 << 8)\n        nHashType = int_to_hex(nHashType, 4)\n        nLocktime = int_to_hex(self.locktime, 4)\n        inputs = self.inputs()\n        outputs = self.outputs()\n        txin = inputs[i]\n        # TODO: py3 hex\n        if self.is_segwit_input(txin):\n            hashPrevouts = bh2u(Hash(bfh(''.join(self.serialize_outpoint(txin) for txin in inputs))))\n            hashSequence = bh2u(Hash(bfh(''.join(int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) for txin in inputs))))\n            hashOutputs = bh2u(Hash(bfh(''.join(self.serialize_output(o) for o in outputs))))\n            outpoint = self.serialize_outpoint(txin)\n            preimage_script = self.get_preimage_script(txin)\n            scriptCode = var_int(len(preimage_script) // 2) + preimage_script\n            amount = int_to_hex(txin['value'], 8)\n            nSequence = int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)\n            preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType\n        else:\n            txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs))\n            txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)\n            preimage = nVersion + txins + txouts + nLocktime + nHashType\n        return preimage\n\n    def is_segwit(self):\n        return any(self.is_segwit_input(x) for x in self.inputs())\n\n    def serialize(self, estimate_size=False, witness=True):\n        nVersion = int_to_hex(self.version, 4)\n        nLocktime = int_to_hex(self.locktime, 4)\n        inputs = self.inputs()\n        outputs = self.outputs()\n        txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)\n        txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)\n        if witness and self.is_segwit():\n            marker = '00'\n            flag = '01'\n            witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)\n            return nVersion + marker + flag + txins + txouts + witness + nLocktime\n        else:\n            return nVersion + txins + txouts + nLocktime\n\n    def hash(self):\n        print(\"warning: deprecated tx.hash()\")\n        return self.txid()\n\n    def txid(self):\n        all_segwit = all(self.is_segwit_input(x) for x in self.inputs())\n        if not all_segwit and not self.is_complete():\n            return None\n        ser = self.serialize(witness=False)\n        return bh2u(Hash(bfh(ser))[::-1])\n\n    def wtxid(self):\n        ser = self.serialize(witness=True)\n        return bh2u(Hash(bfh(ser))[::-1])\n\n    def add_inputs(self, inputs):\n        self._inputs.extend(inputs)\n        self.raw = None\n\n    def add_outputs(self, outputs):\n        self._outputs.extend(outputs)\n        self.raw = None\n\n    def input_value(self):\n        return sum(x['value'] for x in self.inputs())\n\n    def output_value(self):\n        return sum(val for tp, addr, val in self.outputs())\n\n    def get_fee(self):\n        return self.input_value() - self.output_value()\n\n    def is_final(self):\n        return not any([x.get('sequence', 0xffffffff - 1) < 0xffffffff - 1 for x in self.inputs()])\n\n    @profiler\n    def estimated_size(self):\n        \"\"\"Return an estimated virtual tx size in vbytes.\n        BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up.\n        This definition is only for humans, and has little meaning otherwise.\n        If we wanted sub-byte precision, fee calculation should use transaction\n        weights, but for simplicity we approximate that with (virtual_size)x4\n        \"\"\"\n        weight = self.estimated_weight()\n        return self.virtual_size_from_weight(weight)\n\n    @classmethod\n    def estimated_input_weight(cls, txin, is_segwit_tx):\n        '''Return an estimate of serialized input weight in weight units.'''\n        script = cls.input_script(txin, True)\n        input_size = len(cls.serialize_input(txin, script)) // 2\n\n        if cls.is_segwit_input(txin):\n            assert is_segwit_tx\n            witness_size = len(cls.serialize_witness(txin, True)) // 2\n        else:\n            witness_size = 1 if is_segwit_tx else 0\n\n        return 4 * input_size + witness_size\n\n    @classmethod\n    def estimated_output_size(cls, address):\n        \"\"\"Return an estimate of serialized output size in bytes.\"\"\"\n        script = bitcoin.address_to_script(address)\n        # 8 byte value + 1 byte script len + script\n        return 9 + len(script) // 2\n\n    @classmethod\n    def virtual_size_from_weight(cls, weight):\n        return weight // 4 + (weight % 4 > 0)\n\n    def estimated_total_size(self):\n        \"\"\"Return an estimated total transaction size in bytes.\"\"\"\n        return len(self.serialize(True)) // 2 if not self.is_complete() or self.raw is None else len(self.raw) // 2  # ASCII hex string\n\n    def estimated_witness_size(self):\n        \"\"\"Return an estimate of witness size in bytes.\"\"\"\n        if not self.is_segwit():\n            return 0\n        inputs = self.inputs()\n        estimate = not self.is_complete()\n        witness = ''.join(self.serialize_witness(x, estimate) for x in inputs)\n        witness_size = len(witness) // 2 + 2  # include marker and flag\n        return witness_size\n\n    def estimated_base_size(self):\n        \"\"\"Return an estimated base transaction size in bytes.\"\"\"\n        return self.estimated_total_size() - self.estimated_witness_size()\n\n    def estimated_weight(self):\n        \"\"\"Return an estimate of transaction weight.\"\"\"\n        total_tx_size = self.estimated_total_size()\n        base_tx_size = self.estimated_base_size()\n        return 3 * base_tx_size + total_tx_size\n\n    def signature_count(self):\n        r = 0\n        s = 0\n        for txin in self.inputs():\n            if txin['type'] == 'coinbase':\n                continue\n            signatures = list(filter(None, txin.get('signatures',[])))\n            s += len(signatures)\n            r += txin.get('num_sig',-1)\n        return s, r\n\n    def is_complete(self):\n        s, r = self.signature_count()\n        return r == s\n\n    def sign(self, keypairs):\n        for i, txin in enumerate(self.inputs()):\n            num = txin['num_sig']\n            pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)\n            for j, x_pubkey in enumerate(x_pubkeys):\n                signatures = list(filter(None, txin['signatures']))\n                if len(signatures) == num:\n                    # txin is complete\n                    break\n                if x_pubkey in keypairs.keys():\n                    print_error(\"adding signature for\", x_pubkey)\n                    sec, compressed = keypairs.get(x_pubkey)\n                    pubkey = public_key_from_private_key(sec, compressed)\n                    # add signature\n                    pre_hash = Hash(bfh(self.serialize_preimage(i)))\n                    pkey = regenerate_key(sec)\n                    secexp = pkey.secret\n                    private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve = SECP256k1)\n                    public_key = private_key.get_verifying_key()\n                    sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der)\n                    assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der)\n                    txin['signatures'][j] = bh2u(sig) + '41'\n                    #txin['x_pubkeys'][j] = pubkey\n                    txin['pubkeys'][j] = pubkey # needed for fd keys\n                    self._inputs[i] = txin\n        print_error(\"is_complete\", self.is_complete())\n        self.raw = self.serialize()\n\n    def get_outputs(self):\n        \"\"\"convert pubkeys to addresses\"\"\"\n        o = []\n        for type, x, v in self.outputs():\n            if type == TYPE_ADDRESS:\n                addr = x\n            elif type == TYPE_PUBKEY:\n                addr = bitcoin.public_key_to_p2pkh(bfh(x))\n            else:\n                addr = 'SCRIPT ' + x\n            o.append((addr,v))      # consider using yield (addr, v)\n        return o\n\n    def get_output_addresses(self):\n        return [addr for addr, val in self.get_outputs()]\n\n\n    def has_address(self, addr):\n        return (addr in self.get_output_addresses()) or (addr in (tx.get(\"address\") for tx in self.inputs()))\n\n    def as_dict(self):\n        if self.raw is None:\n            self.raw = self.serialize()\n        self.deserialize()\n        out = {\n            'hex': self.raw,\n            'complete': self.is_complete(),\n            'final': self.is_final(),\n        }\n        return out\n\n\ndef tx_from_str(txt):\n    \"json or raw hexadecimal\"\n    import json\n    txt = txt.strip()\n    if not txt:\n        raise ValueError(\"empty string\")\n    try:\n        bfh(txt)\n        is_hex = True\n    except:\n        is_hex = False\n    if is_hex:\n        return txt\n    tx_dict = json.loads(str(txt))\n    assert \"hex\" in tx_dict.keys()\n    return tx_dict[\"hex\"]\n"
  },
  {
    "path": "lib/util.py",
    "content": "# Electrum - lightweight Bitcoin client\n# Copyright (C) 2011 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport binascii\nimport os, sys, re, json\nfrom collections import defaultdict\nfrom datetime import datetime\nfrom decimal import Decimal\nimport traceback\nimport urllib\nimport threading\nimport hmac\nimport requests\n\nfrom .i18n import _\n\n\nimport urllib.request, urllib.parse, urllib.error\nimport queue\n\ndef inv_dict(d):\n    return {v: k for k, v in d.items()}\n\n\nis_bundle = getattr(sys, 'frozen', False)\nis_macOS = sys.platform == 'darwin'\n\nbase_units = {'BTCP':8, 'mBTCP':5, 'uBTCP':2}\nfee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')]\n\ndef normalize_version(v):\n    return [int(x) for x in re.sub(r'(\\.0+)*$','', v).split(\".\")]\n\nclass NotEnoughFunds(Exception): pass\n\n\nclass NoDynamicFeeEstimates(Exception):\n    def __str__(self):\n        return _('Dynamic fee estimates not available')\n\n\nclass InvalidPassword(Exception):\n    def __str__(self):\n        return _(\"Incorrect password\")\n\n# Throw this exception to unwind the stack like when an error occurs.\n# However unlike other exceptions the user won't be informed.\nclass UserCancelled(Exception):\n    '''An exception that is suppressed from the user'''\n    pass\n\nclass MyEncoder(json.JSONEncoder):\n    def default(self, obj):\n        from .transaction import Transaction\n        if isinstance(obj, Transaction):\n            return obj.as_dict()\n        return super(MyEncoder, self).default(obj)\n\nclass PrintError(object):\n    '''A handy base class'''\n    def diagnostic_name(self):\n        return self.__class__.__name__\n\n    def print_error(self, *msg):\n        print_error(\"[%s]\" % self.diagnostic_name(), *msg)\n\n    def print_msg(self, *msg):\n        print_msg(\"[%s]\" % self.diagnostic_name(), *msg)\n\nclass ThreadJob(PrintError):\n    \"\"\"A job that is run periodically from a thread's main loop.  run() is\n    called from that thread's context.\n    \"\"\"\n\n    def run(self):\n        \"\"\"Called periodically from the thread\"\"\"\n        pass\n\nclass DebugMem(ThreadJob):\n    '''A handy class for debugging GC memory leaks'''\n    def __init__(self, classes, interval=30):\n        self.next_time = 0\n        self.classes = classes\n        self.interval = interval\n\n    def mem_stats(self):\n        import gc\n        self.print_error(\"Start memscan\")\n        gc.collect()\n        objmap = defaultdict(list)\n        for obj in gc.get_objects():\n            for class_ in self.classes:\n                if isinstance(obj, class_):\n                    objmap[class_].append(obj)\n        for class_, objs in objmap.items():\n            self.print_error(\"%s: %d\" % (class_.__name__, len(objs)))\n        self.print_error(\"Finish memscan\")\n\n    def run(self):\n        if time.time() > self.next_time:\n            self.mem_stats()\n            self.next_time = time.time() + self.interval\n\nclass DaemonThread(threading.Thread, PrintError):\n    \"\"\" daemon thread that terminates cleanly \"\"\"\n\n    def __init__(self):\n        threading.Thread.__init__(self)\n        self.parent_thread = threading.currentThread()\n        self.running = False\n        self.running_lock = threading.Lock()\n        self.job_lock = threading.Lock()\n        self.jobs = []\n\n    def add_jobs(self, jobs):\n        with self.job_lock:\n            self.jobs.extend(jobs)\n\n    def run_jobs(self):\n        # Don't let a throwing job disrupt the thread, future runs of\n        # itself, or other jobs.  This is useful protection against\n        # malformed or malicious server responses\n        with self.job_lock:\n            for job in self.jobs:\n                try:\n                    job.run()\n                except Exception as e:\n                    traceback.print_exc(file=sys.stderr)\n\n    def remove_jobs(self, jobs):\n        with self.job_lock:\n            for job in jobs:\n                self.jobs.remove(job)\n\n    def start(self):\n        with self.running_lock:\n            self.running = True\n        return threading.Thread.start(self)\n\n    def is_running(self):\n        with self.running_lock:\n            return self.running and self.parent_thread.is_alive()\n\n    def stop(self):\n        with self.running_lock:\n            self.running = False\n\n    def on_stop(self):\n        if 'ANDROID_DATA' in os.environ:\n            import jnius\n            jnius.detach()\n            self.print_error(\"jnius detach\")\n        self.print_error(\"stopped\")\n\n\n# TODO: disable\nis_verbose = True\ndef set_verbosity(b):\n    global is_verbose\n    is_verbose = b\n\n\ndef print_error(*args):\n    if not is_verbose: return\n    print_stderr(*args)\n\ndef print_stderr(*args):\n    args = [str(item) for item in args]\n    sys.stderr.write(\" \".join(args) + \"\\n\")\n    sys.stderr.flush()\n\ndef print_msg(*args):\n    # Stringify args\n    args = [str(item) for item in args]\n    sys.stdout.write(\" \".join(args) + \"\\n\")\n    sys.stdout.flush()\n\ndef json_encode(obj):\n    try:\n        s = json.dumps(obj, sort_keys = True, indent = 4, cls=MyEncoder)\n    except TypeError:\n        s = repr(obj)\n    return s\n\ndef json_decode(x):\n    try:\n        return json.loads(x, parse_float=Decimal)\n    except:\n        return x\n\n\n# taken from Django Source Code\ndef constant_time_compare(val1, val2):\n    \"\"\"Return True if the two strings are equal, False otherwise.\"\"\"\n    return hmac.compare_digest(to_bytes(val1, 'utf8'), to_bytes(val2, 'utf8'))\n\n\n# decorator that prints execution time\ndef profiler(func):\n    def do_profile(func, args, kw_args):\n        n = func.__name__\n        t0 = time.time()\n        o = func(*args, **kw_args)\n        t = time.time() - t0\n        print_error(\"[profiler]\", n, \"%.4f\"%t)\n        return o\n    return lambda *args, **kw_args: do_profile(func, args, kw_args)\n\n\ndef android_ext_dir():\n    import jnius\n    env = jnius.autoclass('android.os.Environment')\n    return env.getExternalStorageDirectory().getPath()\n\ndef android_data_dir():\n    import jnius\n    PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity')\n    return PythonActivity.mActivity.getFilesDir().getPath() + '/data'\n\ndef android_headers_dir():\n    d = android_ext_dir() + '/org.electrum.electrum'\n    if not os.path.exists(d):\n        os.mkdir(d)\n    return d\n\ndef android_check_data_dir():\n    \"\"\" if needed, move old directory to sandbox \"\"\"\n    ext_dir = android_ext_dir()\n    data_dir = android_data_dir()\n    old_electrum_dir = ext_dir + '/electrum'\n    if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir):\n        import shutil\n        new_headers_path = android_headers_dir() + '/blockchain_headers'\n        old_headers_path = old_electrum_dir + '/blockchain_headers'\n        if not os.path.exists(new_headers_path) and os.path.exists(old_headers_path):\n            print_error(\"Moving headers file to\", new_headers_path)\n            shutil.move(old_headers_path, new_headers_path)\n        print_error(\"Moving data to\", data_dir)\n        shutil.move(old_electrum_dir, data_dir)\n    return data_dir\n\ndef get_headers_dir(config):\n    return android_headers_dir() if 'ANDROID_DATA' in os.environ else config.path\n\n\ndef assert_bytes(*args):\n    \"\"\"\n    porting helper, assert args type\n    \"\"\"\n    try:\n        for x in args:\n            assert isinstance(x, (bytes, bytearray))\n    except:\n        print('assert bytes failed', list(map(type, args)))\n        raise\n\n\ndef assert_str(*args):\n    \"\"\"\n    porting helper, assert args type\n    \"\"\"\n    for x in args:\n        assert isinstance(x, str)\n\n\n\ndef to_string(x, enc):\n    if isinstance(x, (bytes, bytearray)):\n        return x.decode(enc)\n    if isinstance(x, str):\n        return x\n    else:\n        raise TypeError(\"Not a string or bytes like object\")\n\ndef to_bytes(something, encoding='utf8'):\n    \"\"\"\n    cast string to bytes() like object, but for python2 support it's bytearray copy\n    \"\"\"\n    if isinstance(something, bytes):\n        return something\n    if isinstance(something, str):\n        return something.encode(encoding)\n    elif isinstance(something, bytearray):\n        return bytes(something)\n    else:\n        raise TypeError(\"Not a string or bytes like object\")\n\n\nbfh = bytes.fromhex\nhfu = binascii.hexlify\n\n\ndef bh2u(x):\n    \"\"\"\n    str with hex representation of a bytes-like object\n\n    >>> x = bytes((1, 2, 10))\n    >>> bh2u(x)\n    '01020A'\n\n    :param x: bytes\n    :rtype: str\n    \"\"\"\n    return hfu(x).decode('ascii')\n\n\ndef user_dir():\n    if 'ANDROID_DATA' in os.environ:\n        return android_check_data_dir()\n    elif os.name == 'posix':\n        return os.path.join(os.environ[\"HOME\"], \".electrum-btcp\")\n    elif \"APPDATA\" in os.environ:\n        return os.path.join(os.environ[\"APPDATA\"], \"Electrum-btcp\")\n    elif \"LOCALAPPDATA\" in os.environ:\n        return os.path.join(os.environ[\"LOCALAPPDATA\"], \"Electrum-btcp\")\n    else:\n        #raise Exception(\"No home directory found in environment variables.\")\n        return\n\n\ndef format_satoshis_plain(x, decimal_point = 8):\n    \"\"\"Display a satoshi amount scaled.  Always uses a '.' as a decimal\n    point and has no thousands separator\"\"\"\n    scale_factor = pow(10, decimal_point)\n    return \"{:.8f}\".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')\n\n\ndef format_satoshis(x, is_diff=False, num_zeros = 0, decimal_point = 8, whitespaces=False):\n    from locale import localeconv\n    if x is None:\n        return 'Unknown'\n    x = int(x)  # Some callers pass Decimal\n    scale_factor = pow (10, decimal_point)\n    integer_part = \"{:n}\".format(int(abs(x) / scale_factor))\n    if x < 0:\n        integer_part = '-' + integer_part\n    elif is_diff:\n        integer_part = '+' + integer_part\n    dp = localeconv()['decimal_point']\n    fract_part = (\"{:0\" + str(decimal_point) + \"}\").format(abs(x) % scale_factor)\n    fract_part = fract_part.rstrip('0')\n    if len(fract_part) < num_zeros:\n        fract_part += \"0\" * (num_zeros - len(fract_part))\n    result = integer_part + dp + fract_part\n    if whitespaces:\n        result += \" \" * (decimal_point - len(fract_part))\n        result = \" \" * (15 - len(result)) + result\n    return result\n\ndef timestamp_to_datetime(timestamp):\n    try:\n        return datetime.fromtimestamp(timestamp)\n    except:\n        return None\n\ndef format_time(timestamp):\n    date = timestamp_to_datetime(timestamp)\n    return date.isoformat(' ')[:-3] if date else _(\"Unknown\")\n\n\n# Takes a timestamp and returns a string with the approximation of the age\ndef age(from_date, since_date = None, target_tz=None, include_seconds=False):\n    if from_date is None:\n        return \"Unknown\"\n\n    from_date = datetime.fromtimestamp(from_date)\n    if since_date is None:\n        since_date = datetime.now(target_tz)\n\n    td = time_difference(from_date - since_date, include_seconds)\n    return td + \" ago\" if from_date < since_date else \"in \" + td\n\n\ndef time_difference(distance_in_time, include_seconds):\n    #distance_in_time = since_date - from_date\n    distance_in_seconds = int(round(abs(distance_in_time.days * 86400 + distance_in_time.seconds)))\n    distance_in_minutes = int(round(distance_in_seconds/60))\n\n    if distance_in_minutes <= 1:\n        if include_seconds:\n            for remainder in [5, 10, 20]:\n                if distance_in_seconds < remainder:\n                    return \"less than %s seconds\" % remainder\n            if distance_in_seconds < 40:\n                return \"half a minute\"\n            elif distance_in_seconds < 60:\n                return \"less than a minute\"\n            else:\n                return \"1 minute\"\n        else:\n            if distance_in_minutes == 0:\n                return \"less than a minute\"\n            else:\n                return \"1 minute\"\n    elif distance_in_minutes < 45:\n        return \"%s minutes\" % distance_in_minutes\n    elif distance_in_minutes < 90:\n        return \"about 1 hour\"\n    elif distance_in_minutes < 1440:\n        return \"about %d hours\" % (round(distance_in_minutes / 60.0))\n    elif distance_in_minutes < 2880:\n        return \"1 day\"\n    elif distance_in_minutes < 43220:\n        return \"%d days\" % (round(distance_in_minutes / 1440))\n    elif distance_in_minutes < 86400:\n        return \"about 1 month\"\n    elif distance_in_minutes < 525600:\n        return \"%d months\" % (round(distance_in_minutes / 43200))\n    elif distance_in_minutes < 1051200:\n        return \"about 1 year\"\n    else:\n        return \"over %d years\" % (round(distance_in_minutes / 525600))\n\n# For raw json, append /insight-api-zcash\nmainnet_block_explorers = {\n    'explorer.btcprivate.org': ('https://explorer.btcprivate.org',\n                        {'tx': 'tx', 'addr': 'address'}),\n    'system default': ('blockchain:',\n                        {'tx': 'tx', 'addr': 'address'})\n\n}\n\ntestnet_block_explorers = {\n    #'testnet.btcprivate.org': ('https://testnet.btcprivate.org',\n    #                    {'tx': 'tx', 'addr': 'address'}),\n    'system default': ('blockchain:',\n                       {'tx': 'tx', 'addr': 'address'})\n}\n\ndef block_explorer_info():\n    from . import bitcoin\n    return testnet_block_explorers if bitcoin.NetworkConstants.TESTNET else mainnet_block_explorers\n\ndef block_explorer(config):\n    return config.get('block_explorer', 'explorer.btcprivate.org')\n\ndef block_explorer_tuple(config):\n    return block_explorer_info().get(block_explorer(config))\n\ndef block_explorer_URL(config, kind, item):\n    be_tuple = block_explorer_tuple(config)\n    if not be_tuple:\n        return\n    kind_str = be_tuple[1].get(kind)\n    if not kind_str:\n        return\n    url_parts = [be_tuple[0], kind_str, item]\n    return \"/\".join(url_parts)\n\n# URL decode\n#_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)\n#urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)\n\ndef parse_URI(uri, on_pr=None):\n    from . import bitcoin\n    from .bitcoin import COIN\n\n    if ':' not in uri:\n        if not bitcoin.is_address(uri):\n            raise BaseException(\"Not a BTCP address\")\n        return {'address': uri}\n\n    u = urllib.parse.urlparse(uri)\n    if u.scheme != 'bitcoin':\n        raise BaseException(\"Not a bitcoin URI\")\n    address = u.path\n\n    # python for android fails to parse query\n    if address.find('?') > 0:\n        address, query = u.path.split('?')\n        pq = urllib.parse.parse_qs(query)\n    else:\n        pq = urllib.parse.parse_qs(u.query)\n\n    for k, v in pq.items():\n        if len(v)!=1:\n            raise Exception('Duplicate Key', k)\n\n    out = {k: v[0] for k, v in pq.items()}\n    if address:\n        if not bitcoin.is_address(address):\n            raise BaseException(\"Invalid BTCP address:\" + address)\n        out['address'] = address\n    if 'amount' in out:\n        am = out['amount']\n        m = re.match('([0-9\\.]+)X([0-9])', am)\n        if m:\n            k = int(m.group(2)) - 8\n            amount = Decimal(m.group(1)) * pow(  Decimal(10) , k)\n        else:\n            amount = Decimal(am) * COIN\n        out['amount'] = int(amount)\n    if 'message' in out:\n        out['message'] = out['message']\n        out['memo'] = out['message']\n    if 'time' in out:\n        out['time'] = int(out['time'])\n    if 'exp' in out:\n        out['exp'] = int(out['exp'])\n    if 'sig' in out:\n        out['sig'] = bh2u(bitcoin.base_decode(out['sig'], None, base=58))\n\n    r = out.get('r')\n    sig = out.get('sig')\n    name = out.get('name')\n    if on_pr and (r or (name and sig)):\n        def get_payment_request_thread():\n            from . import paymentrequest as pr\n            if name and sig:\n                s = pr.serialize_request(out).SerializeToString()\n                request = pr.PaymentRequest(s)\n            else:\n                request = pr.get_payment_request(r)\n            if on_pr:\n                on_pr(request)\n        t = threading.Thread(target=get_payment_request_thread)\n        t.setDaemon(True)\n        t.start()\n\n    return out\n\n\ndef create_URI(addr, amount, message):\n    from . import bitcoin\n    if not bitcoin.is_address(addr):\n        return \"\"\n    query = []\n    if amount:\n        query.append('amount=%s'%format_satoshis_plain(amount))\n    if message:\n        query.append('message=%s'%urllib.parse.quote(message))\n    p = urllib.parse.ParseResult(scheme='bitcoin', netloc='', path=addr, params='', query='&'.join(query), fragment='')\n    return urllib.parse.urlunparse(p)\n\n\n# Python bug (http://bugs.python.org/issue1927) causes raw_input\n# to be redirected improperly between stdin/stderr on Unix systems\n#TODO: py3\ndef raw_input(prompt=None):\n    if prompt:\n        sys.stdout.write(prompt)\n    return builtin_raw_input()\n\nimport builtins\nbuiltin_raw_input = builtins.input\nbuiltins.input = raw_input\n\n\ndef parse_json(message):\n    # TODO: check \\r\\n pattern\n    n = message.find(b'\\n')\n    if n==-1:\n        return None, message\n    try:\n        j = json.loads(message[0:n].decode('utf8'))\n    except:\n        j = None\n    return j, message[n+1:]\n\n\nclass timeout(Exception):\n    pass\n\nimport socket\nimport json\nimport ssl\nimport time\n\n\nclass SocketPipe:\n    def __init__(self, socket):\n        self.socket = socket\n        self.message = b''\n        self.set_timeout(0.1)\n        self.recv_time = time.time()\n\n    def set_timeout(self, t):\n        self.socket.settimeout(t)\n\n    def idle_time(self):\n        return time.time() - self.recv_time\n\n    def get(self):\n        while True:\n            response, self.message = parse_json(self.message)\n            if response is not None:\n                return response\n            try:\n                data = self.socket.recv(1024)\n            except socket.timeout:\n                raise timeout\n            except ssl.SSLError:\n                raise timeout\n            except socket.error as err:\n                if err.errno == 60:\n                    raise timeout\n                elif err.errno in [11, 35, 10035]:\n                    print_error(\"socket errno %d (resource temporarily unavailable)\"% err.errno)\n                    time.sleep(0.2)\n                    raise timeout\n                else:\n                    print_error(\"pipe: socket error\", err)\n                    data = b''\n            except:\n                traceback.print_exc(file=sys.stderr)\n                data = b''\n\n            if not data:  # Connection closed remotely\n                return None\n            self.message += data\n            self.recv_time = time.time()\n\n    def send(self, request):\n        out = json.dumps(request) + '\\n'\n        out = out.encode('utf8')\n        self._send(out)\n\n    def send_all(self, requests):\n        out = b''.join(map(lambda x: (json.dumps(x) + '\\n').encode('utf8'), requests))\n        self._send(out)\n\n    def _send(self, out):\n        while out:\n            try:\n                sent = self.socket.send(out)\n                out = out[sent:]\n            except ssl.SSLError as e:\n                print_error(\"SSLError:\", e)\n                time.sleep(0.1)\n                continue\n            except OSError as e:\n                print_error(\"OSError\", e)\n                time.sleep(0.1)\n                continue\n\n\nclass QueuePipe:\n\n    def __init__(self, send_queue=None, get_queue=None):\n        self.send_queue = send_queue if send_queue else queue.Queue()\n        self.get_queue = get_queue if get_queue else queue.Queue()\n        self.set_timeout(0.1)\n\n    def get(self):\n        try:\n            return self.get_queue.get(timeout=self.timeout)\n        except queue.Empty:\n            raise timeout\n\n    def get_all(self):\n        responses = []\n        while True:\n            try:\n                r = self.get_queue.get_nowait()\n                responses.append(r)\n            except queue.Empty:\n                break\n        return responses\n\n    def set_timeout(self, t):\n        self.timeout = t\n\n    def send(self, request):\n        self.send_queue.put(request)\n\n    def send_all(self, requests):\n        for request in requests:\n            self.send(request)\n\n\ndef get_cert_path():\n    if is_bundle and is_macOS:\n        # set in ./electrum\n        return requests.utils.DEFAULT_CA_BUNDLE_PATH\n    return requests.certs.where()\n"
  },
  {
    "path": "lib/verifier.py",
    "content": "# Electrum - Lightweight Bitcoin Client\n# Copyright (c) 2012 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom .util import ThreadJob\nfrom .bitcoin import *\n\n\nclass SPV(ThreadJob):\n    \"\"\" Simple Payment Verification \"\"\"\n\n    def __init__(self, network, wallet):\n        self.wallet = wallet\n        self.network = network\n        self.blockchain = network.blockchain()\n        # Keyed by tx hash.  Value is None if the merkle branch was\n        # requested, and the merkle root once it has been verified\n        self.merkle_roots = {}\n\n    def run(self):\n        lh = self.network.get_local_height()\n        unverified = self.wallet.get_unverified_txs()\n        for tx_hash, tx_height in unverified.items():\n            # do not request merkle branch before headers are available\n            if (tx_height > 0) and (tx_height <= lh):\n                header = self.network.blockchain().read_header(tx_height)\n                if header is None and self.network.interface:\n                    index = tx_height // NetworkConstants.CHUNK_SIZE\n                    self.network.request_chunk(self.network.interface, index)\n                else:\n                    if tx_hash not in self.merkle_roots:\n                        request = ('blockchain.transaction.get_merkle',\n                                   [tx_hash, tx_height])\n                        self.network.send([request], self.verify_merkle)\n                        self.print_error('requested merkle', tx_hash)\n                        self.merkle_roots[tx_hash] = None\n\n        if self.network.blockchain() != self.blockchain:\n            self.blockchain = self.network.blockchain()\n            self.undo_verifications()\n\n    def verify_merkle(self, r):\n        if r.get('error'):\n            self.print_error('received an error:', r)\n            return\n        params = r['params']\n        merkle = r['result']\n        # Verify the hash of the server-provided merkle branch to a\n        # transaction matches the merkle root of its block\n        tx_hash = params[0]\n        tx_height = merkle.get('block_height')\n        pos = merkle.get('pos')\n        merkle_root = self.hash_merkle_root(merkle['merkle'], tx_hash, pos)\n        header = self.network.blockchain().read_header(tx_height)\n        if not header or header.get('merkle_root') != merkle_root:\n            # FIXME: we should make a fresh connection to a server to\n            # recover from this, as this TX will now never verify\n            self.print_error(\"merkle verification failed for\", tx_hash)\n            return\n        # we passed all the tests\n        self.merkle_roots[tx_hash] = merkle_root\n        self.print_error(\"verified %s\" % tx_hash)\n        self.wallet.add_verified_tx(tx_hash, (tx_height, header.get('timestamp'), pos))\n\n    def hash_merkle_root(self, merkle_s, target_hash, pos):\n        h = hash_decode(target_hash)\n        for i in range(len(merkle_s)):\n            item = merkle_s[i]\n            h = Hash(hash_decode(item) + h) if ((pos >> i) & 1) else Hash(h + hash_decode(item))\n        return hash_encode(h)\n\n    def undo_verifications(self):\n        height = self.blockchain.get_checkpoint()\n        tx_hashes = self.wallet.undo_verifications(self.blockchain, height)\n        for tx_hash in tx_hashes:\n            self.print_error(\"redoing\", tx_hash)\n            self.merkle_roots.pop(tx_hash, None)\n"
  },
  {
    "path": "lib/version.py",
    "content": " # version of the client package\nELECTRUM_VERSION = 'P!1.0.0'\n# protocol version requested\nPROTOCOL_VERSION = '1.1'\n\n# The hash of the mnemonic seed must begin with this\nSEED_PREFIX      = '01'      # Standard wallet\nSEED_PREFIX_2FA  = '101'     # Two-factor authentication\nSEED_PREFIX_SW   = '100'     # Segwit wallet\n\n\ndef seed_prefix(seed_type):\n    if seed_type == 'standard':\n        return SEED_PREFIX\n    elif seed_type == 'segwit':\n        return SEED_PREFIX_SW\n    elif seed_type == '2fa':\n        return SEED_PREFIX_2FA\n"
  },
  {
    "path": "lib/wallet.py",
    "content": "# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n# Wallet classes:\n#   - Imported_Wallet: imported address, no keystore\n#   - Standard_Wallet: one keystore, P2PKH\n#   - Multisig_Wallet: several keystores, P2SH\n\n\nimport os\nimport threading\nimport random\nimport time\nimport json\nimport copy\nimport errno\nimport traceback\nfrom functools import partial\nfrom collections import defaultdict\nfrom numbers import Number\n\nimport sys\n\nfrom .i18n import _\nfrom .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,\n                   format_satoshis, NoDynamicFeeEstimates)\n\nfrom .bitcoin import *\nfrom .version import *\nfrom .keystore import load_keystore, Hardware_KeyStore\nfrom .storage import multisig_type\n\nfrom . import transaction\nfrom .transaction import Transaction\nfrom .plugins import run_hook\nfrom . import bitcoin\nfrom . import coinchooser\nfrom .synchronizer import Synchronizer\nfrom .verifier import SPV\n\nfrom . import paymentrequest\nfrom .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED\nfrom .paymentrequest import InvoiceStore\nfrom .contacts import Contacts\n\nTX_STATUS = [\n    _('Replaceable'),\n    _('Unconfirmed parent'),\n    _('Low fee'),\n    _('Unconfirmed'),\n    _('Not Verified'),\n]\n\n\n\ndef relayfee(network):\n    RELAY_FEE = 1000\n    MAX_RELAY_FEE = 50000\n    f = network.relay_fee if network and network.relay_fee else RELAY_FEE\n    return min(f, MAX_RELAY_FEE)\n\ndef dust_threshold(network):\n    # Change <= dust threshold is added to the tx fee\n    return 182 * 3 * relayfee(network) / 1000\n\n\ndef append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax):\n    if txin_type != 'p2pk':\n        address = bitcoin.pubkey_to_address(txin_type, pubkey)\n        sh = bitcoin.address_to_scripthash(address)\n    else:\n        script = bitcoin.public_key_to_p2pk_script(pubkey)\n        sh = bitcoin.script_to_scripthash(script)\n        address = '(pubkey)'\n    u = network.synchronous_get(('blockchain.scripthash.listunspent', [sh]))\n    for item in u:\n        if len(inputs) >= imax:\n            break\n        item['address'] = address\n        item['type'] = txin_type\n        item['prevout_hash'] = item['tx_hash']\n        item['prevout_n'] = item['tx_pos']\n        item['pubkeys'] = [pubkey]\n        item['x_pubkeys'] = [pubkey]\n        item['signatures'] = [None]\n        item['num_sig'] = 1\n        inputs.append(item)\n\ndef sweep_preparations(privkeys, network, imax=100):\n\n    def find_utxos_for_privkey(txin_type, privkey, compressed):\n        pubkey = bitcoin.public_key_from_private_key(privkey, compressed)\n        append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)\n        keypairs[pubkey] = privkey, compressed\n    inputs = []\n    keypairs = {}\n    for sec in privkeys:\n        txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)\n        find_utxos_for_privkey(txin_type, privkey, compressed)\n        # do other lookups to increase support coverage\n        if is_minikey(sec):\n            # minikeys don't have a compressed byte\n            # we lookup both compressed and uncompressed pubkeys\n            find_utxos_for_privkey(txin_type, privkey, not compressed)\n        elif txin_type == 'p2pkh':\n            # WIF serialization does not distinguish p2pkh and p2pk\n            # we also search for pay-to-pubkey outputs\n            find_utxos_for_privkey('p2pk', privkey, compressed)\n    if not inputs:\n        raise BaseException(_('No inputs found. (Note that inputs need to be confirmed)'))\n    return inputs, keypairs\n\n\ndef sweep(privkeys, network, config, recipient, fee=None, imax=100):\n    inputs, keypairs = sweep_preparations(privkeys, network, imax)\n    total = sum(i.get('value') for i in inputs)\n    if fee is None:\n        outputs = [(TYPE_ADDRESS, recipient, total)]\n        tx = Transaction.from_io(inputs, outputs)\n        fee = config.estimate_fee(tx.estimated_size())\n    if total - fee < 0:\n        raise BaseException(_('Not enough funds on address.') + '\\nTotal: %d satoshis\\nFee: %d'%(total, fee))\n    if total - fee < dust_threshold(network):\n        raise BaseException(_('Not enough funds on address.') + '\\nTotal: %d satoshis\\nFee: %d\\nDust Threshold: %d'%(total, fee, dust_threshold(network)))\n\n    outputs = [(TYPE_ADDRESS, recipient, total - fee)]\n    locktime = network.get_local_height()\n\n    tx = Transaction.from_io(inputs, outputs, locktime=locktime)\n    tx.BIP_LI01_sort()\n    tx.set_rbf(True)\n    tx.sign(keypairs)\n    return tx\n\n\nclass Abstract_Wallet(PrintError):\n    \"\"\"\n    Wallet classes are created to handle various address generation methods.\n    Completion states (watching-only, single account, no seed, etc) are handled inside classes.\n    \"\"\"\n\n    max_change_outputs = 3\n\n    def __init__(self, storage):\n        self.electrum_version = ELECTRUM_VERSION\n        self.storage = storage\n        self.network = None\n        # verifier (SPV) and synchronizer are started in start_threads\n        self.synchronizer = None\n        self.verifier = None\n\n        self.gap_limit_for_change = 6 # constant\n        # saved fields\n        self.use_change            = storage.get('use_change', True)\n        self.multiple_change       = storage.get('multiple_change', False)\n        self.labels                = storage.get('labels', {})\n        self.frozen_addresses      = set(storage.get('frozen_addresses',[]))\n        self.history               = storage.get('addr_history',{})        # address -> list(txid, height)\n\n        self.load_keystore()\n        self.load_addresses()\n        self.load_transactions()\n        self.build_reverse_history()\n\n        # load requests\n        self.receive_requests = self.storage.get('payment_requests', {})\n\n        # Transactions pending verification.  A map from tx hash to transaction\n        # height.  Access is not contended so no lock is needed.\n        self.unverified_tx = defaultdict(int)\n\n        # Verified transactions.  Each value is a (height, timestamp, block_pos) tuple.  Access with self.lock.\n        self.verified_tx = storage.get('verified_tx3', {})\n\n        # there is a difference between wallet.up_to_date and interface.is_up_to_date()\n        # interface.is_up_to_date() returns true when all requests have been answered and processed\n        # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)\n        self.up_to_date = False\n        self.lock = threading.Lock()\n        self.transaction_lock = threading.Lock()\n\n        self.check_history()\n\n        # save wallet type the first time\n        if self.storage.get('wallet_type') is None:\n            self.storage.put('wallet_type', self.wallet_type)\n\n        # invoices and contacts\n        self.invoices = InvoiceStore(self.storage)\n        self.contacts = Contacts(self.storage)\n\n\n    def diagnostic_name(self):\n        return self.basename()\n\n    def __str__(self):\n        return self.basename()\n\n    def get_master_public_key(self):\n        return None\n\n    @profiler\n    def load_transactions(self):\n        self.txi = self.storage.get('txi', {})\n        self.txo = self.storage.get('txo', {})\n        self.tx_fees = self.storage.get('tx_fees', {})\n        self.pruned_txo = self.storage.get('pruned_txo', {})\n        tx_list = self.storage.get('transactions', {})\n        self.transactions = {}\n        for tx_hash, raw in tx_list.items():\n            tx = Transaction(raw)\n            self.transactions[tx_hash] = tx\n            if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None and (tx_hash not in self.pruned_txo.values()):\n                self.print_error(\"removing unreferenced tx\", tx_hash)\n                self.transactions.pop(tx_hash)\n\n    @profiler\n    def save_transactions(self, write=False):\n        with self.transaction_lock:\n            tx = {}\n            for k,v in self.transactions.items():\n                tx[k] = str(v)\n            self.storage.put('transactions', tx)\n            self.storage.put('txi', self.txi)\n            self.storage.put('txo', self.txo)\n            self.storage.put('tx_fees', self.tx_fees)\n            self.storage.put('pruned_txo', self.pruned_txo)\n            self.storage.put('addr_history', self.history)\n            if write:\n                self.storage.write()\n\n    def clear_history(self):\n        with self.transaction_lock:\n            self.txi = {}\n            self.txo = {}\n            self.tx_fees = {}\n            self.pruned_txo = {}\n        self.save_transactions()\n        with self.lock:\n            self.history = {}\n            self.tx_addr_hist = {}\n\n    @profiler\n    def build_reverse_history(self):\n        self.tx_addr_hist = {}\n        for addr, hist in self.history.items():\n            for tx_hash, h in hist:\n                s = self.tx_addr_hist.get(tx_hash, set())\n                s.add(addr)\n                self.tx_addr_hist[tx_hash] = s\n\n    @profiler\n    def check_history(self):\n        save = False\n        mine_addrs = list(filter(lambda k: self.is_mine(self.history[k]), self.history.keys()))\n        if len(mine_addrs) != len(self.history.keys()):\n            save = True\n        for addr in mine_addrs:\n            hist = self.history[addr]\n\n            for tx_hash, tx_height in hist:\n                if tx_hash in self.pruned_txo.values() or self.txi.get(tx_hash) or self.txo.get(tx_hash):\n                    continue\n                tx = self.transactions.get(tx_hash)\n                if tx is not None:\n                    self.add_transaction(tx_hash, tx)\n                    save = True\n        if save:\n            self.save_transactions()\n\n    def basename(self):\n        return os.path.basename(self.storage.path)\n\n    def save_addresses(self):\n        self.storage.put('addresses', {'receiving':self.receiving_addresses, 'change':self.change_addresses})\n\n    def load_addresses(self):\n        d = self.storage.get('addresses', {})\n        if type(d) != dict: d={}\n        self.receiving_addresses = d.get('receiving', [])\n        self.change_addresses = d.get('change', [])\n\n    def synchronize(self):\n        pass\n\n    def set_up_to_date(self, up_to_date):\n        with self.lock:\n            self.up_to_date = up_to_date\n        if up_to_date:\n            self.save_transactions(write=True)\n\n    def is_up_to_date(self):\n        with self.lock: return self.up_to_date\n\n    def set_label(self, name, text = None):\n        changed = False\n        old_text = self.labels.get(name)\n        if text:\n            text = text.replace(\"\\n\", \" \")\n            if old_text != text:\n                self.labels[name] = text\n                changed = True\n        else:\n            if old_text:\n                self.labels.pop(name)\n                changed = True\n\n        if changed:\n            run_hook('set_label', self, name, text)\n            self.storage.put('labels', self.labels)\n\n        return changed\n\n    def is_mine(self, address):\n        return address in self.get_addresses()\n\n    def is_change(self, address):\n        if not self.is_mine(address):\n            return False\n        return address in self.change_addresses\n\n    def get_address_index(self, address):\n        if address in self.receiving_addresses:\n            return False, self.receiving_addresses.index(address)\n        if address in self.change_addresses:\n            return True, self.change_addresses.index(address)\n        raise Exception(\"Address not found\", address)\n\n    def export_private_key(self, address, password):\n        \"\"\" extended WIF format \"\"\"\n        if self.is_watching_only():\n            return []\n        index = self.get_address_index(address)\n        pk, compressed = self.keystore.get_private_key(index, password)\n        if self.txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:\n            pubkeys = self.get_public_keys(address)\n            redeem_script = self.pubkeys_to_redeem_script(pubkeys)\n        else:\n            redeem_script = None\n        return bitcoin.serialize_privkey(pk, compressed, self.txin_type), redeem_script\n\n\n    def get_public_keys(self, address):\n        sequence = self.get_address_index(address)\n        return self.get_pubkeys(*sequence)\n\n    def add_unverified_tx(self, tx_hash, tx_height):\n        if tx_height == 0 and tx_hash in self.verified_tx:\n            self.verified_tx.pop(tx_hash)\n            self.verifier.merkle_roots.pop(tx_hash, None)\n\n        # tx will be verified only if height > 0\n        if tx_hash not in self.verified_tx:\n            self.unverified_tx[tx_hash] = tx_height\n\n    def add_verified_tx(self, tx_hash, info):\n        # Remove from the unverified map and add to the verified map and\n        self.unverified_tx.pop(tx_hash, None)\n        with self.lock:\n            self.verified_tx[tx_hash] = info  # (tx_height, timestamp, pos)\n        height, conf, timestamp = self.get_tx_height(tx_hash)\n        self.network.trigger_callback('verified', tx_hash, height, conf, timestamp)\n\n    def get_unverified_txs(self):\n        '''Returns a map from tx hash to transaction height'''\n        return self.unverified_tx\n\n    def undo_verifications(self, blockchain, height):\n        '''Used by the verifier when a reorg has happened'''\n        txs = set()\n        with self.lock:\n            for tx_hash, item in list(self.verified_tx.items()):\n                tx_height, timestamp, pos = item\n                if tx_height >= height:\n                    header = blockchain.read_header(tx_height)\n                    # fixme: use block hash, not timestamp\n                    if not header or header.get('timestamp') != timestamp:\n                        self.verified_tx.pop(tx_hash, None)\n                        txs.add(tx_hash)\n        return txs\n\n    def get_local_height(self):\n        \"\"\" return last known height if we are offline \"\"\"\n        return self.network.get_local_height() if self.network else self.storage.get('stored_height', 0)\n\n    def get_tx_height(self, tx_hash):\n        \"\"\" return the height and timestamp of a verified transaction. \"\"\"\n        with self.lock:\n            if tx_hash in self.verified_tx:\n                height, timestamp, pos = self.verified_tx[tx_hash]\n                conf = max(self.get_local_height() - height + 1, 0)\n                return height, conf, timestamp\n            else:\n                height = self.unverified_tx[tx_hash]\n                return height, 0, False\n\n    def get_txpos(self, tx_hash):\n        \"return position, even if the tx is unverified\"\n        with self.lock:\n            x = self.verified_tx.get(tx_hash)\n            y = self.unverified_tx.get(tx_hash)\n            if x:\n                height, timestamp, pos = x\n                return height, pos\n            elif y > 0:\n                return y, 0\n            else:\n                return 1e12 - y, 0\n\n    def is_found(self):\n        return self.history.values() != [[]] * len(self.history)\n\n    def get_num_tx(self, address):\n        \"\"\" return number of transactions where address is involved \"\"\"\n        return len(self.history.get(address, []))\n\n    def get_tx_delta(self, tx_hash, address):\n        \"effect of tx on address\"\n        # pruned\n        if tx_hash in self.pruned_txo.values():\n            return None\n        delta = 0\n        # substract the value of coins sent from address\n        d = self.txi.get(tx_hash, {}).get(address, [])\n        for n, v in d:\n            delta -= v\n        # add the value of the coins received at address\n        d = self.txo.get(tx_hash, {}).get(address, [])\n        for n, v, cb in d:\n            delta += v\n        return delta\n\n    def get_wallet_delta(self, tx):\n        \"\"\" effect of tx on wallet \"\"\"\n        addresses = self.get_addresses()\n        is_relevant = False\n        is_mine = False\n        is_pruned = False\n        is_partial = False\n        v_in = v_out = v_out_mine = 0\n        for item in tx.inputs():\n            addr = item.get('address')\n            if addr in addresses:\n                is_mine = True\n                is_relevant = True\n                d = self.txo.get(item['prevout_hash'], {}).get(addr, [])\n                for n, v, cb in d:\n                    if n == item['prevout_n']:\n                        value = v\n                        break\n                else:\n                    value = None\n                if value is None:\n                    is_pruned = True\n                else:\n                    v_in += value\n            else:\n                is_partial = True\n        if not is_mine:\n            is_partial = False\n        for addr, value in tx.get_outputs():\n            v_out += value\n            if addr in addresses:\n                v_out_mine += value\n                is_relevant = True\n        if is_pruned:\n            # some inputs are mine:\n            fee = None\n            if is_mine:\n                v = v_out_mine - v_out\n            else:\n                # no input is mine\n                v = v_out_mine\n        else:\n            v = v_out_mine - v_in\n            if is_partial:\n                # some inputs are mine, but not all\n                fee = None\n            else:\n                # all inputs are mine\n                fee = v_in - v_out\n        if not is_mine:\n            fee = None\n        return is_relevant, is_mine, v, fee\n\n    def get_tx_info(self, tx):\n        is_relevant, is_mine, v, fee = self.get_wallet_delta(tx)\n        exp_n = None\n        can_broadcast = False\n        can_bump = False\n        label = ''\n        height = conf = timestamp = None\n        tx_hash = tx.txid()\n        if tx.is_complete():\n            if tx_hash in self.transactions.keys():\n                label = self.get_label(tx_hash)\n                height, conf, timestamp = self.get_tx_height(tx_hash)\n                if height > 0:\n                    if conf:\n                        status = _(\"%d confirmations\") % conf\n                    else:\n                        status = _('Not Verified')\n                else:\n                    status = _('Unconfirmed')\n                    if fee is None:\n                        fee = self.tx_fees.get(tx_hash)\n                    if fee and self.network.config.has_fee_estimates():\n                        size = tx.estimated_size()\n                        fee_per_kb = fee * 1000 / size\n                        exp_n = self.network.config.reverse_dynfee(fee_per_kb)\n                    can_bump = is_mine and not tx.is_final()\n            else:\n                status = _(\"Signed\")\n                can_broadcast = self.network is not None\n        else:\n            s, r = tx.signature_count()\n            status = _(\"Unsigned\") if s == 0 else _('Partially Signed') + ' (%d/%d)'%(s,r)\n\n        if is_relevant:\n            if is_mine:\n                if fee is not None:\n                    amount = v + fee\n                else:\n                    amount = v\n            else:\n                amount = v\n        else:\n            amount = None\n\n        return tx_hash, status, label, can_broadcast, can_bump, amount, fee, height, conf, timestamp, exp_n\n\n    def get_addr_io(self, address):\n        h = self.history.get(address, [])\n        received = {}\n        sent = {}\n        for tx_hash, height in h:\n            l = self.txo.get(tx_hash, {}).get(address, [])\n            for n, v, is_cb in l:\n                received[tx_hash + ':%d'%n] = (height, v, is_cb)\n        for tx_hash, height in h:\n            l = self.txi.get(tx_hash, {}).get(address, [])\n            for txi, v in l:\n                sent[txi] = height\n        return received, sent\n\n    def get_addr_utxo(self, address):\n        coins, spent = self.get_addr_io(address)\n        for txi in spent:\n            coins.pop(txi)\n        out = {}\n        for txo, v in coins.items():\n            tx_height, value, is_cb = v\n            prevout_hash, prevout_n = txo.split(':')\n            x = {\n                'address':address,\n                'value':value,\n                'prevout_n':int(prevout_n),\n                'prevout_hash':prevout_hash,\n                'height':tx_height,\n                'coinbase':is_cb\n            }\n            out[txo] = x\n        return out\n\n    # return the total amount ever received by an address\n    def get_addr_received(self, address):\n        received, sent = self.get_addr_io(address)\n        return sum([v for height, v, is_cb in received.values()])\n\n    # return the balance of a bitcoin address: confirmed and matured, unconfirmed, unmatured\n    def get_addr_balance(self, address):\n        received, sent = self.get_addr_io(address)\n        c = u = x = 0\n        for txo, (tx_height, v, is_cb) in received.items():\n            if is_cb and tx_height + COINBASE_MATURITY > self.get_local_height():\n                x += v\n            elif tx_height > 0:\n                c += v\n            else:\n                u += v\n            if txo in sent:\n                if sent[txo] > 0:\n                    c -= v\n                else:\n                    u -= v\n        return c, u, x\n\n    def get_spendable_coins(self, domain, config):\n        confirmed_only = config.get('confirmed_only', False)\n        return self.get_utxos(domain, exclude_frozen=True, mature=True, confirmed_only=confirmed_only)\n\n    def get_utxos(self, domain = None, exclude_frozen = False, mature = False, confirmed_only = False):\n        coins = []\n        if domain is None:\n            domain = self.get_addresses()\n        if exclude_frozen:\n            domain = set(domain) - self.frozen_addresses\n        for addr in domain:\n            utxos = self.get_addr_utxo(addr)\n            for x in utxos.values():\n                if confirmed_only and x['height'] <= 0:\n                    continue\n                if mature and x['coinbase'] and x['height'] + COINBASE_MATURITY > self.get_local_height():\n                    continue\n                coins.append(x)\n                continue\n        return coins\n\n    def dummy_address(self):\n        return self.get_receiving_addresses()[0]\n\n    def get_addresses(self):\n        out = []\n        out += self.get_receiving_addresses()\n        out += self.get_change_addresses()\n        return out\n\n    def get_frozen_balance(self):\n        return self.get_balance(self.frozen_addresses)\n\n    def get_balance(self, domain=None):\n        if domain is None:\n            domain = self.get_addresses()\n        cc = uu = xx = 0\n        for addr in domain:\n            c, u, x = self.get_addr_balance(addr)\n            cc += c\n            uu += u\n            xx += x\n        return cc, uu, xx\n\n    def get_address_history(self, address):\n        with self.lock:\n            return self.history.get(address, [])\n\n    def find_pay_to_pubkey_address(self, prevout_hash, prevout_n):\n        dd = self.txo.get(prevout_hash, {})\n        for addr, l in dd.items():\n            for n, v, is_cb in l:\n                if n == prevout_n:\n                    self.print_error(\"found pay-to-pubkey address:\", addr)\n                    return addr\n\n    def add_transaction(self, tx_hash, tx):\n        is_shielded_input = len(tx.inputs()) == 0\n\n        is_coinbase = not is_shielded_input and tx.inputs()[0]['type'] == 'coinbase'\n        with self.transaction_lock:\n            # add inputs\n            self.txi[tx_hash] = d = {}\n            for txi in tx.inputs():\n                addr = txi.get('address')\n                if txi['type'] != 'coinbase':\n                    prevout_hash = txi['prevout_hash']\n                    prevout_n = txi['prevout_n']\n                    ser = prevout_hash + ':%d'%prevout_n\n                if addr == \"(pubkey)\":\n                    addr = self.find_pay_to_pubkey_address(prevout_hash, prevout_n)\n                # find value from prev output\n                if addr and self.is_mine(addr):\n                    dd = self.txo.get(prevout_hash, {})\n                    for n, v, is_cb in dd.get(addr, []):\n                        if n == prevout_n:\n                            if d.get(addr) is None:\n                                d[addr] = []\n                            d[addr].append((ser, v))\n                            break\n                    else:\n                        self.pruned_txo[ser] = tx_hash\n\n            # add outputs\n            self.txo[tx_hash] = d = {}\n            for n, txo in enumerate(tx.outputs()):\n                ser = tx_hash + ':%d'%n\n                _type, x, v = txo\n                if _type == TYPE_ADDRESS:\n                    addr = x\n                elif _type == TYPE_PUBKEY:\n                    addr = bitcoin.public_key_to_p2pkh(bfh(x))\n                else:\n                    addr = None\n                if addr and self.is_mine(addr):\n                    if d.get(addr) is None:\n                        d[addr] = []\n                    d[addr].append((n, v, is_coinbase))\n                # give v to txi that spends me\n                next_tx = self.pruned_txo.get(ser)\n                if next_tx is not None:\n                    self.pruned_txo.pop(ser)\n                    dd = self.txi.get(next_tx, {})\n                    if dd.get(addr) is None:\n                        dd[addr] = []\n                    dd[addr].append((ser, v))\n            # save\n            self.transactions[tx_hash] = tx\n\n    def remove_transaction(self, tx_hash):\n        with self.transaction_lock:\n            self.print_error(\"removing tx from history\", tx_hash)\n            #tx = self.transactions.pop(tx_hash)\n            for ser, hh in list(self.pruned_txo.items()):\n                if hh == tx_hash:\n                    self.pruned_txo.pop(ser)\n            # add tx to pruned_txo, and undo the txi addition\n            for next_tx, dd in self.txi.items():\n                for addr, l in list(dd.items()):\n                    ll = l[:]\n                    for item in ll:\n                        ser, v = item\n                        prev_hash, prev_n = ser.split(':')\n                        if prev_hash == tx_hash:\n                            l.remove(item)\n                            self.pruned_txo[ser] = next_tx\n                    if l == []:\n                        dd.pop(addr)\n                    else:\n                        dd[addr] = l\n            try:\n                self.txi.pop(tx_hash)\n                self.txo.pop(tx_hash)\n            except KeyError:\n                self.print_error(\"tx was not in history\", tx_hash)\n\n    def receive_tx_callback(self, tx_hash, tx, tx_height):\n        self.add_transaction(tx_hash, tx)\n        self.add_unverified_tx(tx_hash, tx_height)\n\n    def receive_history_callback(self, addr, hist, tx_fees):\n        with self.lock:\n            old_hist = self.history.get(addr, [])\n            for tx_hash, height in old_hist:\n                if (tx_hash, height) not in hist:\n                    # remove tx if it's not referenced in histories\n                    self.tx_addr_hist[tx_hash].remove(addr)\n                    if not self.tx_addr_hist[tx_hash]:\n                        self.remove_transaction(tx_hash)\n            self.history[addr] = hist\n\n        for tx_hash, tx_height in hist:\n            # add it in case it was previously unconfirmed\n            self.add_unverified_tx(tx_hash, tx_height)\n            # add reference in tx_addr_hist\n            s = self.tx_addr_hist.get(tx_hash, set())\n            s.add(addr)\n            self.tx_addr_hist[tx_hash] = s\n            # if addr is new, we have to recompute txi and txo\n            tx = self.transactions.get(tx_hash)\n            if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None:\n                self.add_transaction(tx_hash, tx)\n\n        # Store fees\n        self.tx_fees.update(tx_fees)\n\n    def get_history(self, domain=None):\n        # get domain\n        if domain is None:\n            domain = self.get_addresses()\n        # 1. Get the history of each address in the domain, maintain the\n        #    delta of a tx as the sum of its deltas on domain addresses\n        tx_deltas = defaultdict(int)\n        for addr in domain:\n            h = self.get_address_history(addr)\n            for tx_hash, height in h:\n                delta = self.get_tx_delta(tx_hash, addr)\n                if delta is None or tx_deltas[tx_hash] is None:\n                    tx_deltas[tx_hash] = None\n                else:\n                    tx_deltas[tx_hash] += delta\n\n        # 2. create sorted history\n        history = []\n        for tx_hash in tx_deltas:\n            delta = tx_deltas[tx_hash]\n            height, conf, timestamp = self.get_tx_height(tx_hash)\n            history.append((tx_hash, height, conf, timestamp, delta))\n        history.sort(key = lambda x: self.get_txpos(x[0]))\n        history.reverse()\n\n        # 3. add balance\n        c, u, x = self.get_balance(domain)\n        balance = c + u + x\n        h2 = []\n        for tx_hash, height, conf, timestamp, delta in history:\n            h2.append((tx_hash, height, conf, timestamp, delta, balance))\n            if balance is None or delta is None:\n                balance = None\n            else:\n                balance -= delta\n        h2.reverse()\n\n        # fixme: this may happen if history is incomplete\n        if balance not in [None, 0]:\n            self.print_error(\"Error: history not synchronized\")\n            return []\n\n        return h2\n\n    def get_label(self, tx_hash):\n        label = self.labels.get(tx_hash, '')\n        if label is '':\n            label = self.get_default_label(tx_hash)\n        return label\n\n    def get_default_label(self, tx_hash):\n        if self.txi.get(tx_hash) == {}:\n            d = self.txo.get(tx_hash, {})\n            labels = []\n            for addr in d.keys():\n                label = self.labels.get(addr)\n                if label:\n                    labels.append(label)\n            return ', '.join(labels)\n        return ''\n\n    def get_tx_status(self, tx_hash, height, conf, timestamp):\n        from .util import format_time\n        if conf == 0:\n            tx = self.transactions.get(tx_hash)\n            if not tx:\n                return 3, 'Unknown'\n            is_final = tx and tx.is_final()\n            fee = self.tx_fees.get(tx_hash)\n            if fee and self.network and self.network.config.has_fee_estimates():\n                size = len(tx.raw)/2\n                low_fee = int(self.network.config.dynfee(0)*size/1000)\n                is_lowfee = fee < low_fee * 0.5\n            else:\n                is_lowfee = False\n            if height==0 and not is_final:\n                status = 0\n            elif height < 0:\n                status = 1\n            elif height == 0 and is_lowfee:\n                status = 2\n            elif height == 0:\n                status = 3\n            else:\n                status = 4\n        else:\n            status = 4 + min(conf, 6)\n        time_str = format_time(timestamp) if timestamp else _(\"Unknown\")\n        status_str = TX_STATUS[status] if status < 5 else time_str\n        return status, status_str\n\n    def relayfee(self):\n        return relayfee(self.network)\n\n    def dust_threshold(self):\n        return dust_threshold(self.network)\n\n    def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None,\n                                  change_addr=None, is_sweep=False):\n        # check outputs\n        i_max = None\n        for i, o in enumerate(outputs):\n            _type, data, value = o\n            if _type == TYPE_ADDRESS:\n                if not is_address(data):\n                    raise BaseException(\"Invalid bitcoin address:\" + data)\n            if value == '!':\n                if i_max is not None:\n                    raise BaseException(\"More than one output set to spend max\")\n                i_max = i\n\n        # Avoid index-out-of-range with inputs[0] below\n        if not inputs:\n            raise NotEnoughFunds()\n\n        if fixed_fee is None and config.fee_per_kb() is None:\n            raise NoDynamicFeeEstimates()\n\n        for item in inputs:\n            self.add_input_info(item)\n\n        # change address\n        if change_addr:\n            change_addrs = [change_addr]\n        else:\n            addrs = self.get_change_addresses()[-self.gap_limit_for_change:]\n            if self.use_change and addrs:\n                # New change addresses are created only after a few\n                # confirmations.  Select the unused addresses within the\n                # gap limit; if none take one at random\n                change_addrs = [addr for addr in addrs if\n                                self.get_num_tx(addr) == 0]\n                if not change_addrs:\n                    change_addrs = [random.choice(addrs)]\n            else:\n                change_addrs = [inputs[0]['address']]\n\n        # Fee estimator\n        if fixed_fee is None:\n            fee_estimator = config.estimate_fee\n        elif isinstance(fixed_fee, Number):\n            fee_estimator = lambda size: fixed_fee\n        elif callable(fixed_fee):\n            fee_estimator = fixed_fee\n        else:\n            raise BaseException('Invalid argument fixed_fee: %s' % fixed_fee)\n\n        if i_max is None:\n            # Let the coin chooser select the coins to spend\n            max_change = self.max_change_outputs if self.multiple_change else 1\n            coin_chooser = coinchooser.get_coin_chooser(config)\n            tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change],\n                                      fee_estimator, self.dust_threshold())\n        else:\n            sendable = sum(map(lambda x:x['value'], inputs))\n            _type, data, value = outputs[i_max]\n            outputs[i_max] = (_type, data, 0)\n            tx = Transaction.from_io(inputs, outputs[:])\n            fee = fee_estimator(tx.estimated_size())\n            amount = max(0, sendable - tx.output_value() - fee)\n            outputs[i_max] = (_type, data, amount)\n            tx = Transaction.from_io(inputs, outputs[:])\n\n        # Sort the inputs and outputs deterministically\n        tx.BIP_LI01_sort()\n        # Timelock tx to current height.\n        tx.locktime = self.get_local_height()\n        run_hook('make_unsigned_transaction', self, tx)\n        return tx\n\n    def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):\n        coins = self.get_spendable_coins(domain, config)\n        tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr)\n        self.sign_transaction(tx, password)\n        return tx\n\n    def is_frozen(self, addr):\n        return addr in self.frozen_addresses\n\n    def set_frozen_state(self, addrs, freeze):\n        '''Set frozen state of the addresses to FREEZE, True or False'''\n        if all(self.is_mine(addr) for addr in addrs):\n            if freeze:\n                self.frozen_addresses |= set(addrs)\n            else:\n                self.frozen_addresses -= set(addrs)\n            self.storage.put('frozen_addresses', list(self.frozen_addresses))\n            return True\n        return False\n\n    def prepare_for_verifier(self):\n        # review transactions that are in the history\n        for addr, hist in self.history.items():\n            for tx_hash, tx_height in hist:\n                # add it in case it was previously unconfirmed\n                self.add_unverified_tx(tx_hash, tx_height)\n\n        # if we are on a pruning server, remove unverified transactions\n        with self.lock:\n            vr = list(self.verified_tx.keys()) + list(self.unverified_tx.keys())\n        for tx_hash in list(self.transactions):\n            if tx_hash not in vr:\n                self.print_error(\"removing transaction\", tx_hash)\n                self.transactions.pop(tx_hash)\n\n    def start_threads(self, network):\n        self.network = network\n        if self.network is not None:\n            self.prepare_for_verifier()\n            self.verifier = SPV(self.network, self)\n            self.synchronizer = Synchronizer(self, network)\n            network.add_jobs([self.verifier, self.synchronizer])\n        else:\n            self.verifier = None\n            self.synchronizer = None\n\n    def stop_threads(self):\n        if self.network:\n            self.network.remove_jobs([self.synchronizer, self.verifier])\n            self.synchronizer.release()\n            self.synchronizer = None\n            self.verifier = None\n            # Now no references to the syncronizer or verifier\n            # remain so they will be GC-ed\n            self.storage.put('stored_height', self.get_local_height())\n        self.save_transactions()\n        self.storage.put('verified_tx3', self.verified_tx)\n        self.storage.write()\n\n    def wait_until_synchronized(self, callback=None):\n        def wait_for_wallet():\n            self.set_up_to_date(False)\n            while not self.is_up_to_date():\n                if callback:\n                    msg = \"%s\\n%s %d\"%(\n                        _(\"Please wait...\"),\n                        _(\"Addresses generated:\"),\n                        len(self.addresses(True)))\n                    callback(msg)\n                time.sleep(0.1)\n        def wait_for_network():\n            while not self.network.is_connected():\n                if callback:\n                    msg = \"%s \\n\" % (_(\"Connecting...\"))\n                    callback(msg)\n                time.sleep(0.1)\n        # wait until we are connected, because the user\n        # might have selected another server\n        if self.network:\n            wait_for_network()\n            wait_for_wallet()\n        else:\n            self.synchronize()\n\n    def can_export(self):\n        return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key')\n\n    def is_used(self, address):\n        h = self.history.get(address,[])\n        c, u, x = self.get_addr_balance(address)\n        return len(h) > 0 and c + u + x == 0\n\n    def is_empty(self, address):\n        c, u, x = self.get_addr_balance(address)\n        return c+u+x == 0\n\n    def address_is_old(self, address, age_limit=2):\n        age = -1\n        h = self.history.get(address, [])\n        for tx_hash, tx_height in h:\n            if tx_height == 0:\n                tx_age = 0\n            else:\n                tx_age = self.get_local_height() - tx_height + 1\n            if tx_age > age:\n                age = tx_age\n        return age > age_limit\n\n    def bump_fee(self, tx, delta):\n        if tx.is_final():\n            raise BaseException(_(\"Cannot bump fee: transaction is final\"))\n        inputs = copy.deepcopy(tx.inputs())\n        outputs = copy.deepcopy(tx.outputs())\n        for txin in inputs:\n            txin['signatures'] = [None] * len(txin['signatures'])\n            self.add_input_info(txin)\n        # use own outputs\n        s = list(filter(lambda x: self.is_mine(x[1]), outputs))\n        # ... unless there is none\n        if not s:\n            s = outputs\n            x_fee = run_hook('get_tx_extra_fee', self, tx)\n            if x_fee:\n                x_fee_address, x_fee_amount = x_fee\n                s = filter(lambda x: x[1]!=x_fee_address, s)\n\n        # prioritize low value outputs, to get rid of dust\n        s = sorted(s, key=lambda x: x[2])\n        for o in s:\n            i = outputs.index(o)\n            otype, address, value = o\n            if value - delta >= self.dust_threshold():\n                outputs[i] = otype, address, value - delta\n                delta = 0\n                break\n            else:\n                del outputs[i]\n                delta -= value\n                if delta > 0:\n                    continue\n        if delta > 0:\n            raise BaseException(_('Cannot bump fee: could not find suitable outputs'))\n        locktime = self.get_local_height()\n        tx_new = Transaction.from_io(inputs, outputs, locktime=locktime)\n        tx_new.BIP_LI01_sort()\n        return tx_new\n\n    def cpfp(self, tx, fee):\n        txid = tx.txid()\n        for i, o in enumerate(tx.outputs()):\n            otype, address, value = o\n            if otype == TYPE_ADDRESS and self.is_mine(address):\n                break\n        else:\n            return\n        coins = self.get_addr_utxo(address)\n        item = coins.get(txid+':%d'%i)\n        if not item:\n            return\n        self.add_input_info(item)\n        inputs = [item]\n        outputs = [(TYPE_ADDRESS, address, value - fee)]\n        locktime = self.get_local_height()\n        # note: no need to call tx.BIP_LI01_sort() here - single input/output\n        return Transaction.from_io(inputs, outputs, locktime=locktime)\n\n    def add_input_info(self, txin):\n        address = txin['address']\n        if self.is_mine(address):\n            txin['type'] = self.get_txin_type(address)\n            # segwit needs value to sign\n            if txin.get('value') is None and Transaction.is_segwit_input(txin):\n                received, spent = self.get_addr_io(address)\n                item = received.get(txin['prevout_hash']+':%d'%txin['prevout_n'])\n                tx_height, value, is_cb = item\n                txin['value'] = value\n            self.add_input_sig_info(txin, address)\n\n    def can_sign(self, tx):\n        if tx.is_complete():\n            return False\n        for k in self.get_keystores():\n            if k.can_sign(tx):\n                return True\n        return False\n\n    def get_input_tx(self, tx_hash):\n        # First look up an input transaction in the wallet where it\n        # will likely be.  If co-signing a transaction it may not have\n        # all the input txs, in which case we ask the network.\n        tx = self.transactions.get(tx_hash)\n        if not tx and self.network:\n            request = ('blockchain.transaction.get', [tx_hash])\n            tx = Transaction(self.network.synchronous_get(request))\n        return tx\n\n    def add_hw_info(self, tx):\n        # add previous tx for hw wallets\n        for txin in tx.inputs():\n            tx_hash = txin['prevout_hash']\n            txin['prev_tx'] = self.get_input_tx(tx_hash)\n        # add output info for hw wallets\n        info = {}\n        xpubs = self.get_master_public_keys()\n        for txout in tx.outputs():\n            _type, addr, amount = txout\n            if self.is_change(addr):\n                index = self.get_address_index(addr)\n                pubkeys = self.get_public_keys(addr)\n                # sort xpubs using the order of pubkeys\n                sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs)))\n                info[addr] = index, sorted_xpubs, self.m if isinstance(self, Multisig_Wallet) else None\n        tx.output_info = info\n\n    def sign_transaction(self, tx, password):\n        if self.is_watching_only():\n            return\n        # hardware wallets require extra info\n        if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]):\n            self.add_hw_info(tx)\n        # sign\n        for k in self.get_keystores():\n            try:\n                if k.can_sign(tx):\n                    k.sign_transaction(tx, password)\n            except UserCancelled:\n                continue\n\n    def get_unused_addresses(self):\n        # fixme: use slots from expired requests\n        domain = self.get_receiving_addresses()\n        return [addr for addr in domain if not self.history.get(addr)\n                and addr not in self.receive_requests.keys()]\n\n    def get_unused_address(self):\n        addrs = self.get_unused_addresses()\n        if addrs:\n            return addrs[0]\n\n    def get_receiving_address(self):\n        # always return an address\n        domain = self.get_receiving_addresses()\n        if not domain:\n            return\n        choice = domain[0]\n        for addr in domain:\n            if not self.history.get(addr):\n                if addr not in self.receive_requests.keys():\n                    return addr\n                else:\n                    choice = addr\n        return choice\n\n    def get_payment_status(self, address, amount):\n        local_height = self.get_local_height()\n        received, sent = self.get_addr_io(address)\n        l = []\n        for txo, x in received.items():\n            h, v, is_cb = x\n            txid, n = txo.split(':')\n            info = self.verified_tx.get(txid)\n            if info:\n                tx_height, timestamp, pos = info\n                conf = local_height - tx_height\n            else:\n                conf = 0\n            l.append((conf, v))\n        vsum = 0\n        for conf, v in reversed(sorted(l)):\n            vsum += v\n            if vsum >= amount:\n                return True, conf\n        return False, None\n\n    def get_payment_request(self, addr, config):\n        r = self.receive_requests.get(addr)\n        if not r:\n            return\n        out = copy.copy(r)\n        out['URI'] = 'bitcoin:' + addr + '?amount=' + format_satoshis(out.get('amount'))\n        status, conf = self.get_request_status(addr)\n        out['status'] = status\n        if conf is not None:\n            out['confirmations'] = conf\n        # check if bip70 file exists\n        rdir = config.get('requests_dir')\n        if rdir:\n            key = out.get('id', addr)\n            path = os.path.join(rdir, 'req', key[0], key[1], key)\n            if os.path.exists(path):\n                baseurl = 'file://' + rdir\n                rewrite = config.get('url_rewrite')\n                if rewrite:\n                    baseurl = baseurl.replace(*rewrite)\n                out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)\n                out['URI'] += '&r=' + out['request_url']\n                out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key\n                websocket_server_announce = config.get('websocket_server_announce')\n                if websocket_server_announce:\n                    out['websocket_server'] = websocket_server_announce\n                else:\n                    out['websocket_server'] = config.get('websocket_server', 'localhost')\n                websocket_port_announce = config.get('websocket_port_announce')\n                if websocket_port_announce:\n                    out['websocket_port'] = websocket_port_announce\n                else:\n                    out['websocket_port'] = config.get('websocket_port', 9999)\n        return out\n\n    def get_request_status(self, key):\n        r = self.receive_requests.get(key)\n        if r is None:\n            return PR_UNKNOWN\n        address = r['address']\n        amount = r.get('amount')\n        timestamp = r.get('time', 0)\n        if timestamp and type(timestamp) != int:\n            timestamp = 0\n        expiration = r.get('exp')\n        if expiration and type(expiration) != int:\n            expiration = 0\n        conf = None\n        if amount:\n            if self.up_to_date:\n                paid, conf = self.get_payment_status(address, amount)\n                status = PR_PAID if paid else PR_UNPAID\n                if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration:\n                    status = PR_EXPIRED\n            else:\n                status = PR_UNKNOWN\n        else:\n            status = PR_UNKNOWN\n        return status, conf\n\n    def make_payment_request(self, addr, amount, message, expiration):\n        timestamp = int(time.time())\n        _id = bh2u(Hash(addr + \"%d\"%timestamp))[0:10]\n        r = {'time':timestamp, 'amount':amount, 'exp':expiration, 'address':addr, 'memo':message, 'id':_id}\n        return r\n\n    def sign_payment_request(self, key, alias, alias_addr, password):\n        req = self.receive_requests.get(key)\n        alias_privkey = self.export_private_key(alias_addr, password)[0]\n        pr = paymentrequest.make_unsigned_request(req)\n        paymentrequest.sign_request_with_alias(pr, alias, alias_privkey)\n        req['name'] = pr.pki_data\n        req['sig'] = bh2u(pr.signature)\n        self.receive_requests[key] = req\n        self.storage.put('payment_requests', self.receive_requests)\n\n    def add_payment_request(self, req, config):\n        addr = req['address']\n        amount = req.get('amount')\n        message = req.get('memo')\n        self.receive_requests[addr] = req\n        self.storage.put('payment_requests', self.receive_requests)\n        self.set_label(addr, message) # should be a default label\n\n        rdir = config.get('requests_dir')\n        if rdir and amount is not None:\n            key = req.get('id', addr)\n            pr = paymentrequest.make_request(config, req)\n            path = os.path.join(rdir, 'req', key[0], key[1], key)\n            if not os.path.exists(path):\n                try:\n                    os.makedirs(path)\n                except OSError as exc:\n                    if exc.errno != errno.EEXIST:\n                        raise\n            with open(os.path.join(path, key), 'wb') as f:\n                f.write(pr.SerializeToString())\n            # reload\n            req = self.get_payment_request(addr, config)\n            with open(os.path.join(path, key + '.json'), 'w') as f:\n                f.write(json.dumps(req))\n        return req\n\n    def remove_payment_request(self, addr, config):\n        if addr not in self.receive_requests:\n            return False\n        r = self.receive_requests.pop(addr)\n        rdir = config.get('requests_dir')\n        if rdir:\n            key = r.get('id', addr)\n            for s in ['.json', '']:\n                n = os.path.join(rdir, 'req', key[0], key[1], key, key + s)\n                if os.path.exists(n):\n                    os.unlink(n)\n        self.storage.put('payment_requests', self.receive_requests)\n        return True\n\n    def get_sorted_requests(self, config):\n        def f(x):\n            try:\n                addr = x.get('address')\n                return self.get_address_index(addr) or addr\n            except:\n                return addr\n        return sorted(map(lambda x: self.get_payment_request(x, config), self.receive_requests.keys()), key=f)\n\n    def get_fingerprint(self):\n        raise NotImplementedError()\n\n    def can_import_privkey(self):\n        return False\n\n    def can_import_address(self):\n        return False\n\n    def can_delete_address(self):\n        return False\n\n    def add_address(self, address):\n        if address not in self.history:\n            self.history[address] = []\n        if self.synchronizer:\n            self.synchronizer.add(address)\n\n    def has_password(self):\n        return self.storage.get('use_encryption', False)\n\n    def check_password(self, password):\n        self.keystore.check_password(password)\n\n    def sign_message(self, address, message, password):\n        index = self.get_address_index(address)\n        return self.keystore.sign_message(index, message, password)\n\n    def decrypt_message(self, pubkey, message, password):\n        addr = self.pubkeys_to_address(pubkey)\n        index = self.get_address_index(addr)\n        return self.keystore.decrypt_message(index, message, password)\n\n\nclass Simple_Wallet(Abstract_Wallet):\n    # wallet with a single keystore\n\n    def get_keystore(self):\n        return self.keystore\n\n    def get_keystores(self):\n        return [self.keystore]\n\n    def is_watching_only(self):\n        return self.keystore.is_watching_only()\n\n    def can_change_password(self):\n        return self.keystore.can_change_password()\n\n    def update_password(self, old_pw, new_pw, encrypt=False):\n        if old_pw is None and self.has_password():\n            raise InvalidPassword()\n        self.keystore.update_password(old_pw, new_pw)\n        self.save_keystore()\n        self.storage.set_password(new_pw, encrypt)\n        self.storage.write()\n\n    def save_keystore(self):\n        self.storage.put('keystore', self.keystore.dump())\n\n\nclass Imported_Wallet(Simple_Wallet):\n    # wallet made of imported addresses\n\n    wallet_type = 'imported'\n    txin_type = 'address'\n\n    def __init__(self, storage):\n        Abstract_Wallet.__init__(self, storage)\n\n    def is_watching_only(self):\n        return self.keystore is None\n\n    def get_keystores(self):\n        return [self.keystore] if self.keystore else []\n\n    def can_import_privkey(self):\n        return bool(self.keystore)\n\n    def load_keystore(self):\n        self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None\n\n    def save_keystore(self):\n        self.storage.put('keystore', self.keystore.dump())\n\n    def load_addresses(self):\n        self.addresses = self.storage.get('addresses', {})\n        # fixme: a reference to addresses is needed\n        if self.keystore:\n            self.keystore.addresses = self.addresses\n\n    def save_addresses(self):\n        self.storage.put('addresses', self.addresses)\n\n    def can_change_password(self):\n        return not self.is_watching_only()\n\n    def can_import_address(self):\n        return self.is_watching_only()\n\n    def can_delete_address(self):\n        return True\n\n    def has_seed(self):\n        return False\n\n    def is_deterministic(self):\n        return False\n\n    def is_change(self, address):\n        return False\n\n    def get_master_public_keys(self):\n        return []\n\n    def is_beyond_limit(self, address, is_change):\n        return False\n\n    def get_fingerprint(self):\n        return ''\n\n    def get_addresses(self, include_change=False):\n        return sorted(self.addresses.keys())\n\n    def get_receiving_addresses(self):\n        return self.get_addresses()\n\n    def get_change_addresses(self):\n        return []\n\n    def import_address(self, address):\n        if not bitcoin.is_address(address):\n            return ''\n        if address in self.addresses:\n            return ''\n        self.addresses[address] = {}\n        self.storage.put('addresses', self.addresses)\n        self.storage.write()\n        self.add_address(address)\n        return address\n\n    def delete_address(self, address):\n        if address not in self.addresses:\n            return\n\n        transactions_to_remove = set()  # only referred to by this address\n        transactions_new = set()  # txs that are not only referred to by address\n        with self.lock:\n            for addr, details in self.history.items():\n                if addr == address:\n                    for tx_hash, height in details:\n                        transactions_to_remove.add(tx_hash)\n                else:\n                    for tx_hash, height in details:\n                        transactions_new.add(tx_hash)\n            transactions_to_remove -= transactions_new\n            self.history.pop(address, None)\n\n            for tx_hash in transactions_to_remove:\n                self.remove_transaction(tx_hash)\n                self.tx_fees.pop(tx_hash, None)\n                self.verified_tx.pop(tx_hash, None)\n                self.unverified_tx.pop(tx_hash, None)\n                self.transactions.pop(tx_hash, None)\n                # FIXME: what about pruned_txo?\n\n        self.storage.put('verified_tx3', self.verified_tx)\n        self.save_transactions()\n\n        self.set_label(address, None)\n        self.remove_payment_request(address, {})\n        self.set_frozen_state([address], False)\n\n        pubkey = self.get_public_key(address)\n        self.addresses.pop(address)\n        if pubkey:\n            self.keystore.delete_imported_key(pubkey)\n            self.save_keystore()\n        self.storage.put('addresses', self.addresses)\n\n        self.storage.write()\n\n    def get_address_index(self, address):\n        return self.get_public_key(address)\n\n    def get_public_key(self, address):\n        return self.addresses[address].get('pubkey')\n\n    def import_private_key(self, sec, pw, redeem_script=None):\n        try:\n            txin_type, pubkey = self.keystore.import_privkey(sec, pw)\n        except Exception:\n            raise BaseException('Invalid private key', sec)\n        if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:\n            if redeem_script is not None:\n                raise BaseException('Cannot use redeem script with', txin_type, sec)\n            addr = bitcoin.pubkey_to_address(txin_type, pubkey)\n        elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:\n            if redeem_script is None:\n                raise BaseException('Redeem script required for', txin_type, sec)\n            addr = bitcoin.redeem_script_to_address(txin_type, redeem_script)\n        else:\n            raise NotImplementedError(txin_type)\n        self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':redeem_script}\n        self.save_keystore()\n        self.save_addresses()\n        self.storage.write()\n        self.add_address(addr)\n        return addr\n\n    def export_private_key(self, address, password):\n        d = self.addresses[address]\n        pubkey = d['pubkey']\n        redeem_script = d['redeem_script']\n        sec = pw_decode(self.keystore.keypairs[pubkey], password)\n        return sec, redeem_script\n\n    def get_txin_type(self, address):\n        return self.addresses[address].get('type', 'address')\n\n    def add_input_sig_info(self, txin, address):\n        if self.is_watching_only():\n            x_pubkey = 'fd' + address_to_script(address)\n            txin['x_pubkeys'] = [x_pubkey]\n            txin['signatures'] = [None]\n            return\n        if txin['type'] in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:\n            pubkey = self.addresses[address]['pubkey']\n            txin['num_sig'] = 1\n            txin['x_pubkeys'] = [pubkey]\n            txin['signatures'] = [None]\n        else:\n            redeem_script = self.addresses[address]['redeem_script']\n            num_sig = 2\n            num_keys = 3\n            txin['num_sig'] = num_sig\n            txin['redeem_script'] = redeem_script\n            txin['signatures'] = [None] * num_keys\n\n    def pubkeys_to_address(self, pubkey):\n        for addr, v in self.addresses.items():\n            if v.get('pubkey') == pubkey:\n                return addr\n\nclass Deterministic_Wallet(Abstract_Wallet):\n\n    def __init__(self, storage):\n        Abstract_Wallet.__init__(self, storage)\n        self.gap_limit = storage.get('gap_limit', 20)\n\n    def has_seed(self):\n        return self.keystore.has_seed()\n\n    def is_deterministic(self):\n        return self.keystore.is_deterministic()\n\n    def get_receiving_addresses(self):\n        return self.receiving_addresses\n\n    def get_change_addresses(self):\n        return self.change_addresses\n\n    def get_seed(self, password):\n        return self.keystore.get_seed(password)\n\n    def add_seed(self, seed, pw):\n        self.keystore.add_seed(seed, pw)\n\n    def change_gap_limit(self, value):\n        '''This method is not called in the code, it is kept for console use'''\n        if value >= self.gap_limit:\n            self.gap_limit = value\n            self.storage.put('gap_limit', self.gap_limit)\n            return True\n        elif value >= self.min_acceptable_gap():\n            addresses = self.get_receiving_addresses()\n            k = self.num_unused_trailing_addresses(addresses)\n            n = len(addresses) - k + value\n            self.receiving_addresses = self.receiving_addresses[0:n]\n            self.gap_limit = value\n            self.storage.put('gap_limit', self.gap_limit)\n            self.save_addresses()\n            return True\n        else:\n            return False\n\n    def num_unused_trailing_addresses(self, addresses):\n        k = 0\n        for a in addresses[::-1]:\n            if self.history.get(a):break\n            k = k + 1\n        return k\n\n    def min_acceptable_gap(self):\n        # fixme: this assumes wallet is synchronized\n        n = 0\n        nmax = 0\n        addresses = self.get_receiving_addresses()\n        k = self.num_unused_trailing_addresses(addresses)\n        for a in addresses[0:-k]:\n            if self.history.get(a):\n                n = 0\n            else:\n                n += 1\n                if n > nmax: nmax = n\n        return nmax + 1\n\n    def create_new_address(self, for_change=False):\n        assert type(for_change) is bool\n        addr_list = self.change_addresses if for_change else self.receiving_addresses\n        n = len(addr_list)\n        x = self.derive_pubkeys(for_change, n)\n        address = self.pubkeys_to_address(x)\n        addr_list.append(address)\n        self.save_addresses()\n        self.add_address(address)\n        return address\n\n    def synchronize_sequence(self, for_change):\n        limit = self.gap_limit_for_change if for_change else self.gap_limit\n        while True:\n            addresses = self.get_change_addresses() if for_change else self.get_receiving_addresses()\n            if len(addresses) < limit:\n                self.create_new_address(for_change)\n                continue\n            if list(map(lambda a: self.address_is_old(a), addresses[-limit:] )) == limit*[False]:\n                break\n            else:\n                self.create_new_address(for_change)\n\n    def synchronize(self):\n        with self.lock:\n            if self.is_deterministic():\n                self.synchronize_sequence(False)\n                self.synchronize_sequence(True)\n            else:\n                if len(self.receiving_addresses) != len(self.keystore.keypairs):\n                    pubkeys = self.keystore.keypairs.keys()\n                    self.receiving_addresses = [self.pubkeys_to_address(i) for i in pubkeys]\n                    self.save_addresses()\n                    for addr in self.receiving_addresses:\n                        self.add_address(addr)\n\n    def is_beyond_limit(self, address, is_change):\n        addr_list = self.get_change_addresses() if is_change else self.get_receiving_addresses()\n        i = addr_list.index(address)\n        prev_addresses = addr_list[:max(0, i)]\n        limit = self.gap_limit_for_change if is_change else self.gap_limit\n        if len(prev_addresses) < limit:\n            return False\n        prev_addresses = prev_addresses[max(0, i - limit):]\n        for addr in prev_addresses:\n            if self.history.get(addr):\n                return False\n        return True\n\n    def get_master_public_keys(self):\n        return [self.get_master_public_key()]\n\n    def get_fingerprint(self):\n        return self.get_master_public_key()\n\n    def get_txin_type(self, address):\n        return self.txin_type\n\n\nclass Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet):\n\n    \"\"\" Deterministic Wallet with a single pubkey per address \"\"\"\n\n    def __init__(self, storage):\n        Deterministic_Wallet.__init__(self, storage)\n\n    def get_public_key(self, address):\n        sequence = self.get_address_index(address)\n        pubkey = self.get_pubkey(*sequence)\n        return pubkey\n\n    def load_keystore(self):\n        self.keystore = load_keystore(self.storage, 'keystore')\n        try:\n            xtype = bitcoin.xpub_type(self.keystore.xpub)\n        except:\n            xtype = 'standard'\n        self.txin_type = 'p2pkh' if xtype == 'standard' else xtype\n\n    def get_pubkey(self, c, i):\n        return self.derive_pubkeys(c, i)\n\n    def get_public_keys(self, address):\n        return [self.get_public_key(address)]\n\n    def add_input_sig_info(self, txin, address):\n        derivation = self.get_address_index(address)\n        x_pubkey = self.keystore.get_xpubkey(*derivation)\n        txin['x_pubkeys'] = [x_pubkey]\n        txin['signatures'] = [None]\n        txin['num_sig'] = 1\n\n    def get_master_public_key(self):\n        return self.keystore.get_master_public_key()\n\n    def derive_pubkeys(self, c, i):\n        return self.keystore.derive_pubkey(c, i)\n\n\n\n\n\n\nclass Standard_Wallet(Simple_Deterministic_Wallet):\n    wallet_type = 'standard'\n\n    def pubkeys_to_address(self, pubkey):\n        return bitcoin.pubkey_to_address(self.txin_type, pubkey)\n\n\nclass Multisig_Wallet(Deterministic_Wallet):\n    # generic m of n\n    gap_limit = 20\n\n    def __init__(self, storage):\n        self.wallet_type = storage.get('wallet_type')\n        self.m, self.n = multisig_type(self.wallet_type)\n        Deterministic_Wallet.__init__(self, storage)\n\n    def get_pubkeys(self, c, i):\n        return self.derive_pubkeys(c, i)\n\n    def pubkeys_to_address(self, pubkeys):\n        redeem_script = self.pubkeys_to_redeem_script(pubkeys)\n        return bitcoin.redeem_script_to_address(self.txin_type, redeem_script)\n\n    def pubkeys_to_redeem_script(self, pubkeys):\n        return transaction.multisig_script(sorted(pubkeys), self.m)\n\n    def derive_pubkeys(self, c, i):\n        return [k.derive_pubkey(c, i) for k in self.get_keystores()]\n\n    def load_keystore(self):\n        self.keystores = {}\n        for i in range(self.n):\n            name = 'x%d/'%(i+1)\n            self.keystores[name] = load_keystore(self.storage, name)\n        self.keystore = self.keystores['x1/']\n        xtype = bitcoin.xpub_type(self.keystore.xpub)\n        self.txin_type = 'p2sh' if xtype == 'standard' else xtype\n\n    def save_keystore(self):\n        for name, k in self.keystores.items():\n            self.storage.put(name, k.dump())\n\n    def get_keystore(self):\n        return self.keystores.get('x1/')\n\n    def get_keystores(self):\n        return [self.keystores[i] for i in sorted(self.keystores.keys())]\n\n    def update_password(self, old_pw, new_pw, encrypt=False):\n        if old_pw is None and self.has_password():\n            raise InvalidPassword()\n        for name, keystore in self.keystores.items():\n            if keystore.can_change_password():\n                keystore.update_password(old_pw, new_pw)\n                self.storage.put(name, keystore.dump())\n        self.storage.set_password(new_pw, encrypt)\n        self.storage.write()\n\n    def has_seed(self):\n        return self.keystore.has_seed()\n\n    def can_change_password(self):\n        return self.keystore.can_change_password()\n\n    def is_watching_only(self):\n        return not any([not k.is_watching_only() for k in self.get_keystores()])\n\n    def get_master_public_key(self):\n        return self.keystore.get_master_public_key()\n\n    def get_master_public_keys(self):\n        return [k.get_master_public_key() for k in self.get_keystores()]\n\n    def get_fingerprint(self):\n        return ''.join(sorted(self.get_master_public_keys()))\n\n    def add_input_sig_info(self, txin, address):\n        # x_pubkeys are not sorted here because it would be too slow\n        # they are sorted in transaction.get_sorted_pubkeys\n        # pubkeys is set to None to signal that x_pubkeys are unsorted\n        derivation = self.get_address_index(address)\n        txin['x_pubkeys'] = [k.get_xpubkey(*derivation) for k in self.get_keystores()]\n        txin['pubkeys'] = None\n        # we need n place holders\n        txin['signatures'] = [None] * self.n\n        txin['num_sig'] = self.m\n\n\nwallet_types = ['standard', 'multisig', 'imported']\n\ndef register_wallet_type(category):\n    wallet_types.append(category)\n\nwallet_constructors = {\n    'standard': Standard_Wallet,\n    'old': Standard_Wallet,\n    'xpub': Standard_Wallet,\n    'imported': Imported_Wallet\n}\n\ndef register_constructor(wallet_type, constructor):\n    wallet_constructors[wallet_type] = constructor\n\n# former WalletFactory\nclass Wallet(object):\n    \"\"\"The main wallet \"entry point\".\n    This class is actually a factory that will return a wallet of the correct\n    type when passed a WalletStorage instance.\"\"\"\n\n    def __new__(self, storage):\n        wallet_type = storage.get('wallet_type')\n        WalletClass = Wallet.wallet_class(wallet_type)\n        wallet = WalletClass(storage)\n        # Convert hardware wallets restored with older versions of\n        # Electrum to BIP44 wallets.  A hardware wallet does not have\n        # a seed and plugins do not need to handle having one.\n        rwc = getattr(wallet, 'restore_wallet_class', None)\n        if rwc and storage.get('seed', ''):\n            storage.print_error(\"converting wallet type to \" + rwc.wallet_type)\n            storage.put('wallet_type', rwc.wallet_type)\n            wallet = rwc(storage)\n        return wallet\n\n    @staticmethod\n    def wallet_class(wallet_type):\n        if multisig_type(wallet_type):\n            return Multisig_Wallet\n        if wallet_type in wallet_constructors:\n            return wallet_constructors[wallet_type]\n        raise RuntimeError(\"Unknown wallet type: \" + wallet_type)\n\n"
  },
  {
    "path": "lib/websockets.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nimport queue\nimport threading, os, json\nfrom collections import defaultdict\ntry:\n    from SimpleWebSocketServer import WebSocket, SimpleSSLWebSocketServer\nexcept ImportError:\n    import sys\n    sys.exit(\"install SimpleWebSocketServer\")\n\nfrom . import util\n\nrequest_queue = queue.Queue()\n\nclass ElectrumWebSocket(WebSocket):\n\n    def handleMessage(self):\n        assert self.data[0:3] == 'id:'\n        util.print_error(\"message received\", self.data)\n        request_id = self.data[3:]\n        request_queue.put((self, request_id))\n\n    def handleConnected(self):\n        util.print_error(\"connected\", self.address)\n\n    def handleClose(self):\n        util.print_error(\"closed\", self.address)\n\n\n\nclass WsClientThread(util.DaemonThread):\n\n    def __init__(self, config, network):\n        util.DaemonThread.__init__(self)\n        self.network = network\n        self.config = config\n        self.response_queue = queue.Queue()\n        self.subscriptions = defaultdict(list)\n\n    def make_request(self, request_id):\n        # read json file\n        rdir = self.config.get('requests_dir')\n        n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json')\n        with open(n) as f:\n            s = f.read()\n        d = json.loads(s)\n        addr = d.get('address')\n        amount = d.get('amount')\n        return addr, amount\n\n    def reading_thread(self):\n        while self.is_running():\n            try:\n                ws, request_id = request_queue.get()\n            except queue.Empty:\n                continue\n            try:\n                addr, amount = self.make_request(request_id)\n            except:\n                continue\n            l = self.subscriptions.get(addr, [])\n            l.append((ws, amount))\n            self.subscriptions[addr] = l\n            self.network.send([('blockchain.address.subscribe', [addr])], self.response_queue.put)\n\n\n    def run(self):\n        threading.Thread(target=self.reading_thread).start()\n        while self.is_running():\n            try:\n                r = self.response_queue.get(timeout=0.1)\n            except queue.Empty:\n                continue\n            util.print_error('response', r)\n            method = r.get('method')\n            params = r.get('params')\n            result = r.get('result')\n            if result is None:\n                continue    \n            if method == 'blockchain.address.subscribe':\n                self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put)\n            elif method == 'blockchain.address.get_balance':\n                addr = params[0]\n                l = self.subscriptions.get(addr, [])\n                for ws, amount in l:\n                    if not ws.closed:\n                        if sum(result.values()) >=amount:\n                            ws.sendMessage('paid')\n\n\n\nclass WebSocketServer(threading.Thread):\n\n    def __init__(self, config, ns):\n        threading.Thread.__init__(self)\n        self.config = config\n        self.net_server = ns\n        self.daemon = True\n\n    def run(self):\n        t = WsClientThread(self.config, self.net_server)\n        t.start()\n\n        host = self.config.get('websocket_server')\n        port = self.config.get('websocket_port', 9999)\n        certfile = self.config.get('ssl_chain')\n        keyfile = self.config.get('ssl_privkey')\n        self.server = SimpleSSLWebSocketServer(host, port, ElectrumWebSocket, certfile, keyfile)\n        self.server.serveforever()\n\n\n"
  },
  {
    "path": "lib/wordlist/chinese_simplified.txt",
    "content": "的\n一\n是\n在\n不\n了\n有\n和\n人\n这\n中\n大\n为\n上\n个\n国\n我\n以\n要\n他\n时\n来\n用\n们\n生\n到\n作\n地\n于\n出\n就\n分\n对\n成\n会\n可\n主\n发\n年\n动\n同\n工\n也\n能\n下\n过\n子\n说\n产\n种\n面\n而\n方\n后\n多\n定\n行\n学\n法\n所\n民\n得\n经\n十\n三\n之\n进\n着\n等\n部\n度\n家\n电\n力\n里\n如\n水\n化\n高\n自\n二\n理\n起\n小\n物\n现\n实\n加\n量\n都\n两\n体\n制\n机\n当\n使\n点\n从\n业\n本\n去\n把\n性\n好\n应\n开\n它\n合\n还\n因\n由\n其\n些\n然\n前\n外\n天\n政\n四\n日\n那\n社\n义\n事\n平\n形\n相\n全\n表\n间\n样\n与\n关\n各\n重\n新\n线\n内\n数\n正\n心\n反\n你\n明\n看\n原\n又\n么\n利\n比\n或\n但\n质\n气\n第\n向\n道\n命\n此\n变\n条\n只\n没\n结\n解\n问\n意\n建\n月\n公\n无\n系\n军\n很\n情\n者\n最\n立\n代\n想\n已\n通\n并\n提\n直\n题\n党\n程\n展\n五\n果\n料\n象\n员\n革\n位\n入\n常\n文\n总\n次\n品\n式\n活\n设\n及\n管\n特\n件\n长\n求\n老\n头\n基\n资\n边\n流\n路\n级\n少\n图\n山\n统\n接\n知\n较\n将\n组\n见\n计\n别\n她\n手\n角\n期\n根\n论\n运\n农\n指\n几\n九\n区\n强\n放\n决\n西\n被\n干\n做\n必\n战\n先\n回\n则\n任\n取\n据\n处\n队\n南\n给\n色\n光\n门\n即\n保\n治\n北\n造\n百\n规\n热\n领\n七\n海\n口\n东\n导\n器\n压\n志\n世\n金\n增\n争\n济\n阶\n油\n思\n术\n极\n交\n受\n联\n什\n认\n六\n共\n权\n收\n证\n改\n清\n美\n再\n采\n转\n更\n单\n风\n切\n打\n白\n教\n速\n花\n带\n安\n场\n身\n车\n例\n真\n务\n具\n万\n每\n目\n至\n达\n走\n积\n示\n议\n声\n报\n斗\n完\n类\n八\n离\n华\n名\n确\n才\n科\n张\n信\n马\n节\n话\n米\n整\n空\n元\n况\n今\n集\n温\n传\n土\n许\n步\n群\n广\n石\n记\n需\n段\n研\n界\n拉\n林\n律\n叫\n且\n究\n观\n越\n织\n装\n影\n算\n低\n持\n音\n众\n书\n布\n复\n容\n儿\n须\n际\n商\n非\n验\n连\n断\n深\n难\n近\n矿\n千\n周\n委\n素\n技\n备\n半\n办\n青\n省\n列\n习\n响\n约\n支\n般\n史\n感\n劳\n便\n团\n往\n酸\n历\n市\n克\n何\n除\n消\n构\n府\n称\n太\n准\n精\n值\n号\n率\n族\n维\n划\n选\n标\n写\n存\n候\n毛\n亲\n快\n效\n斯\n院\n查\n江\n型\n眼\n王\n按\n格\n养\n易\n置\n派\n层\n片\n始\n却\n专\n状\n育\n厂\n京\n识\n适\n属\n圆\n包\n火\n住\n调\n满\n县\n局\n照\n参\n红\n细\n引\n听\n该\n铁\n价\n严\n首\n底\n液\n官\n德\n随\n病\n苏\n失\n尔\n死\n讲\n配\n女\n黄\n推\n显\n谈\n罪\n神\n艺\n呢\n席\n含\n企\n望\n密\n批\n营\n项\n防\n举\n球\n英\n氧\n势\n告\n李\n台\n落\n木\n帮\n轮\n破\n亚\n师\n围\n注\n远\n字\n材\n排\n供\n河\n态\n封\n另\n施\n减\n树\n溶\n怎\n止\n案\n言\n士\n均\n武\n固\n叶\n鱼\n波\n视\n仅\n费\n紧\n爱\n左\n章\n早\n朝\n害\n续\n轻\n服\n试\n食\n充\n兵\n源\n判\n护\n司\n足\n某\n练\n差\n致\n板\n田\n降\n黑\n犯\n负\n击\n范\n继\n兴\n似\n余\n坚\n曲\n输\n修\n故\n城\n夫\n够\n送\n笔\n船\n占\n右\n财\n吃\n富\n春\n职\n觉\n汉\n画\n功\n巴\n跟\n虽\n杂\n飞\n检\n吸\n助\n升\n阳\n互\n初\n创\n抗\n考\n投\n坏\n策\n古\n径\n换\n未\n跑\n留\n钢\n曾\n端\n责\n站\n简\n述\n钱\n副\n尽\n帝\n射\n草\n冲\n承\n独\n令\n限\n阿\n宣\n环\n双\n请\n超\n微\n让\n控\n州\n良\n轴\n找\n否\n纪\n益\n依\n优\n顶\n础\n载\n倒\n房\n突\n坐\n粉\n敌\n略\n客\n袁\n冷\n胜\n绝\n析\n块\n剂\n测\n丝\n协\n诉\n念\n陈\n仍\n罗\n盐\n友\n洋\n错\n苦\n夜\n刑\n移\n频\n逐\n靠\n混\n母\n短\n皮\n终\n聚\n汽\n村\n云\n哪\n既\n距\n卫\n停\n烈\n央\n察\n烧\n迅\n境\n若\n印\n洲\n刻\n括\n激\n孔\n搞\n甚\n室\n待\n核\n校\n散\n侵\n吧\n甲\n游\n久\n菜\n味\n旧\n模\n湖\n货\n损\n预\n阻\n毫\n普\n稳\n乙\n妈\n植\n息\n扩\n银\n语\n挥\n酒\n守\n拿\n序\n纸\n医\n缺\n雨\n吗\n针\n刘\n啊\n急\n唱\n误\n训\n愿\n审\n附\n获\n茶\n鲜\n粮\n斤\n孩\n脱\n硫\n肥\n善\n龙\n演\n父\n渐\n血\n欢\n械\n掌\n歌\n沙\n刚\n攻\n谓\n盾\n讨\n晚\n粒\n乱\n燃\n矛\n乎\n杀\n药\n宁\n鲁\n贵\n钟\n煤\n读\n班\n伯\n香\n介\n迫\n句\n丰\n培\n握\n兰\n担\n弦\n蛋\n沉\n假\n穿\n执\n答\n乐\n谁\n顺\n烟\n缩\n征\n脸\n喜\n松\n脚\n困\n异\n免\n背\n星\n福\n买\n染\n井\n概\n慢\n怕\n磁\n倍\n祖\n皇\n促\n静\n补\n评\n翻\n肉\n践\n尼\n衣\n宽\n扬\n棉\n希\n伤\n操\n垂\n秋\n宜\n氢\n套\n督\n振\n架\n亮\n末\n宪\n庆\n编\n牛\n触\n映\n雷\n销\n诗\n座\n居\n抓\n裂\n胞\n呼\n娘\n景\n威\n绿\n晶\n厚\n盟\n衡\n鸡\n孙\n延\n危\n胶\n屋\n乡\n临\n陆\n顾\n掉\n呀\n灯\n岁\n措\n束\n耐\n剧\n玉\n赵\n跳\n哥\n季\n课\n凯\n胡\n额\n款\n绍\n卷\n齐\n伟\n蒸\n殖\n永\n宗\n苗\n川\n炉\n岩\n弱\n零\n杨\n奏\n沿\n露\n杆\n探\n滑\n镇\n饭\n浓\n航\n怀\n赶\n库\n夺\n伊\n灵\n税\n途\n灭\n赛\n归\n召\n鼓\n播\n盘\n裁\n险\n康\n唯\n录\n菌\n纯\n借\n糖\n盖\n横\n符\n私\n努\n堂\n域\n枪\n润\n幅\n哈\n竟\n熟\n虫\n泽\n脑\n壤\n碳\n欧\n遍\n侧\n寨\n敢\n彻\n虑\n斜\n薄\n庭\n纳\n弹\n饲\n伸\n折\n麦\n湿\n暗\n荷\n瓦\n塞\n床\n筑\n恶\n户\n访\n塔\n奇\n透\n梁\n刀\n旋\n迹\n卡\n氯\n遇\n份\n毒\n泥\n退\n洗\n摆\n灰\n彩\n卖\n耗\n夏\n择\n忙\n铜\n献\n硬\n予\n繁\n圈\n雪\n函\n亦\n抽\n篇\n阵\n阴\n丁\n尺\n追\n堆\n雄\n迎\n泛\n爸\n楼\n避\n谋\n吨\n野\n猪\n旗\n累\n偏\n典\n馆\n索\n秦\n脂\n潮\n爷\n豆\n忽\n托\n惊\n塑\n遗\n愈\n朱\n替\n纤\n粗\n倾\n尚\n痛\n楚\n谢\n奋\n购\n磨\n君\n池\n旁\n碎\n骨\n监\n捕\n弟\n暴\n割\n贯\n殊\n释\n词\n亡\n壁\n顿\n宝\n午\n尘\n闻\n揭\n炮\n残\n冬\n桥\n妇\n警\n综\n招\n吴\n付\n浮\n遭\n徐\n您\n摇\n谷\n赞\n箱\n隔\n订\n男\n吹\n园\n纷\n唐\n败\n宋\n玻\n巨\n耕\n坦\n荣\n闭\n湾\n键\n凡\n驻\n锅\n救\n恩\n剥\n凝\n碱\n齿\n截\n炼\n麻\n纺\n禁\n废\n盛\n版\n缓\n净\n睛\n昌\n婚\n涉\n筒\n嘴\n插\n岸\n朗\n庄\n街\n藏\n姑\n贸\n腐\n奴\n啦\n惯\n乘\n伙\n恢\n匀\n纱\n扎\n辩\n耳\n彪\n臣\n亿\n璃\n抵\n脉\n秀\n萨\n俄\n网\n舞\n店\n喷\n纵\n寸\n汗\n挂\n洪\n贺\n闪\n柬\n爆\n烯\n津\n稻\n墙\n软\n勇\n像\n滚\n厘\n蒙\n芳\n肯\n坡\n柱\n荡\n腿\n仪\n旅\n尾\n轧\n冰\n贡\n登\n黎\n削\n钻\n勒\n逃\n障\n氨\n郭\n峰\n币\n港\n伏\n轨\n亩\n毕\n擦\n莫\n刺\n浪\n秘\n援\n株\n健\n售\n股\n岛\n甘\n泡\n睡\n童\n铸\n汤\n阀\n休\n汇\n舍\n牧\n绕\n炸\n哲\n磷\n绩\n朋\n淡\n尖\n启\n陷\n柴\n呈\n徒\n颜\n泪\n稍\n忘\n泵\n蓝\n拖\n洞\n授\n镜\n辛\n壮\n锋\n贫\n虚\n弯\n摩\n泰\n幼\n廷\n尊\n窗\n纲\n弄\n隶\n疑\n氏\n宫\n姐\n震\n瑞\n怪\n尤\n琴\n循\n描\n膜\n违\n夹\n腰\n缘\n珠\n穷\n森\n枝\n竹\n沟\n催\n绳\n忆\n邦\n剩\n幸\n浆\n栏\n拥\n牙\n贮\n礼\n滤\n钠\n纹\n罢\n拍\n咱\n喊\n袖\n埃\n勤\n罚\n焦\n潜\n伍\n墨\n欲\n缝\n姓\n刊\n饱\n仿\n奖\n铝\n鬼\n丽\n跨\n默\n挖\n链\n扫\n喝\n袋\n炭\n污\n幕\n诸\n弧\n励\n梅\n奶\n洁\n灾\n舟\n鉴\n苯\n讼\n抱\n毁\n懂\n寒\n智\n埔\n寄\n届\n跃\n渡\n挑\n丹\n艰\n贝\n碰\n拔\n爹\n戴\n码\n梦\n芽\n熔\n赤\n渔\n哭\n敬\n颗\n奔\n铅\n仲\n虎\n稀\n妹\n乏\n珍\n申\n桌\n遵\n允\n隆\n螺\n仓\n魏\n锐\n晓\n氮\n兼\n隐\n碍\n赫\n拨\n忠\n肃\n缸\n牵\n抢\n博\n巧\n壳\n兄\n杜\n讯\n诚\n碧\n祥\n柯\n页\n巡\n矩\n悲\n灌\n龄\n伦\n票\n寻\n桂\n铺\n圣\n恐\n恰\n郑\n趣\n抬\n荒\n腾\n贴\n柔\n滴\n猛\n阔\n辆\n妻\n填\n撤\n储\n签\n闹\n扰\n紫\n砂\n递\n戏\n吊\n陶\n伐\n喂\n疗\n瓶\n婆\n抚\n臂\n摸\n忍\n虾\n蜡\n邻\n胸\n巩\n挤\n偶\n弃\n槽\n劲\n乳\n邓\n吉\n仁\n烂\n砖\n租\n乌\n舰\n伴\n瓜\n浅\n丙\n暂\n燥\n橡\n柳\n迷\n暖\n牌\n秧\n胆\n详\n簧\n踏\n瓷\n谱\n呆\n宾\n糊\n洛\n辉\n愤\n竞\n隙\n怒\n粘\n乃\n绪\n肩\n籍\n敏\n涂\n熙\n皆\n侦\n悬\n掘\n享\n纠\n醒\n狂\n锁\n淀\n恨\n牲\n霸\n爬\n赏\n逆\n玩\n陵\n祝\n秒\n浙\n貌\n役\n彼\n悉\n鸭\n趋\n凤\n晨\n畜\n辈\n秩\n卵\n署\n梯\n炎\n滩\n棋\n驱\n筛\n峡\n冒\n啥\n寿\n译\n浸\n泉\n帽\n迟\n硅\n疆\n贷\n漏\n稿\n冠\n嫩\n胁\n芯\n牢\n叛\n蚀\n奥\n鸣\n岭\n羊\n凭\n串\n塘\n绘\n酵\n融\n盆\n锡\n庙\n筹\n冻\n辅\n摄\n袭\n筋\n拒\n僚\n旱\n钾\n鸟\n漆\n沈\n眉\n疏\n添\n棒\n穗\n硝\n韩\n逼\n扭\n侨\n凉\n挺\n碗\n栽\n炒\n杯\n患\n馏\n劝\n豪\n辽\n勃\n鸿\n旦\n吏\n拜\n狗\n埋\n辊\n掩\n饮\n搬\n骂\n辞\n勾\n扣\n估\n蒋\n绒\n雾\n丈\n朵\n姆\n拟\n宇\n辑\n陕\n雕\n偿\n蓄\n崇\n剪\n倡\n厅\n咬\n驶\n薯\n刷\n斥\n番\n赋\n奉\n佛\n浇\n漫\n曼\n扇\n钙\n桃\n扶\n仔\n返\n俗\n亏\n腔\n鞋\n棱\n覆\n框\n悄\n叔\n撞\n骗\n勘\n旺\n沸\n孤\n吐\n孟\n渠\n屈\n疾\n妙\n惜\n仰\n狠\n胀\n谐\n抛\n霉\n桑\n岗\n嘛\n衰\n盗\n渗\n脏\n赖\n涌\n甜\n曹\n阅\n肌\n哩\n厉\n烃\n纬\n毅\n昨\n伪\n症\n煮\n叹\n钉\n搭\n茎\n笼\n酷\n偷\n弓\n锥\n恒\n杰\n坑\n鼻\n翼\n纶\n叙\n狱\n逮\n罐\n络\n棚\n抑\n膨\n蔬\n寺\n骤\n穆\n冶\n枯\n册\n尸\n凸\n绅\n坯\n牺\n焰\n轰\n欣\n晋\n瘦\n御\n锭\n锦\n丧\n旬\n锻\n垄\n搜\n扑\n邀\n亭\n酯\n迈\n舒\n脆\n酶\n闲\n忧\n酚\n顽\n羽\n涨\n卸\n仗\n陪\n辟\n惩\n杭\n姚\n肚\n捉\n飘\n漂\n昆\n欺\n吾\n郎\n烷\n汁\n呵\n饰\n萧\n雅\n邮\n迁\n燕\n撒\n姻\n赴\n宴\n烦\n债\n帐\n斑\n铃\n旨\n醇\n董\n饼\n雏\n姿\n拌\n傅\n腹\n妥\n揉\n贤\n拆\n歪\n葡\n胺\n丢\n浩\n徽\n昂\n垫\n挡\n览\n贪\n慰\n缴\n汪\n慌\n冯\n诺\n姜\n谊\n凶\n劣\n诬\n耀\n昏\n躺\n盈\n骑\n乔\n溪\n丛\n卢\n抹\n闷\n咨\n刮\n驾\n缆\n悟\n摘\n铒\n掷\n颇\n幻\n柄\n惠\n惨\n佳\n仇\n腊\n窝\n涤\n剑\n瞧\n堡\n泼\n葱\n罩\n霍\n捞\n胎\n苍\n滨\n俩\n捅\n湘\n砍\n霞\n邵\n萄\n疯\n淮\n遂\n熊\n粪\n烘\n宿\n档\n戈\n驳\n嫂\n裕\n徙\n箭\n捐\n肠\n撑\n晒\n辨\n殿\n莲\n摊\n搅\n酱\n屏\n疫\n哀\n蔡\n堵\n沫\n皱\n畅\n叠\n阁\n莱\n敲\n辖\n钩\n痕\n坝\n巷\n饿\n祸\n丘\n玄\n溜\n曰\n逻\n彭\n尝\n卿\n妨\n艇\n吞\n韦\n怨\n矮\n歇\n"
  },
  {
    "path": "lib/wordlist/english.txt",
    "content": "abandon\nability\nable\nabout\nabove\nabsent\nabsorb\nabstract\nabsurd\nabuse\naccess\naccident\naccount\naccuse\nachieve\nacid\nacoustic\nacquire\nacross\nact\naction\nactor\nactress\nactual\nadapt\nadd\naddict\naddress\nadjust\nadmit\nadult\nadvance\nadvice\naerobic\naffair\nafford\nafraid\nagain\nage\nagent\nagree\nahead\naim\nair\nairport\naisle\nalarm\nalbum\nalcohol\nalert\nalien\nall\nalley\nallow\nalmost\nalone\nalpha\nalready\nalso\nalter\nalways\namateur\namazing\namong\namount\namused\nanalyst\nanchor\nancient\nanger\nangle\nangry\nanimal\nankle\nannounce\nannual\nanother\nanswer\nantenna\nantique\nanxiety\nany\napart\napology\nappear\napple\napprove\napril\narch\narctic\narea\narena\nargue\narm\narmed\narmor\narmy\naround\narrange\narrest\narrive\narrow\nart\nartefact\nartist\nartwork\nask\naspect\nassault\nasset\nassist\nassume\nasthma\nathlete\natom\nattack\nattend\nattitude\nattract\nauction\naudit\naugust\naunt\nauthor\nauto\nautumn\naverage\navocado\navoid\nawake\naware\naway\nawesome\nawful\nawkward\naxis\nbaby\nbachelor\nbacon\nbadge\nbag\nbalance\nbalcony\nball\nbamboo\nbanana\nbanner\nbar\nbarely\nbargain\nbarrel\nbase\nbasic\nbasket\nbattle\nbeach\nbean\nbeauty\nbecause\nbecome\nbeef\nbefore\nbegin\nbehave\nbehind\nbelieve\nbelow\nbelt\nbench\nbenefit\nbest\nbetray\nbetter\nbetween\nbeyond\nbicycle\nbid\nbike\nbind\nbiology\nbird\nbirth\nbitter\nblack\nblade\nblame\nblanket\nblast\nbleak\nbless\nblind\nblood\nblossom\nblouse\nblue\nblur\nblush\nboard\nboat\nbody\nboil\nbomb\nbone\nbonus\nbook\nboost\nborder\nboring\nborrow\nboss\nbottom\nbounce\nbox\nboy\nbracket\nbrain\nbrand\nbrass\nbrave\nbread\nbreeze\nbrick\nbridge\nbrief\nbright\nbring\nbrisk\nbroccoli\nbroken\nbronze\nbroom\nbrother\nbrown\nbrush\nbubble\nbuddy\nbudget\nbuffalo\nbuild\nbulb\nbulk\nbullet\nbundle\nbunker\nburden\nburger\nburst\nbus\nbusiness\nbusy\nbutter\nbuyer\nbuzz\ncabbage\ncabin\ncable\ncactus\ncage\ncake\ncall\ncalm\ncamera\ncamp\ncan\ncanal\ncancel\ncandy\ncannon\ncanoe\ncanvas\ncanyon\ncapable\ncapital\ncaptain\ncar\ncarbon\ncard\ncargo\ncarpet\ncarry\ncart\ncase\ncash\ncasino\ncastle\ncasual\ncat\ncatalog\ncatch\ncategory\ncattle\ncaught\ncause\ncaution\ncave\nceiling\ncelery\ncement\ncensus\ncentury\ncereal\ncertain\nchair\nchalk\nchampion\nchange\nchaos\nchapter\ncharge\nchase\nchat\ncheap\ncheck\ncheese\nchef\ncherry\nchest\nchicken\nchief\nchild\nchimney\nchoice\nchoose\nchronic\nchuckle\nchunk\nchurn\ncigar\ncinnamon\ncircle\ncitizen\ncity\ncivil\nclaim\nclap\nclarify\nclaw\nclay\nclean\nclerk\nclever\nclick\nclient\ncliff\nclimb\nclinic\nclip\nclock\nclog\nclose\ncloth\ncloud\nclown\nclub\nclump\ncluster\nclutch\ncoach\ncoast\ncoconut\ncode\ncoffee\ncoil\ncoin\ncollect\ncolor\ncolumn\ncombine\ncome\ncomfort\ncomic\ncommon\ncompany\nconcert\nconduct\nconfirm\ncongress\nconnect\nconsider\ncontrol\nconvince\ncook\ncool\ncopper\ncopy\ncoral\ncore\ncorn\ncorrect\ncost\ncotton\ncouch\ncountry\ncouple\ncourse\ncousin\ncover\ncoyote\ncrack\ncradle\ncraft\ncram\ncrane\ncrash\ncrater\ncrawl\ncrazy\ncream\ncredit\ncreek\ncrew\ncricket\ncrime\ncrisp\ncritic\ncrop\ncross\ncrouch\ncrowd\ncrucial\ncruel\ncruise\ncrumble\ncrunch\ncrush\ncry\ncrystal\ncube\nculture\ncup\ncupboard\ncurious\ncurrent\ncurtain\ncurve\ncushion\ncustom\ncute\ncycle\ndad\ndamage\ndamp\ndance\ndanger\ndaring\ndash\ndaughter\ndawn\nday\ndeal\ndebate\ndebris\ndecade\ndecember\ndecide\ndecline\ndecorate\ndecrease\ndeer\ndefense\ndefine\ndefy\ndegree\ndelay\ndeliver\ndemand\ndemise\ndenial\ndentist\ndeny\ndepart\ndepend\ndeposit\ndepth\ndeputy\nderive\ndescribe\ndesert\ndesign\ndesk\ndespair\ndestroy\ndetail\ndetect\ndevelop\ndevice\ndevote\ndiagram\ndial\ndiamond\ndiary\ndice\ndiesel\ndiet\ndiffer\ndigital\ndignity\ndilemma\ndinner\ndinosaur\ndirect\ndirt\ndisagree\ndiscover\ndisease\ndish\ndismiss\ndisorder\ndisplay\ndistance\ndivert\ndivide\ndivorce\ndizzy\ndoctor\ndocument\ndog\ndoll\ndolphin\ndomain\ndonate\ndonkey\ndonor\ndoor\ndose\ndouble\ndove\ndraft\ndragon\ndrama\ndrastic\ndraw\ndream\ndress\ndrift\ndrill\ndrink\ndrip\ndrive\ndrop\ndrum\ndry\nduck\ndumb\ndune\nduring\ndust\ndutch\nduty\ndwarf\ndynamic\neager\neagle\nearly\nearn\nearth\neasily\neast\neasy\necho\necology\neconomy\nedge\nedit\neducate\neffort\negg\neight\neither\nelbow\nelder\nelectric\nelegant\nelement\nelephant\nelevator\nelite\nelse\nembark\nembody\nembrace\nemerge\nemotion\nemploy\nempower\nempty\nenable\nenact\nend\nendless\nendorse\nenemy\nenergy\nenforce\nengage\nengine\nenhance\nenjoy\nenlist\nenough\nenrich\nenroll\nensure\nenter\nentire\nentry\nenvelope\nepisode\nequal\nequip\nera\nerase\nerode\nerosion\nerror\nerupt\nescape\nessay\nessence\nestate\neternal\nethics\nevidence\nevil\nevoke\nevolve\nexact\nexample\nexcess\nexchange\nexcite\nexclude\nexcuse\nexecute\nexercise\nexhaust\nexhibit\nexile\nexist\nexit\nexotic\nexpand\nexpect\nexpire\nexplain\nexpose\nexpress\nextend\nextra\neye\neyebrow\nfabric\nface\nfaculty\nfade\nfaint\nfaith\nfall\nfalse\nfame\nfamily\nfamous\nfan\nfancy\nfantasy\nfarm\nfashion\nfat\nfatal\nfather\nfatigue\nfault\nfavorite\nfeature\nfebruary\nfederal\nfee\nfeed\nfeel\nfemale\nfence\nfestival\nfetch\nfever\nfew\nfiber\nfiction\nfield\nfigure\nfile\nfilm\nfilter\nfinal\nfind\nfine\nfinger\nfinish\nfire\nfirm\nfirst\nfiscal\nfish\nfit\nfitness\nfix\nflag\nflame\nflash\nflat\nflavor\nflee\nflight\nflip\nfloat\nflock\nfloor\nflower\nfluid\nflush\nfly\nfoam\nfocus\nfog\nfoil\nfold\nfollow\nfood\nfoot\nforce\nforest\nforget\nfork\nfortune\nforum\nforward\nfossil\nfoster\nfound\nfox\nfragile\nframe\nfrequent\nfresh\nfriend\nfringe\nfrog\nfront\nfrost\nfrown\nfrozen\nfruit\nfuel\nfun\nfunny\nfurnace\nfury\nfuture\ngadget\ngain\ngalaxy\ngallery\ngame\ngap\ngarage\ngarbage\ngarden\ngarlic\ngarment\ngas\ngasp\ngate\ngather\ngauge\ngaze\ngeneral\ngenius\ngenre\ngentle\ngenuine\ngesture\nghost\ngiant\ngift\ngiggle\nginger\ngiraffe\ngirl\ngive\nglad\nglance\nglare\nglass\nglide\nglimpse\nglobe\ngloom\nglory\nglove\nglow\nglue\ngoat\ngoddess\ngold\ngood\ngoose\ngorilla\ngospel\ngossip\ngovern\ngown\ngrab\ngrace\ngrain\ngrant\ngrape\ngrass\ngravity\ngreat\ngreen\ngrid\ngrief\ngrit\ngrocery\ngroup\ngrow\ngrunt\nguard\nguess\nguide\nguilt\nguitar\ngun\ngym\nhabit\nhair\nhalf\nhammer\nhamster\nhand\nhappy\nharbor\nhard\nharsh\nharvest\nhat\nhave\nhawk\nhazard\nhead\nhealth\nheart\nheavy\nhedgehog\nheight\nhello\nhelmet\nhelp\nhen\nhero\nhidden\nhigh\nhill\nhint\nhip\nhire\nhistory\nhobby\nhockey\nhold\nhole\nholiday\nhollow\nhome\nhoney\nhood\nhope\nhorn\nhorror\nhorse\nhospital\nhost\nhotel\nhour\nhover\nhub\nhuge\nhuman\nhumble\nhumor\nhundred\nhungry\nhunt\nhurdle\nhurry\nhurt\nhusband\nhybrid\nice\nicon\nidea\nidentify\nidle\nignore\nill\nillegal\nillness\nimage\nimitate\nimmense\nimmune\nimpact\nimpose\nimprove\nimpulse\ninch\ninclude\nincome\nincrease\nindex\nindicate\nindoor\nindustry\ninfant\ninflict\ninform\ninhale\ninherit\ninitial\ninject\ninjury\ninmate\ninner\ninnocent\ninput\ninquiry\ninsane\ninsect\ninside\ninspire\ninstall\nintact\ninterest\ninto\ninvest\ninvite\ninvolve\niron\nisland\nisolate\nissue\nitem\nivory\njacket\njaguar\njar\njazz\njealous\njeans\njelly\njewel\njob\njoin\njoke\njourney\njoy\njudge\njuice\njump\njungle\njunior\njunk\njust\nkangaroo\nkeen\nkeep\nketchup\nkey\nkick\nkid\nkidney\nkind\nkingdom\nkiss\nkit\nkitchen\nkite\nkitten\nkiwi\nknee\nknife\nknock\nknow\nlab\nlabel\nlabor\nladder\nlady\nlake\nlamp\nlanguage\nlaptop\nlarge\nlater\nlatin\nlaugh\nlaundry\nlava\nlaw\nlawn\nlawsuit\nlayer\nlazy\nleader\nleaf\nlearn\nleave\nlecture\nleft\nleg\nlegal\nlegend\nleisure\nlemon\nlend\nlength\nlens\nleopard\nlesson\nletter\nlevel\nliar\nliberty\nlibrary\nlicense\nlife\nlift\nlight\nlike\nlimb\nlimit\nlink\nlion\nliquid\nlist\nlittle\nlive\nlizard\nload\nloan\nlobster\nlocal\nlock\nlogic\nlonely\nlong\nloop\nlottery\nloud\nlounge\nlove\nloyal\nlucky\nluggage\nlumber\nlunar\nlunch\nluxury\nlyrics\nmachine\nmad\nmagic\nmagnet\nmaid\nmail\nmain\nmajor\nmake\nmammal\nman\nmanage\nmandate\nmango\nmansion\nmanual\nmaple\nmarble\nmarch\nmargin\nmarine\nmarket\nmarriage\nmask\nmass\nmaster\nmatch\nmaterial\nmath\nmatrix\nmatter\nmaximum\nmaze\nmeadow\nmean\nmeasure\nmeat\nmechanic\nmedal\nmedia\nmelody\nmelt\nmember\nmemory\nmention\nmenu\nmercy\nmerge\nmerit\nmerry\nmesh\nmessage\nmetal\nmethod\nmiddle\nmidnight\nmilk\nmillion\nmimic\nmind\nminimum\nminor\nminute\nmiracle\nmirror\nmisery\nmiss\nmistake\nmix\nmixed\nmixture\nmobile\nmodel\nmodify\nmom\nmoment\nmonitor\nmonkey\nmonster\nmonth\nmoon\nmoral\nmore\nmorning\nmosquito\nmother\nmotion\nmotor\nmountain\nmouse\nmove\nmovie\nmuch\nmuffin\nmule\nmultiply\nmuscle\nmuseum\nmushroom\nmusic\nmust\nmutual\nmyself\nmystery\nmyth\nnaive\nname\nnapkin\nnarrow\nnasty\nnation\nnature\nnear\nneck\nneed\nnegative\nneglect\nneither\nnephew\nnerve\nnest\nnet\nnetwork\nneutral\nnever\nnews\nnext\nnice\nnight\nnoble\nnoise\nnominee\nnoodle\nnormal\nnorth\nnose\nnotable\nnote\nnothing\nnotice\nnovel\nnow\nnuclear\nnumber\nnurse\nnut\noak\nobey\nobject\noblige\nobscure\nobserve\nobtain\nobvious\noccur\nocean\noctober\nodor\noff\noffer\noffice\noften\noil\nokay\nold\nolive\nolympic\nomit\nonce\none\nonion\nonline\nonly\nopen\nopera\nopinion\noppose\noption\norange\norbit\norchard\norder\nordinary\norgan\norient\noriginal\norphan\nostrich\nother\noutdoor\nouter\noutput\noutside\noval\noven\nover\nown\nowner\noxygen\noyster\nozone\npact\npaddle\npage\npair\npalace\npalm\npanda\npanel\npanic\npanther\npaper\nparade\nparent\npark\nparrot\nparty\npass\npatch\npath\npatient\npatrol\npattern\npause\npave\npayment\npeace\npeanut\npear\npeasant\npelican\npen\npenalty\npencil\npeople\npepper\nperfect\npermit\nperson\npet\nphone\nphoto\nphrase\nphysical\npiano\npicnic\npicture\npiece\npig\npigeon\npill\npilot\npink\npioneer\npipe\npistol\npitch\npizza\nplace\nplanet\nplastic\nplate\nplay\nplease\npledge\npluck\nplug\nplunge\npoem\npoet\npoint\npolar\npole\npolice\npond\npony\npool\npopular\nportion\nposition\npossible\npost\npotato\npottery\npoverty\npowder\npower\npractice\npraise\npredict\nprefer\nprepare\npresent\npretty\nprevent\nprice\npride\nprimary\nprint\npriority\nprison\nprivate\nprize\nproblem\nprocess\nproduce\nprofit\nprogram\nproject\npromote\nproof\nproperty\nprosper\nprotect\nproud\nprovide\npublic\npudding\npull\npulp\npulse\npumpkin\npunch\npupil\npuppy\npurchase\npurity\npurpose\npurse\npush\nput\npuzzle\npyramid\nquality\nquantum\nquarter\nquestion\nquick\nquit\nquiz\nquote\nrabbit\nraccoon\nrace\nrack\nradar\nradio\nrail\nrain\nraise\nrally\nramp\nranch\nrandom\nrange\nrapid\nrare\nrate\nrather\nraven\nraw\nrazor\nready\nreal\nreason\nrebel\nrebuild\nrecall\nreceive\nrecipe\nrecord\nrecycle\nreduce\nreflect\nreform\nrefuse\nregion\nregret\nregular\nreject\nrelax\nrelease\nrelief\nrely\nremain\nremember\nremind\nremove\nrender\nrenew\nrent\nreopen\nrepair\nrepeat\nreplace\nreport\nrequire\nrescue\nresemble\nresist\nresource\nresponse\nresult\nretire\nretreat\nreturn\nreunion\nreveal\nreview\nreward\nrhythm\nrib\nribbon\nrice\nrich\nride\nridge\nrifle\nright\nrigid\nring\nriot\nripple\nrisk\nritual\nrival\nriver\nroad\nroast\nrobot\nrobust\nrocket\nromance\nroof\nrookie\nroom\nrose\nrotate\nrough\nround\nroute\nroyal\nrubber\nrude\nrug\nrule\nrun\nrunway\nrural\nsad\nsaddle\nsadness\nsafe\nsail\nsalad\nsalmon\nsalon\nsalt\nsalute\nsame\nsample\nsand\nsatisfy\nsatoshi\nsauce\nsausage\nsave\nsay\nscale\nscan\nscare\nscatter\nscene\nscheme\nschool\nscience\nscissors\nscorpion\nscout\nscrap\nscreen\nscript\nscrub\nsea\nsearch\nseason\nseat\nsecond\nsecret\nsection\nsecurity\nseed\nseek\nsegment\nselect\nsell\nseminar\nsenior\nsense\nsentence\nseries\nservice\nsession\nsettle\nsetup\nseven\nshadow\nshaft\nshallow\nshare\nshed\nshell\nsheriff\nshield\nshift\nshine\nship\nshiver\nshock\nshoe\nshoot\nshop\nshort\nshoulder\nshove\nshrimp\nshrug\nshuffle\nshy\nsibling\nsick\nside\nsiege\nsight\nsign\nsilent\nsilk\nsilly\nsilver\nsimilar\nsimple\nsince\nsing\nsiren\nsister\nsituate\nsix\nsize\nskate\nsketch\nski\nskill\nskin\nskirt\nskull\nslab\nslam\nsleep\nslender\nslice\nslide\nslight\nslim\nslogan\nslot\nslow\nslush\nsmall\nsmart\nsmile\nsmoke\nsmooth\nsnack\nsnake\nsnap\nsniff\nsnow\nsoap\nsoccer\nsocial\nsock\nsoda\nsoft\nsolar\nsoldier\nsolid\nsolution\nsolve\nsomeone\nsong\nsoon\nsorry\nsort\nsoul\nsound\nsoup\nsource\nsouth\nspace\nspare\nspatial\nspawn\nspeak\nspecial\nspeed\nspell\nspend\nsphere\nspice\nspider\nspike\nspin\nspirit\nsplit\nspoil\nsponsor\nspoon\nsport\nspot\nspray\nspread\nspring\nspy\nsquare\nsqueeze\nsquirrel\nstable\nstadium\nstaff\nstage\nstairs\nstamp\nstand\nstart\nstate\nstay\nsteak\nsteel\nstem\nstep\nstereo\nstick\nstill\nsting\nstock\nstomach\nstone\nstool\nstory\nstove\nstrategy\nstreet\nstrike\nstrong\nstruggle\nstudent\nstuff\nstumble\nstyle\nsubject\nsubmit\nsubway\nsuccess\nsuch\nsudden\nsuffer\nsugar\nsuggest\nsuit\nsummer\nsun\nsunny\nsunset\nsuper\nsupply\nsupreme\nsure\nsurface\nsurge\nsurprise\nsurround\nsurvey\nsuspect\nsustain\nswallow\nswamp\nswap\nswarm\nswear\nsweet\nswift\nswim\nswing\nswitch\nsword\nsymbol\nsymptom\nsyrup\nsystem\ntable\ntackle\ntag\ntail\ntalent\ntalk\ntank\ntape\ntarget\ntask\ntaste\ntattoo\ntaxi\nteach\nteam\ntell\nten\ntenant\ntennis\ntent\nterm\ntest\ntext\nthank\nthat\ntheme\nthen\ntheory\nthere\nthey\nthing\nthis\nthought\nthree\nthrive\nthrow\nthumb\nthunder\nticket\ntide\ntiger\ntilt\ntimber\ntime\ntiny\ntip\ntired\ntissue\ntitle\ntoast\ntobacco\ntoday\ntoddler\ntoe\ntogether\ntoilet\ntoken\ntomato\ntomorrow\ntone\ntongue\ntonight\ntool\ntooth\ntop\ntopic\ntopple\ntorch\ntornado\ntortoise\ntoss\ntotal\ntourist\ntoward\ntower\ntown\ntoy\ntrack\ntrade\ntraffic\ntragic\ntrain\ntransfer\ntrap\ntrash\ntravel\ntray\ntreat\ntree\ntrend\ntrial\ntribe\ntrick\ntrigger\ntrim\ntrip\ntrophy\ntrouble\ntruck\ntrue\ntruly\ntrumpet\ntrust\ntruth\ntry\ntube\ntuition\ntumble\ntuna\ntunnel\nturkey\nturn\nturtle\ntwelve\ntwenty\ntwice\ntwin\ntwist\ntwo\ntype\ntypical\nugly\numbrella\nunable\nunaware\nuncle\nuncover\nunder\nundo\nunfair\nunfold\nunhappy\nuniform\nunique\nunit\nuniverse\nunknown\nunlock\nuntil\nunusual\nunveil\nupdate\nupgrade\nuphold\nupon\nupper\nupset\nurban\nurge\nusage\nuse\nused\nuseful\nuseless\nusual\nutility\nvacant\nvacuum\nvague\nvalid\nvalley\nvalve\nvan\nvanish\nvapor\nvarious\nvast\nvault\nvehicle\nvelvet\nvendor\nventure\nvenue\nverb\nverify\nversion\nvery\nvessel\nveteran\nviable\nvibrant\nvicious\nvictory\nvideo\nview\nvillage\nvintage\nviolin\nvirtual\nvirus\nvisa\nvisit\nvisual\nvital\nvivid\nvocal\nvoice\nvoid\nvolcano\nvolume\nvote\nvoyage\nwage\nwagon\nwait\nwalk\nwall\nwalnut\nwant\nwarfare\nwarm\nwarrior\nwash\nwasp\nwaste\nwater\nwave\nway\nwealth\nweapon\nwear\nweasel\nweather\nweb\nwedding\nweekend\nweird\nwelcome\nwest\nwet\nwhale\nwhat\nwheat\nwheel\nwhen\nwhere\nwhip\nwhisper\nwide\nwidth\nwife\nwild\nwill\nwin\nwindow\nwine\nwing\nwink\nwinner\nwinter\nwire\nwisdom\nwise\nwish\nwitness\nwolf\nwoman\nwonder\nwood\nwool\nword\nwork\nworld\nworry\nworth\nwrap\nwreck\nwrestle\nwrist\nwrite\nwrong\nyard\nyear\nyellow\nyou\nyoung\nyouth\nzebra\nzero\nzone\nzoo\n"
  },
  {
    "path": "lib/wordlist/japanese.txt",
    "content": "あいこくしん\nあいさつ\nあいだ\nあおぞら\nあかちゃん\nあきる\nあけがた\nあける\nあこがれる\nあさい\nあさひ\nあしあと\nあじわう\nあずかる\nあずき\nあそぶ\nあたえる\nあたためる\nあたりまえ\nあたる\nあつい\nあつかう\nあっしゅく\nあつまり\nあつめる\nあてな\nあてはまる\nあひる\nあぶら\nあぶる\nあふれる\nあまい\nあまど\nあまやかす\nあまり\nあみもの\nあめりか\nあやまる\nあゆむ\nあらいぐま\nあらし\nあらすじ\nあらためる\nあらゆる\nあらわす\nありがとう\nあわせる\nあわてる\nあんい\nあんがい\nあんこ\nあんぜん\nあんてい\nあんない\nあんまり\nいいだす\nいおん\nいがい\nいがく\nいきおい\nいきなり\nいきもの\nいきる\nいくじ\nいくぶん\nいけばな\nいけん\nいこう\nいこく\nいこつ\nいさましい\nいさん\nいしき\nいじゅう\nいじょう\nいじわる\nいずみ\nいずれ\nいせい\nいせえび\nいせかい\nいせき\nいぜん\nいそうろう\nいそがしい\nいだい\nいだく\nいたずら\nいたみ\nいたりあ\nいちおう\nいちじ\nいちど\nいちば\nいちぶ\nいちりゅう\nいつか\nいっしゅん\nいっせい\nいっそう\nいったん\nいっち\nいってい\nいっぽう\nいてざ\nいてん\nいどう\nいとこ\nいない\nいなか\nいねむり\nいのち\nいのる\nいはつ\nいばる\nいはん\nいびき\nいひん\nいふく\nいへん\nいほう\nいみん\nいもうと\nいもたれ\nいもり\nいやがる\nいやす\nいよかん\nいよく\nいらい\nいらすと\nいりぐち\nいりょう\nいれい\nいれもの\nいれる\nいろえんぴつ\nいわい\nいわう\nいわかん\nいわば\nいわゆる\nいんげんまめ\nいんさつ\nいんしょう\nいんよう\nうえき\nうえる\nうおざ\nうがい\nうかぶ\nうかべる\nうきわ\nうくらいな\nうくれれ\nうけたまわる\nうけつけ\nうけとる\nうけもつ\nうける\nうごかす\nうごく\nうこん\nうさぎ\nうしなう\nうしろがみ\nうすい\nうすぎ\nうすぐらい\nうすめる\nうせつ\nうちあわせ\nうちがわ\nうちき\nうちゅう\nうっかり\nうつくしい\nうったえる\nうつる\nうどん\nうなぎ\nうなじ\nうなずく\nうなる\nうねる\nうのう\nうぶげ\nうぶごえ\nうまれる\nうめる\nうもう\nうやまう\nうよく\nうらがえす\nうらぐち\nうらない\nうりあげ\nうりきれ\nうるさい\nうれしい\nうれゆき\nうれる\nうろこ\nうわき\nうわさ\nうんこう\nうんちん\nうんてん\nうんどう\nえいえん\nえいが\nえいきょう\nえいご\nえいせい\nえいぶん\nえいよう\nえいわ\nえおり\nえがお\nえがく\nえきたい\nえくせる\nえしゃく\nえすて\nえつらん\nえのぐ\nえほうまき\nえほん\nえまき\nえもじ\nえもの\nえらい\nえらぶ\nえりあ\nえんえん\nえんかい\nえんぎ\nえんげき\nえんしゅう\nえんぜつ\nえんそく\nえんちょう\nえんとつ\nおいかける\nおいこす\nおいしい\nおいつく\nおうえん\nおうさま\nおうじ\nおうせつ\nおうたい\nおうふく\nおうべい\nおうよう\nおえる\nおおい\nおおう\nおおどおり\nおおや\nおおよそ\nおかえり\nおかず\nおがむ\nおかわり\nおぎなう\nおきる\nおくさま\nおくじょう\nおくりがな\nおくる\nおくれる\nおこす\nおこなう\nおこる\nおさえる\nおさない\nおさめる\nおしいれ\nおしえる\nおじぎ\nおじさん\nおしゃれ\nおそらく\nおそわる\nおたがい\nおたく\nおだやか\nおちつく\nおっと\nおつり\nおでかけ\nおとしもの\nおとなしい\nおどり\nおどろかす\nおばさん\nおまいり\nおめでとう\nおもいで\nおもう\nおもたい\nおもちゃ\nおやつ\nおやゆび\nおよぼす\nおらんだ\nおろす\nおんがく\nおんけい\nおんしゃ\nおんせん\nおんだん\nおんちゅう\nおんどけい\nかあつ\nかいが\nがいき\nがいけん\nがいこう\nかいさつ\nかいしゃ\nかいすいよく\nかいぜん\nかいぞうど\nかいつう\nかいてん\nかいとう\nかいふく\nがいへき\nかいほう\nかいよう\nがいらい\nかいわ\nかえる\nかおり\nかかえる\nかがく\nかがし\nかがみ\nかくご\nかくとく\nかざる\nがぞう\nかたい\nかたち\nがちょう\nがっきゅう\nがっこう\nがっさん\nがっしょう\nかなざわし\nかのう\nがはく\nかぶか\nかほう\nかほご\nかまう\nかまぼこ\nかめれおん\nかゆい\nかようび\nからい\nかるい\nかろう\nかわく\nかわら\nがんか\nかんけい\nかんこう\nかんしゃ\nかんそう\nかんたん\nかんち\nがんばる\nきあい\nきあつ\nきいろ\nぎいん\nきうい\nきうん\nきえる\nきおう\nきおく\nきおち\nきおん\nきかい\nきかく\nきかんしゃ\nききて\nきくばり\nきくらげ\nきけんせい\nきこう\nきこえる\nきこく\nきさい\nきさく\nきさま\nきさらぎ\nぎじかがく\nぎしき\nぎじたいけん\nぎじにってい\nぎじゅつしゃ\nきすう\nきせい\nきせき\nきせつ\nきそう\nきぞく\nきぞん\nきたえる\nきちょう\nきつえん\nぎっちり\nきつつき\nきつね\nきてい\nきどう\nきどく\nきない\nきなが\nきなこ\nきぬごし\nきねん\nきのう\nきのした\nきはく\nきびしい\nきひん\nきふく\nきぶん\nきぼう\nきほん\nきまる\nきみつ\nきむずかしい\nきめる\nきもだめし\nきもち\nきもの\nきゃく\nきやく\nぎゅうにく\nきよう\nきょうりゅう\nきらい\nきらく\nきりん\nきれい\nきれつ\nきろく\nぎろん\nきわめる\nぎんいろ\nきんかくじ\nきんじょ\nきんようび\nぐあい\nくいず\nくうかん\nくうき\nくうぐん\nくうこう\nぐうせい\nくうそう\nぐうたら\nくうふく\nくうぼ\nくかん\nくきょう\nくげん\nぐこう\nくさい\nくさき\nくさばな\nくさる\nくしゃみ\nくしょう\nくすのき\nくすりゆび\nくせげ\nくせん\nぐたいてき\nくださる\nくたびれる\nくちこみ\nくちさき\nくつした\nぐっすり\nくつろぐ\nくとうてん\nくどく\nくなん\nくねくね\nくのう\nくふう\nくみあわせ\nくみたてる\nくめる\nくやくしょ\nくらす\nくらべる\nくるま\nくれる\nくろう\nくわしい\nぐんかん\nぐんしょく\nぐんたい\nぐんて\nけあな\nけいかく\nけいけん\nけいこ\nけいさつ\nげいじゅつ\nけいたい\nげいのうじん\nけいれき\nけいろ\nけおとす\nけおりもの\nげきか\nげきげん\nげきだん\nげきちん\nげきとつ\nげきは\nげきやく\nげこう\nげこくじょう\nげざい\nけさき\nげざん\nけしき\nけしごむ\nけしょう\nげすと\nけたば\nけちゃっぷ\nけちらす\nけつあつ\nけつい\nけつえき\nけっこん\nけつじょ\nけっせき\nけってい\nけつまつ\nげつようび\nげつれい\nけつろん\nげどく\nけとばす\nけとる\nけなげ\nけなす\nけなみ\nけぬき\nげねつ\nけねん\nけはい\nげひん\nけぶかい\nげぼく\nけまり\nけみかる\nけむし\nけむり\nけもの\nけらい\nけろけろ\nけわしい\nけんい\nけんえつ\nけんお\nけんか\nげんき\nけんげん\nけんこう\nけんさく\nけんしゅう\nけんすう\nげんそう\nけんちく\nけんてい\nけんとう\nけんない\nけんにん\nげんぶつ\nけんま\nけんみん\nけんめい\nけんらん\nけんり\nこあくま\nこいぬ\nこいびと\nごうい\nこうえん\nこうおん\nこうかん\nごうきゅう\nごうけい\nこうこう\nこうさい\nこうじ\nこうすい\nごうせい\nこうそく\nこうたい\nこうちゃ\nこうつう\nこうてい\nこうどう\nこうない\nこうはい\nごうほう\nごうまん\nこうもく\nこうりつ\nこえる\nこおり\nごかい\nごがつ\nごかん\nこくご\nこくさい\nこくとう\nこくない\nこくはく\nこぐま\nこけい\nこける\nここのか\nこころ\nこさめ\nこしつ\nこすう\nこせい\nこせき\nこぜん\nこそだて\nこたい\nこたえる\nこたつ\nこちょう\nこっか\nこつこつ\nこつばん\nこつぶ\nこてい\nこてん\nことがら\nことし\nことば\nことり\nこなごな\nこねこね\nこのまま\nこのみ\nこのよ\nごはん\nこひつじ\nこふう\nこふん\nこぼれる\nごまあぶら\nこまかい\nごますり\nこまつな\nこまる\nこむぎこ\nこもじ\nこもち\nこもの\nこもん\nこやく\nこやま\nこゆう\nこゆび\nこよい\nこよう\nこりる\nこれくしょん\nころっけ\nこわもて\nこわれる\nこんいん\nこんかい\nこんき\nこんしゅう\nこんすい\nこんだて\nこんとん\nこんなん\nこんびに\nこんぽん\nこんまけ\nこんや\nこんれい\nこんわく\nざいえき\nさいかい\nさいきん\nざいげん\nざいこ\nさいしょ\nさいせい\nざいたく\nざいちゅう\nさいてき\nざいりょう\nさうな\nさかいし\nさがす\nさかな\nさかみち\nさがる\nさぎょう\nさくし\nさくひん\nさくら\nさこく\nさこつ\nさずかる\nざせき\nさたん\nさつえい\nざつおん\nざっか\nざつがく\nさっきょく\nざっし\nさつじん\nざっそう\nさつたば\nさつまいも\nさてい\nさといも\nさとう\nさとおや\nさとし\nさとる\nさのう\nさばく\nさびしい\nさべつ\nさほう\nさほど\nさます\nさみしい\nさみだれ\nさむけ\nさめる\nさやえんどう\nさゆう\nさよう\nさよく\nさらだ\nざるそば\nさわやか\nさわる\nさんいん\nさんか\nさんきゃく\nさんこう\nさんさい\nざんしょ\nさんすう\nさんせい\nさんそ\nさんち\nさんま\nさんみ\nさんらん\nしあい\nしあげ\nしあさって\nしあわせ\nしいく\nしいん\nしうち\nしえい\nしおけ\nしかい\nしかく\nじかん\nしごと\nしすう\nじだい\nしたうけ\nしたぎ\nしたて\nしたみ\nしちょう\nしちりん\nしっかり\nしつじ\nしつもん\nしてい\nしてき\nしてつ\nじてん\nじどう\nしなぎれ\nしなもの\nしなん\nしねま\nしねん\nしのぐ\nしのぶ\nしはい\nしばかり\nしはつ\nしはらい\nしはん\nしひょう\nしふく\nじぶん\nしへい\nしほう\nしほん\nしまう\nしまる\nしみん\nしむける\nじむしょ\nしめい\nしめる\nしもん\nしゃいん\nしゃうん\nしゃおん\nじゃがいも\nしやくしょ\nしゃくほう\nしゃけん\nしゃこ\nしゃざい\nしゃしん\nしゃせん\nしゃそう\nしゃたい\nしゃちょう\nしゃっきん\nじゃま\nしゃりん\nしゃれい\nじゆう\nじゅうしょ\nしゅくはく\nじゅしん\nしゅっせき\nしゅみ\nしゅらば\nじゅんばん\nしょうかい\nしょくたく\nしょっけん\nしょどう\nしょもつ\nしらせる\nしらべる\nしんか\nしんこう\nじんじゃ\nしんせいじ\nしんちく\nしんりん\nすあげ\nすあし\nすあな\nずあん\nすいえい\nすいか\nすいとう\nずいぶん\nすいようび\nすうがく\nすうじつ\nすうせん\nすおどり\nすきま\nすくう\nすくない\nすける\nすごい\nすこし\nずさん\nすずしい\nすすむ\nすすめる\nすっかり\nずっしり\nずっと\nすてき\nすてる\nすねる\nすのこ\nすはだ\nすばらしい\nずひょう\nずぶぬれ\nすぶり\nすふれ\nすべて\nすべる\nずほう\nすぼん\nすまい\nすめし\nすもう\nすやき\nすらすら\nするめ\nすれちがう\nすろっと\nすわる\nすんぜん\nすんぽう\nせあぶら\nせいかつ\nせいげん\nせいじ\nせいよう\nせおう\nせかいかん\nせきにん\nせきむ\nせきゆ\nせきらんうん\nせけん\nせこう\nせすじ\nせたい\nせたけ\nせっかく\nせっきゃく\nぜっく\nせっけん\nせっこつ\nせっさたくま\nせつぞく\nせつだん\nせつでん\nせっぱん\nせつび\nせつぶん\nせつめい\nせつりつ\nせなか\nせのび\nせはば\nせびろ\nせぼね\nせまい\nせまる\nせめる\nせもたれ\nせりふ\nぜんあく\nせんい\nせんえい\nせんか\nせんきょ\nせんく\nせんげん\nぜんご\nせんさい\nせんしゅ\nせんすい\nせんせい\nせんぞ\nせんたく\nせんちょう\nせんてい\nせんとう\nせんぬき\nせんねん\nせんぱい\nぜんぶ\nぜんぽう\nせんむ\nせんめんじょ\nせんもん\nせんやく\nせんゆう\nせんよう\nぜんら\nぜんりゃく\nせんれい\nせんろ\nそあく\nそいとげる\nそいね\nそうがんきょう\nそうき\nそうご\nそうしん\nそうだん\nそうなん\nそうび\nそうめん\nそうり\nそえもの\nそえん\nそがい\nそげき\nそこう\nそこそこ\nそざい\nそしな\nそせい\nそせん\nそそぐ\nそだてる\nそつう\nそつえん\nそっかん\nそつぎょう\nそっけつ\nそっこう\nそっせん\nそっと\nそとがわ\nそとづら\nそなえる\nそなた\nそふぼ\nそぼく\nそぼろ\nそまつ\nそまる\nそむく\nそむりえ\nそめる\nそもそも\nそよかぜ\nそらまめ\nそろう\nそんかい\nそんけい\nそんざい\nそんしつ\nそんぞく\nそんちょう\nぞんび\nぞんぶん\nそんみん\nたあい\nたいいん\nたいうん\nたいえき\nたいおう\nだいがく\nたいき\nたいぐう\nたいけん\nたいこ\nたいざい\nだいじょうぶ\nだいすき\nたいせつ\nたいそう\nだいたい\nたいちょう\nたいてい\nだいどころ\nたいない\nたいねつ\nたいのう\nたいはん\nだいひょう\nたいふう\nたいへん\nたいほ\nたいまつばな\nたいみんぐ\nたいむ\nたいめん\nたいやき\nたいよう\nたいら\nたいりょく\nたいる\nたいわん\nたうえ\nたえる\nたおす\nたおる\nたおれる\nたかい\nたかね\nたきび\nたくさん\nたこく\nたこやき\nたさい\nたしざん\nだじゃれ\nたすける\nたずさわる\nたそがれ\nたたかう\nたたく\nただしい\nたたみ\nたちばな\nだっかい\nだっきゃく\nだっこ\nだっしゅつ\nだったい\nたてる\nたとえる\nたなばた\nたにん\nたぬき\nたのしみ\nたはつ\nたぶん\nたべる\nたぼう\nたまご\nたまる\nだむる\nためいき\nためす\nためる\nたもつ\nたやすい\nたよる\nたらす\nたりきほんがん\nたりょう\nたりる\nたると\nたれる\nたれんと\nたろっと\nたわむれる\nだんあつ\nたんい\nたんおん\nたんか\nたんき\nたんけん\nたんご\nたんさん\nたんじょうび\nだんせい\nたんそく\nたんたい\nだんち\nたんてい\nたんとう\nだんな\nたんにん\nだんねつ\nたんのう\nたんぴん\nだんぼう\nたんまつ\nたんめい\nだんれつ\nだんろ\nだんわ\nちあい\nちあん\nちいき\nちいさい\nちえん\nちかい\nちから\nちきゅう\nちきん\nちけいず\nちけん\nちこく\nちさい\nちしき\nちしりょう\nちせい\nちそう\nちたい\nちたん\nちちおや\nちつじょ\nちてき\nちてん\nちぬき\nちぬり\nちのう\nちひょう\nちへいせん\nちほう\nちまた\nちみつ\nちみどろ\nちめいど\nちゃんこなべ\nちゅうい\nちゆりょく\nちょうし\nちょさくけん\nちらし\nちらみ\nちりがみ\nちりょう\nちるど\nちわわ\nちんたい\nちんもく\nついか\nついたち\nつうか\nつうじょう\nつうはん\nつうわ\nつかう\nつかれる\nつくね\nつくる\nつけね\nつける\nつごう\nつたえる\nつづく\nつつじ\nつつむ\nつとめる\nつながる\nつなみ\nつねづね\nつのる\nつぶす\nつまらない\nつまる\nつみき\nつめたい\nつもり\nつもる\nつよい\nつるぼ\nつるみく\nつわもの\nつわり\nてあし\nてあて\nてあみ\nていおん\nていか\nていき\nていけい\nていこく\nていさつ\nていし\nていせい\nていたい\nていど\nていねい\nていひょう\nていへん\nていぼう\nてうち\nておくれ\nてきとう\nてくび\nでこぼこ\nてさぎょう\nてさげ\nてすり\nてそう\nてちがい\nてちょう\nてつがく\nてつづき\nでっぱ\nてつぼう\nてつや\nでぬかえ\nてぬき\nてぬぐい\nてのひら\nてはい\nてぶくろ\nてふだ\nてほどき\nてほん\nてまえ\nてまきずし\nてみじか\nてみやげ\nてらす\nてれび\nてわけ\nてわたし\nでんあつ\nてんいん\nてんかい\nてんき\nてんぐ\nてんけん\nてんごく\nてんさい\nてんし\nてんすう\nでんち\nてんてき\nてんとう\nてんない\nてんぷら\nてんぼうだい\nてんめつ\nてんらんかい\nでんりょく\nでんわ\nどあい\nといれ\nどうかん\nとうきゅう\nどうぐ\nとうし\nとうむぎ\nとおい\nとおか\nとおく\nとおす\nとおる\nとかい\nとかす\nときおり\nときどき\nとくい\nとくしゅう\nとくてん\nとくに\nとくべつ\nとけい\nとける\nとこや\nとさか\nとしょかん\nとそう\nとたん\nとちゅう\nとっきゅう\nとっくん\nとつぜん\nとつにゅう\nとどける\nととのえる\nとない\nとなえる\nとなり\nとのさま\nとばす\nどぶがわ\nとほう\nとまる\nとめる\nともだち\nともる\nどようび\nとらえる\nとんかつ\nどんぶり\nないかく\nないこう\nないしょ\nないす\nないせん\nないそう\nなおす\nながい\nなくす\nなげる\nなこうど\nなさけ\nなたでここ\nなっとう\nなつやすみ\nななおし\nなにごと\nなにもの\nなにわ\nなのか\nなふだ\nなまいき\nなまえ\nなまみ\nなみだ\nなめらか\nなめる\nなやむ\nならう\nならび\nならぶ\nなれる\nなわとび\nなわばり\nにあう\nにいがた\nにうけ\nにおい\nにかい\nにがて\nにきび\nにくしみ\nにくまん\nにげる\nにさんかたんそ\nにしき\nにせもの\nにちじょう\nにちようび\nにっか\nにっき\nにっけい\nにっこう\nにっさん\nにっしょく\nにっすう\nにっせき\nにってい\nになう\nにほん\nにまめ\nにもつ\nにやり\nにゅういん\nにりんしゃ\nにわとり\nにんい\nにんか\nにんき\nにんげん\nにんしき\nにんずう\nにんそう\nにんたい\nにんち\nにんてい\nにんにく\nにんぷ\nにんまり\nにんむ\nにんめい\nにんよう\nぬいくぎ\nぬかす\nぬぐいとる\nぬぐう\nぬくもり\nぬすむ\nぬまえび\nぬめり\nぬらす\nぬんちゃく\nねあげ\nねいき\nねいる\nねいろ\nねぐせ\nねくたい\nねくら\nねこぜ\nねこむ\nねさげ\nねすごす\nねそべる\nねだん\nねつい\nねっしん\nねつぞう\nねったいぎょ\nねぶそく\nねふだ\nねぼう\nねほりはほり\nねまき\nねまわし\nねみみ\nねむい\nねむたい\nねもと\nねらう\nねわざ\nねんいり\nねんおし\nねんかん\nねんきん\nねんぐ\nねんざ\nねんし\nねんちゃく\nねんど\nねんぴ\nねんぶつ\nねんまつ\nねんりょう\nねんれい\nのいず\nのおづま\nのがす\nのきなみ\nのこぎり\nのこす\nのこる\nのせる\nのぞく\nのぞむ\nのたまう\nのちほど\nのっく\nのばす\nのはら\nのべる\nのぼる\nのみもの\nのやま\nのらいぬ\nのらねこ\nのりもの\nのりゆき\nのれん\nのんき\nばあい\nはあく\nばあさん\nばいか\nばいく\nはいけん\nはいご\nはいしん\nはいすい\nはいせん\nはいそう\nはいち\nばいばい\nはいれつ\nはえる\nはおる\nはかい\nばかり\nはかる\nはくしゅ\nはけん\nはこぶ\nはさみ\nはさん\nはしご\nばしょ\nはしる\nはせる\nぱそこん\nはそん\nはたん\nはちみつ\nはつおん\nはっかく\nはづき\nはっきり\nはっくつ\nはっけん\nはっこう\nはっさん\nはっしん\nはったつ\nはっちゅう\nはってん\nはっぴょう\nはっぽう\nはなす\nはなび\nはにかむ\nはぶらし\nはみがき\nはむかう\nはめつ\nはやい\nはやし\nはらう\nはろうぃん\nはわい\nはんい\nはんえい\nはんおん\nはんかく\nはんきょう\nばんぐみ\nはんこ\nはんしゃ\nはんすう\nはんだん\nぱんち\nぱんつ\nはんてい\nはんとし\nはんのう\nはんぱ\nはんぶん\nはんぺん\nはんぼうき\nはんめい\nはんらん\nはんろん\nひいき\nひうん\nひえる\nひかく\nひかり\nひかる\nひかん\nひくい\nひけつ\nひこうき\nひこく\nひさい\nひさしぶり\nひさん\nびじゅつかん\nひしょ\nひそか\nひそむ\nひたむき\nひだり\nひたる\nひつぎ\nひっこし\nひっし\nひつじゅひん\nひっす\nひつぜん\nぴったり\nぴっちり\nひつよう\nひてい\nひとごみ\nひなまつり\nひなん\nひねる\nひはん\nひびく\nひひょう\nひほう\nひまわり\nひまん\nひみつ\nひめい\nひめじし\nひやけ\nひやす\nひよう\nびょうき\nひらがな\nひらく\nひりつ\nひりょう\nひるま\nひるやすみ\nひれい\nひろい\nひろう\nひろき\nひろゆき\nひんかく\nひんけつ\nひんこん\nひんしゅ\nひんそう\nぴんち\nひんぱん\nびんぼう\nふあん\nふいうち\nふうけい\nふうせん\nぷうたろう\nふうとう\nふうふ\nふえる\nふおん\nふかい\nふきん\nふくざつ\nふくぶくろ\nふこう\nふさい\nふしぎ\nふじみ\nふすま\nふせい\nふせぐ\nふそく\nぶたにく\nふたん\nふちょう\nふつう\nふつか\nふっかつ\nふっき\nふっこく\nぶどう\nふとる\nふとん\nふのう\nふはい\nふひょう\nふへん\nふまん\nふみん\nふめつ\nふめん\nふよう\nふりこ\nふりる\nふるい\nふんいき\nぶんがく\nぶんぐ\nふんしつ\nぶんせき\nふんそう\nぶんぽう\nへいあん\nへいおん\nへいがい\nへいき\nへいげん\nへいこう\nへいさ\nへいしゃ\nへいせつ\nへいそ\nへいたく\nへいてん\nへいねつ\nへいわ\nへきが\nへこむ\nべにいろ\nべにしょうが\nへらす\nへんかん\nべんきょう\nべんごし\nへんさい\nへんたい\nべんり\nほあん\nほいく\nぼうぎょ\nほうこく\nほうそう\nほうほう\nほうもん\nほうりつ\nほえる\nほおん\nほかん\nほきょう\nぼきん\nほくろ\nほけつ\nほけん\nほこう\nほこる\nほしい\nほしつ\nほしゅ\nほしょう\nほせい\nほそい\nほそく\nほたて\nほたる\nぽちぶくろ\nほっきょく\nほっさ\nほったん\nほとんど\nほめる\nほんい\nほんき\nほんけ\nほんしつ\nほんやく\nまいにち\nまかい\nまかせる\nまがる\nまける\nまこと\nまさつ\nまじめ\nますく\nまぜる\nまつり\nまとめ\nまなぶ\nまぬけ\nまねく\nまほう\nまもる\nまゆげ\nまよう\nまろやか\nまわす\nまわり\nまわる\nまんが\nまんきつ\nまんぞく\nまんなか\nみいら\nみうち\nみえる\nみがく\nみかた\nみかん\nみけん\nみこん\nみじかい\nみすい\nみすえる\nみせる\nみっか\nみつかる\nみつける\nみてい\nみとめる\nみなと\nみなみかさい\nみねらる\nみのう\nみのがす\nみほん\nみもと\nみやげ\nみらい\nみりょく\nみわく\nみんか\nみんぞく\nむいか\nむえき\nむえん\nむかい\nむかう\nむかえ\nむかし\nむぎちゃ\nむける\nむげん\nむさぼる\nむしあつい\nむしば\nむじゅん\nむしろ\nむすう\nむすこ\nむすぶ\nむすめ\nむせる\nむせん\nむちゅう\nむなしい\nむのう\nむやみ\nむよう\nむらさき\nむりょう\nむろん\nめいあん\nめいうん\nめいえん\nめいかく\nめいきょく\nめいさい\nめいし\nめいそう\nめいぶつ\nめいれい\nめいわく\nめぐまれる\nめざす\nめした\nめずらしい\nめだつ\nめまい\nめやす\nめんきょ\nめんせき\nめんどう\nもうしあげる\nもうどうけん\nもえる\nもくし\nもくてき\nもくようび\nもちろん\nもどる\nもらう\nもんく\nもんだい\nやおや\nやける\nやさい\nやさしい\nやすい\nやすたろう\nやすみ\nやせる\nやそう\nやたい\nやちん\nやっと\nやっぱり\nやぶる\nやめる\nややこしい\nやよい\nやわらかい\nゆうき\nゆうびんきょく\nゆうべ\nゆうめい\nゆけつ\nゆしゅつ\nゆせん\nゆそう\nゆたか\nゆちゃく\nゆでる\nゆにゅう\nゆびわ\nゆらい\nゆれる\nようい\nようか\nようきゅう\nようじ\nようす\nようちえん\nよかぜ\nよかん\nよきん\nよくせい\nよくぼう\nよけい\nよごれる\nよさん\nよしゅう\nよそう\nよそく\nよっか\nよてい\nよどがわく\nよねつ\nよやく\nよゆう\nよろこぶ\nよろしい\nらいう\nらくがき\nらくご\nらくさつ\nらくだ\nらしんばん\nらせん\nらぞく\nらたい\nらっか\nられつ\nりえき\nりかい\nりきさく\nりきせつ\nりくぐん\nりくつ\nりけん\nりこう\nりせい\nりそう\nりそく\nりてん\nりねん\nりゆう\nりゅうがく\nりよう\nりょうり\nりょかん\nりょくちゃ\nりょこう\nりりく\nりれき\nりろん\nりんご\nるいけい\nるいさい\nるいじ\nるいせき\nるすばん\nるりがわら\nれいかん\nれいぎ\nれいせい\nれいぞうこ\nれいとう\nれいぼう\nれきし\nれきだい\nれんあい\nれんけい\nれんこん\nれんさい\nれんしゅう\nれんぞく\nれんらく\nろうか\nろうご\nろうじん\nろうそく\nろくが\nろこつ\nろじうら\nろしゅつ\nろせん\nろてん\nろめん\nろれつ\nろんぎ\nろんぱ\nろんぶん\nろんり\nわかす\nわかめ\nわかやま\nわかれる\nわしつ\nわじまし\nわすれもの\nわらう\nわれる\n"
  },
  {
    "path": "lib/wordlist/portuguese.txt",
    "content": "# Copyright (c) 2014, The Monero Project\r\n# \r\n# All rights reserved.\r\n# \r\n# Redistribution and use in source and binary forms, with or without modification, are\r\n# permitted provided that the following conditions are met:\r\n# \r\n# 1. Redistributions of source code must retain the above copyright notice, this list of\r\n#    conditions and the following disclaimer.\r\n# \r\n# 2. Redistributions in binary form must reproduce the above copyright notice, this list\r\n#    of conditions and the following disclaimer in the documentation and/or other\r\n#    materials provided with the distribution.\r\n# \r\n# 3. Neither the name of the copyright holder nor the names of its contributors may be\r\n#    used to endorse or promote products derived from this software without specific\r\n#    prior written permission.\r\n# \r\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY\r\n# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r\n# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL\r\n# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r\n# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r\n# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\r\n# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF\r\n# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n\r\nabaular\r\nabdominal\r\nabeto\r\nabissinio\r\nabjeto\r\nablucao\r\nabnegar\r\nabotoar\r\nabrutalhar\r\nabsurdo\r\nabutre\r\nacautelar\r\naccessorios\r\nacetona\r\nachocolatado\r\nacirrar\r\nacne\r\nacovardar\r\nacrostico\r\nactinomicete\r\nacustico\r\nadaptavel\r\nadeus\r\nadivinho\r\nadjunto\r\nadmoestar\r\nadnominal\r\nadotivo\r\nadquirir\r\nadriatico\r\nadsorcao\r\nadutora\r\nadvogar\r\naerossol\r\nafazeres\r\nafetuoso\r\nafixo\r\nafluir\r\nafortunar\r\nafrouxar\r\naftosa\r\nafunilar\r\nagentes\r\nagito\r\naglutinar\r\naiatola\r\naimore\r\naino\r\naipo\r\nairoso\r\najeitar\r\najoelhar\r\najudante\r\najuste\r\nalazao\r\nalbumina\r\nalcunha\r\nalegria\r\nalexandre\r\nalforriar\r\nalguns\r\nalhures\r\nalivio\r\nalmoxarife\r\nalotropico\r\nalpiste\r\nalquimista\r\nalsaciano\r\naltura\r\naluviao\r\nalvura\r\namazonico\r\nambulatorio\r\nametodico\r\namizades\r\namniotico\r\namovivel\r\namurada\r\nanatomico\r\nancorar\r\nanexo\r\nanfora\r\naniversario\r\nanjo\r\nanotar\r\nansioso\r\nanturio\r\nanuviar\r\nanverso\r\nanzol\r\naonde\r\napaziguar\r\napito\r\naplicavel\r\napoteotico\r\naprimorar\r\naprumo\r\napto\r\napuros\r\naquoso\r\narauto\r\narbusto\r\narduo\r\naresta\r\narfar\r\narguto\r\naritmetico\r\narlequim\r\narmisticio\r\naromatizar\r\narpoar\r\narquivo\r\narrumar\r\narsenio\r\narturiano\r\naruaque\r\narvores\r\nasbesto\r\nascorbico\r\naspirina\r\nasqueroso\r\nassustar\r\nastuto\r\natazanar\r\nativo\r\natletismo\r\natmosferico\r\natormentar\r\natroz\r\naturdir\r\naudivel\r\nauferir\r\naugusto\r\naula\r\naumento\r\naurora\r\nautuar\r\navatar\r\navexar\r\navizinhar\r\navolumar\r\navulso\r\naxiomatico\r\nazerbaijano\r\nazimute\r\nazoto\r\nazulejo\r\nbacteriologista\r\nbadulaque\r\nbaforada\r\nbaixote\r\nbajular\r\nbalzaquiana\r\nbambuzal\r\nbanzo\r\nbaoba\r\nbaqueta\r\nbarulho\r\nbastonete\r\nbatuta\r\nbauxita\r\nbavaro\r\nbazuca\r\nbcrepuscular\r\nbeato\r\nbeduino\r\nbegonia\r\nbehaviorista\r\nbeisebol\r\nbelzebu\r\nbemol\r\nbenzido\r\nbeocio\r\nbequer\r\nberro\r\nbesuntar\r\nbetume\r\nbexiga\r\nbezerro\r\nbiatlon\r\nbiboca\r\nbicuspide\r\nbidirecional\r\nbienio\r\nbifurcar\r\nbigorna\r\nbijuteria\r\nbimotor\r\nbinormal\r\nbioxido\r\nbipolarizacao\r\nbiquini\r\nbirutice\r\nbisturi\r\nbituca\r\nbiunivoco\r\nbivalve\r\nbizarro\r\nblasfemo\r\nblenorreia\r\nblindar\r\nbloqueio\r\nblusao\r\nboazuda\r\nbofete\r\nbojudo\r\nbolso\r\nbombordo\r\nbonzo\r\nbotina\r\nboquiaberto\r\nbostoniano\r\nbotulismo\r\nbourbon\r\nbovino\r\nboximane\r\nbravura\r\nbrevidade\r\nbritar\r\nbroxar\r\nbruno\r\nbruxuleio\r\nbubonico\r\nbucolico\r\nbuda\r\nbudista\r\nbueiro\r\nbuffer\r\nbugre\r\nbujao\r\nbumerangue\r\nburundines\r\nbusto\r\nbutique\r\nbuzios\r\ncaatinga\r\ncabuqui\r\ncacunda\r\ncafuzo\r\ncajueiro\r\ncamurca\r\ncanudo\r\ncaquizeiro\r\ncarvoeiro\r\ncasulo\r\ncatuaba\r\ncauterizar\r\ncebolinha\r\ncedula\r\nceifeiro\r\ncelulose\r\ncerzir\r\ncesto\r\ncetro\r\nceus\r\ncevar\r\nchavena\r\ncheroqui\r\nchita\r\nchovido\r\nchuvoso\r\nciatico\r\ncibernetico\r\ncicuta\r\ncidreira\r\ncientistas\r\ncifrar\r\ncigarro\r\ncilio\r\ncimo\r\ncinzento\r\ncioso\r\ncipriota\r\ncirurgico\r\ncisto\r\ncitrico\r\nciumento\r\ncivismo\r\nclavicula\r\nclero\r\nclitoris\r\ncluster\r\ncoaxial\r\ncobrir\r\ncocota\r\ncodorniz\r\ncoexistir\r\ncogumelo\r\ncoito\r\ncolusao\r\ncompaixao\r\ncomutativo\r\ncontentamento\r\nconvulsivo\r\ncoordenativa\r\ncoquetel\r\ncorreto\r\ncorvo\r\ncostureiro\r\ncotovia\r\ncovil\r\ncozinheiro\r\ncretino\r\ncristo\r\ncrivo\r\ncrotalo\r\ncruzes\r\ncubo\r\ncucuia\r\ncueiro\r\ncuidar\r\ncujo\r\ncultural\r\ncunilingua\r\ncupula\r\ncurvo\r\ncustoso\r\ncutucar\r\nczarismo\r\ndablio\r\ndacota\r\ndados\r\ndaguerreotipo\r\ndaiquiri\r\ndaltonismo\r\ndamista\r\ndantesco\r\ndaquilo\r\ndarwinista\r\ndasein\r\ndativo\r\ndeao\r\ndebutantes\r\ndecurso\r\ndeduzir\r\ndefunto\r\ndegustar\r\ndejeto\r\ndeltoide\r\ndemover\r\ndenunciar\r\ndeputado\r\ndeque\r\ndervixe\r\ndesvirtuar\r\ndeturpar\r\ndeuteronomio\r\ndevoto\r\ndextrose\r\ndezoito\r\ndiatribe\r\ndicotomico\r\ndidatico\r\ndietista\r\ndifuso\r\ndigressao\r\ndiluvio\r\ndiminuto\r\ndinheiro\r\ndinossauro\r\ndioxido\r\ndiplomatico\r\ndique\r\ndirimivel\r\ndisturbio\r\ndiurno\r\ndivulgar\r\ndizivel\r\ndoar\r\ndobro\r\ndocura\r\ndodoi\r\ndoer\r\ndogue\r\ndoloso\r\ndomo\r\ndonzela\r\ndoping\r\ndorsal\r\ndossie\r\ndote\r\ndoutro\r\ndoze\r\ndravidico\r\ndreno\r\ndriver\r\ndropes\r\ndruso\r\ndubnio\r\nducto\r\ndueto\r\ndulija\r\ndundum\r\nduodeno\r\nduquesa\r\ndurou\r\nduvidoso\r\nduzia\r\nebano\r\nebrio\r\neburneo\r\necharpe\r\neclusa\r\necossistema\r\nectoplasma\r\necumenismo\r\neczema\r\neden\r\neditorial\r\nedredom\r\nedulcorar\r\nefetuar\r\nefigie\r\nefluvio\r\negiptologo\r\negresso\r\negua\r\neinsteiniano\r\neira\r\neivar\r\neixos\r\nejetar\r\nelastomero\r\neldorado\r\nelixir\r\nelmo\r\neloquente\r\nelucidativo\r\nemaranhar\r\nembutir\r\nemerito\r\nemfa\r\nemitir\r\nemotivo\r\nempuxo\r\nemulsao\r\nenamorar\r\nencurvar\r\nenduro\r\nenevoar\r\nenfurnar\r\nenguico\r\nenho\r\nenigmista\r\nenlutar\r\nenormidade\r\nenpreendimento\r\nenquanto\r\nenriquecer\r\nenrugar\r\nentusiastico\r\nenunciar\r\nenvolvimento\r\nenxuto\r\nenzimatico\r\neolico\r\nepiteto\r\nepoxi\r\nepura\r\nequivoco\r\nerario\r\nerbio\r\nereto\r\nerguido\r\nerisipela\r\nermo\r\nerotizar\r\nerros\r\nerupcao\r\nervilha\r\nesburacar\r\nescutar\r\nesfuziante\r\nesguio\r\nesloveno\r\nesmurrar\r\nesoterismo\r\nesperanca\r\nespirito\r\nespurio\r\nessencialmente\r\nesturricar\r\nesvoacar\r\netario\r\neterno\r\netiquetar\r\netnologo\r\netos\r\netrusco\r\neuclidiano\r\neuforico\r\neugenico\r\neunuco\r\neuropio\r\neustaquio\r\neutanasia\r\nevasivo\r\neventualidade\r\nevitavel\r\nevoluir\r\nexaustor\r\nexcursionista\r\nexercito\r\nexfoliado\r\nexito\r\nexotico\r\nexpurgo\r\nexsudar\r\nextrusora\r\nexumar\r\nfabuloso\r\nfacultativo\r\nfado\r\nfagulha\r\nfaixas\r\nfajuto\r\nfaltoso\r\nfamoso\r\nfanzine\r\nfapesp\r\nfaquir\r\nfartura\r\nfastio\r\nfaturista\r\nfausto\r\nfavorito\r\nfaxineira\r\nfazer\r\nfealdade\r\nfebril\r\nfecundo\r\nfedorento\r\nfeerico\r\nfeixe\r\nfelicidade\r\nfelipe\r\nfeltro\r\nfemur\r\nfenotipo\r\nfervura\r\nfestivo\r\nfeto\r\nfeudo\r\nfevereiro\r\nfezinha\r\nfiasco\r\nfibra\r\nficticio\r\nfiduciario\r\nfiesp\r\nfifa\r\nfigurino\r\nfijiano\r\nfiltro\r\nfinura\r\nfiorde\r\nfiquei\r\nfirula\r\nfissurar\r\nfitoteca\r\nfivela\r\nfixo\r\nflavio\r\nflexor\r\nflibusteiro\r\nflotilha\r\nfluxograma\r\nfobos\r\nfoco\r\nfofura\r\nfoguista\r\nfoie\r\nfoliculo\r\nfominha\r\nfonte\r\nforum\r\nfosso\r\nfotossintese\r\nfoxtrote\r\nfraudulento\r\nfrevo\r\nfrivolo\r\nfrouxo\r\nfrutose\r\nfuba\r\nfucsia\r\nfugitivo\r\nfuinha\r\nfujao\r\nfulustreco\r\nfumo\r\nfunileiro\r\nfurunculo\r\nfustigar\r\nfuturologo\r\nfuxico\r\nfuzue\r\ngabriel\r\ngado\r\ngaelico\r\ngafieira\r\ngaguejo\r\ngaivota\r\ngajo\r\ngalvanoplastico\r\ngamo\r\nganso\r\ngarrucha\r\ngastronomo\r\ngatuno\r\ngaussiano\r\ngaviao\r\ngaxeta\r\ngazeteiro\r\ngear\r\ngeiser\r\ngeminiano\r\ngeneroso\r\ngenuino\r\ngeossinclinal\r\ngerundio\r\ngestual\r\ngetulista\r\ngibi\r\ngigolo\r\ngilete\r\nginseng\r\ngiroscopio\r\nglaucio\r\nglacial\r\ngleba\r\nglifo\r\nglote\r\nglutonia\r\ngnostico\r\ngoela\r\ngogo\r\ngoitaca\r\ngolpista\r\ngomo\r\ngonzo\r\ngorro\r\ngostou\r\ngoticula\r\ngourmet\r\ngoverno\r\ngozo\r\ngraxo\r\ngrevista\r\ngrito\r\ngrotesco\r\ngruta\r\nguaxinim\r\ngude\r\ngueto\r\nguizo\r\nguloso\r\ngume\r\nguru\r\ngustativo\r\ngustavo\r\ngutural\r\nhabitue\r\nhaitiano\r\nhalterofilista\r\nhamburguer\r\nhanseniase\r\nhappening\r\nharpista\r\nhastear\r\nhaveres\r\nhebreu\r\nhectometro\r\nhedonista\r\nhegira\r\nhelena\r\nhelminto\r\nhemorroidas\r\nhenrique\r\nheptassilabo\r\nhertziano\r\nhesitar\r\nheterossexual\r\nheuristico\r\nhexagono\r\nhiato\r\nhibrido\r\nhidrostatico\r\nhieroglifo\r\nhifenizar\r\nhigienizar\r\nhilario\r\nhimen\r\nhino\r\nhippie\r\nhirsuto\r\nhistoriografia\r\nhitlerista\r\nhodometro\r\nhoje\r\nholograma\r\nhomus\r\nhonroso\r\nhoquei\r\nhorto\r\nhostilizar\r\nhotentote\r\nhuguenote\r\nhumilde\r\nhuno\r\nhurra\r\nhutu\r\niaia\r\nialorixa\r\niambico\r\niansa\r\niaque\r\niara\r\niatista\r\niberico\r\nibis\r\nicar\r\niceberg\r\nicosagono\r\nidade\r\nideologo\r\nidiotice\r\nidoso\r\niemenita\r\niene\r\nigarape\r\niglu\r\nignorar\r\nigreja\r\niguaria\r\niidiche\r\nilativo\r\niletrado\r\nilharga\r\nilimitado\r\nilogismo\r\nilustrissimo\r\nimaturo\r\nimbuzeiro\r\nimerso\r\nimitavel\r\nimovel\r\nimputar\r\nimutavel\r\ninaveriguavel\r\nincutir\r\ninduzir\r\ninextricavel\r\ninfusao\r\ningua\r\ninhame\r\niniquo\r\ninjusto\r\ninning\r\ninoxidavel\r\ninquisitorial\r\ninsustentavel\r\nintumescimento\r\ninutilizavel\r\ninvulneravel\r\ninzoneiro\r\niodo\r\niogurte\r\nioio\r\nionosfera\r\nioruba\r\niota\r\nipsilon\r\nirascivel\r\niris\r\nirlandes\r\nirmaos\r\niroques\r\nirrupcao\r\nisca\r\nisento\r\nislandes\r\nisotopo\r\nisqueiro\r\nisraelita\r\nisso\r\nisto\r\niterbio\r\nitinerario\r\nitrio\r\niuane\r\niugoslavo\r\njabuticabeira\r\njacutinga\r\njade\r\njagunco\r\njainista\r\njaleco\r\njambo\r\njantarada\r\njapones\r\njaqueta\r\njarro\r\njasmim\r\njato\r\njaula\r\njavel\r\njazz\r\njegue\r\njeitoso\r\njejum\r\njenipapo\r\njeova\r\njequitiba\r\njersei\r\njesus\r\njetom\r\njiboia\r\njihad\r\njilo\r\njingle\r\njipe\r\njocoso\r\njoelho\r\njoguete\r\njoio\r\njojoba\r\njorro\r\njota\r\njoule\r\njoviano\r\njubiloso\r\njudoca\r\njugular\r\njuizo\r\njujuba\r\njuliano\r\njumento\r\njunto\r\njururu\r\njusto\r\njuta\r\njuventude\r\nlabutar\r\nlaguna\r\nlaico\r\nlajota\r\nlanterninha\r\nlapso\r\nlaquear\r\nlastro\r\nlauto\r\nlavrar\r\nlaxativo\r\nlazer\r\nleasing\r\nlebre\r\nlecionar\r\nledo\r\nleguminoso\r\nleitura\r\nlele\r\nlemure\r\nlento\r\nleonardo\r\nleopardo\r\nlepton\r\nleque\r\nleste\r\nletreiro\r\nleucocito\r\nlevitico\r\nlexicologo\r\nlhama\r\nlhufas\r\nliame\r\nlicoroso\r\nlidocaina\r\nliliputiano\r\nlimusine\r\nlinotipo\r\nlipoproteina\r\nliquidos\r\nlirismo\r\nlisura\r\nliturgico\r\nlivros\r\nlixo\r\nlobulo\r\nlocutor\r\nlodo\r\nlogro\r\nlojista\r\nlombriga\r\nlontra\r\nloop\r\nloquaz\r\nlorota\r\nlosango\r\nlotus\r\nlouvor\r\nluar\r\nlubrificavel\r\nlucros\r\nlugubre\r\nluis\r\nluminoso\r\nluneta\r\nlustroso\r\nluto\r\nluvas\r\nluxuriante\r\nluzeiro\r\nmaduro\r\nmaestro\r\nmafioso\r\nmagro\r\nmaiuscula\r\nmajoritario\r\nmalvisto\r\nmamute\r\nmanutencao\r\nmapoteca\r\nmaquinista\r\nmarzipa\r\nmasturbar\r\nmatuto\r\nmausoleu\r\nmavioso\r\nmaxixe\r\nmazurca\r\nmeandro\r\nmecha\r\nmedusa\r\nmefistofelico\r\nmegera\r\nmeirinho\r\nmelro\r\nmemorizar\r\nmenu\r\nmequetrefe\r\nmertiolate\r\nmestria\r\nmetroviario\r\nmexilhao\r\nmezanino\r\nmiau\r\nmicrossegundo\r\nmidia\r\nmigratorio\r\nmimosa\r\nminuto\r\nmiosotis\r\nmirtilo\r\nmisturar\r\nmitzvah\r\nmiudos\r\nmixuruca\r\nmnemonico\r\nmoagem\r\nmobilizar\r\nmodulo\r\nmoer\r\nmofo\r\nmogno\r\nmoita\r\nmolusco\r\nmonumento\r\nmoqueca\r\nmorubixaba\r\nmostruario\r\nmotriz\r\nmouse\r\nmovivel\r\nmozarela\r\nmuarra\r\nmuculmano\r\nmudo\r\nmugir\r\nmuitos\r\nmumunha\r\nmunir\r\nmuon\r\nmuquira\r\nmurros\r\nmusselina\r\nnacoes\r\nnado\r\nnaftalina\r\nnago\r\nnaipe\r\nnaja\r\nnalgum\r\nnamoro\r\nnanquim\r\nnapolitano\r\nnaquilo\r\nnascimento\r\nnautilo\r\nnavios\r\nnazista\r\nnebuloso\r\nnectarina\r\nnefrologo\r\nnegus\r\nnelore\r\nnenufar\r\nnepotismo\r\nnervura\r\nneste\r\nnetuno\r\nneutron\r\nnevoeiro\r\nnewtoniano\r\nnexo\r\nnhenhenhem\r\nnhoque\r\nnigeriano\r\nniilista\r\nninho\r\nniobio\r\nniponico\r\nniquelar\r\nnirvana\r\nnisto\r\nnitroglicerina\r\nnivoso\r\nnobreza\r\nnocivo\r\nnoel\r\nnogueira\r\nnoivo\r\nnojo\r\nnominativo\r\nnonuplo\r\nnoruegues\r\nnostalgico\r\nnoturno\r\nnouveau\r\nnuanca\r\nnublar\r\nnucleotideo\r\nnudista\r\nnulo\r\nnumismatico\r\nnunquinha\r\nnupcias\r\nnutritivo\r\nnuvens\r\noasis\r\nobcecar\r\nobeso\r\nobituario\r\nobjetos\r\noblongo\r\nobnoxio\r\nobrigatorio\r\nobstruir\r\nobtuso\r\nobus\r\nobvio\r\nocaso\r\noccipital\r\noceanografo\r\nocioso\r\noclusivo\r\nocorrer\r\nocre\r\noctogono\r\nodalisca\r\nodisseia\r\nodorifico\r\noersted\r\noeste\r\nofertar\r\nofidio\r\noftalmologo\r\nogiva\r\nogum\r\noigale\r\noitavo\r\noitocentos\r\nojeriza\r\nolaria\r\noleoso\r\nolfato\r\nolhos\r\noliveira\r\nolmo\r\nolor\r\nolvidavel\r\nombudsman\r\nomeleteira\r\nomitir\r\nomoplata\r\nonanismo\r\nondular\r\noneroso\r\nonomatopeico\r\nontologico\r\nonus\r\nonze\r\nopalescente\r\nopcional\r\noperistico\r\nopio\r\noposto\r\noprobrio\r\noptometrista\r\nopusculo\r\noratorio\r\norbital\r\norcar\r\norfao\r\norixa\r\norla\r\nornitologo\r\norquidea\r\nortorrombico\r\norvalho\r\nosculo\r\nosmotico\r\nossudo\r\nostrogodo\r\notario\r\notite\r\nouro\r\nousar\r\noutubro\r\nouvir\r\novario\r\novernight\r\noviparo\r\novni\r\novoviviparo\r\novulo\r\noxala\r\noxente\r\noxiuro\r\noxossi\r\nozonizar\r\npaciente\r\npactuar\r\npadronizar\r\npaete\r\npagodeiro\r\npaixao\r\npajem\r\npaludismo\r\npampas\r\npanturrilha\r\npapudo\r\npaquistanes\r\npastoso\r\npatua\r\npaulo\r\npauzinhos\r\npavoroso\r\npaxa\r\npazes\r\npeao\r\npecuniario\r\npedunculo\r\npegaso\r\npeixinho\r\npejorativo\r\npelvis\r\npenuria\r\npequno\r\npetunia\r\npezada\r\npiauiense\r\npictorico\r\npierro\r\npigmeu\r\npijama\r\npilulas\r\npimpolho\r\npintura\r\npiorar\r\npipocar\r\npiqueteiro\r\npirulito\r\npistoleiro\r\npituitaria\r\npivotar\r\npixote\r\npizzaria\r\nplistoceno\r\nplotar\r\npluviometrico\r\npneumonico\r\npoco\r\npodridao\r\npoetisa\r\npogrom\r\npois\r\npolvorosa\r\npomposo\r\nponderado\r\npontudo\r\npopuloso\r\npoquer\r\nporvir\r\nposudo\r\npotro\r\npouso\r\npovoar\r\nprazo\r\nprezar\r\nprivilegios\r\nproximo\r\nprussiano\r\npseudopode\r\npsoriase\r\npterossauros\r\nptialina\r\nptolemaico\r\npudor\r\npueril\r\npufe\r\npugilista\r\npuir\r\npujante\r\npulverizar\r\npumba\r\npunk\r\npurulento\r\npustula\r\nputsch\r\npuxe\r\nquatrocentos\r\nquetzal\r\nquixotesco\r\nquotizavel\r\nrabujice\r\nracista\r\nradonio\r\nrafia\r\nragu\r\nrajado\r\nralo\r\nrampeiro\r\nranzinza\r\nraptor\r\nraquitismo\r\nraro\r\nrasurar\r\nratoeira\r\nravioli\r\nrazoavel\r\nreavivar\r\nrebuscar\r\nrecusavel\r\nreduzivel\r\nreexposicao\r\nrefutavel\r\nregurgitar\r\nreivindicavel\r\nrejuvenescimento\r\nrelva\r\nremuneravel\r\nrenunciar\r\nreorientar\r\nrepuxo\r\nrequisito\r\nresumo\r\nreturno\r\nreutilizar\r\nrevolvido\r\nrezonear\r\nriacho\r\nribossomo\r\nricota\r\nridiculo\r\nrifle\r\nrigoroso\r\nrijo\r\nrimel\r\nrins\r\nrios\r\nriqueza\r\nriquixa\r\nrissole\r\nritualistico\r\nrivalizar\r\nrixa\r\nrobusto\r\nrococo\r\nrodoviario\r\nroer\r\nrogo\r\nrojao\r\nrolo\r\nrompimento\r\nronronar\r\nroqueiro\r\nrorqual\r\nrosto\r\nrotundo\r\nrouxinol\r\nroxo\r\nroyal\r\nruas\r\nrucula\r\nrudimentos\r\nruela\r\nrufo\r\nrugoso\r\nruivo\r\nrule\r\nrumoroso\r\nrunico\r\nruptura\r\nrural\r\nrustico\r\nrutilar\r\nsaariano\r\nsabujo\r\nsacudir\r\nsadomasoquista\r\nsafra\r\nsagui\r\nsais\r\nsamurai\r\nsantuario\r\nsapo\r\nsaquear\r\nsartriano\r\nsaturno\r\nsaude\r\nsauva\r\nsaveiro\r\nsaxofonista\r\nsazonal\r\nscherzo\r\nscript\r\nseara\r\nseborreia\r\nsecura\r\nseduzir\r\nsefardim\r\nseguro\r\nseja\r\nselvas\r\nsempre\r\nsenzala\r\nsepultura\r\nsequoia\r\nsestercio\r\nsetuplo\r\nseus\r\nseviciar\r\nsezonismo\r\nshalom\r\nsiames\r\nsibilante\r\nsicrano\r\nsidra\r\nsifilitico\r\nsignos\r\nsilvo\r\nsimultaneo\r\nsinusite\r\nsionista\r\nsirio\r\nsisudo\r\nsituar\r\nsivan\r\nslide\r\nslogan\r\nsoar\r\nsobrio\r\nsocratico\r\nsodomizar\r\nsoerguer\r\nsoftware\r\nsogro\r\nsoja\r\nsolver\r\nsomente\r\nsonso\r\nsopro\r\nsoquete\r\nsorveteiro\r\nsossego\r\nsoturno\r\nsousafone\r\nsovinice\r\nsozinho\r\nsuavizar\r\nsubverter\r\nsucursal\r\nsudoriparo\r\nsufragio\r\nsugestoes\r\nsuite\r\nsujo\r\nsultao\r\nsumula\r\nsuntuoso\r\nsuor\r\nsupurar\r\nsuruba\r\nsusto\r\nsuturar\r\nsuvenir\r\ntabuleta\r\ntaco\r\ntadjique\r\ntafeta\r\ntagarelice\r\ntaitiano\r\ntalvez\r\ntampouco\r\ntanzaniano\r\ntaoista\r\ntapume\r\ntaquion\r\ntarugo\r\ntascar\r\ntatuar\r\ntautologico\r\ntavola\r\ntaxionomista\r\ntchecoslovaco\r\nteatrologo\r\ntectonismo\r\ntedioso\r\nteflon\r\ntegumento\r\nteixo\r\ntelurio\r\ntemporas\r\ntenue\r\nteosofico\r\ntepido\r\ntequila\r\nterrorista\r\ntestosterona\r\ntetrico\r\nteutonico\r\nteve\r\ntexugo\r\ntiara\r\ntibia\r\ntiete\r\ntifoide\r\ntigresa\r\ntijolo\r\ntilintar\r\ntimpano\r\ntintureiro\r\ntiquete\r\ntiroteio\r\ntisico\r\ntitulos\r\ntive\r\ntoar\r\ntoboga\r\ntofu\r\ntogoles\r\ntoicinho\r\ntolueno\r\ntomografo\r\ntontura\r\ntoponimo\r\ntoquio\r\ntorvelinho\r\ntostar\r\ntoto\r\ntouro\r\ntoxina\r\ntrazer\r\ntrezentos\r\ntrivialidade\r\ntrovoar\r\ntruta\r\ntuaregue\r\ntubular\r\ntucano\r\ntudo\r\ntufo\r\ntuiste\r\ntulipa\r\ntumultuoso\r\ntunisino\r\ntupiniquim\r\nturvo\r\ntutu\r\nucraniano\r\nudenista\r\nufanista\r\nufologo\r\nugaritico\r\nuiste\r\nuivo\r\nulceroso\r\nulema\r\nultravioleta\r\numbilical\r\numero\r\numido\r\numlaut\r\nunanimidade\r\nunesco\r\nungulado\r\nunheiro\r\nunivoco\r\nuntuoso\r\nurano\r\nurbano\r\nurdir\r\nuretra\r\nurgente\r\nurinol\r\nurna\r\nurologo\r\nurro\r\nursulina\r\nurtiga\r\nurupe\r\nusavel\r\nusbeque\r\nusei\r\nusineiro\r\nusurpar\r\nutero\r\nutilizar\r\nutopico\r\nuvular\r\nuxoricidio\r\nvacuo\r\nvadio\r\nvaguear\r\nvaivem\r\nvalvula\r\nvampiro\r\nvantajoso\r\nvaporoso\r\nvaquinha\r\nvarziano\r\nvasto\r\nvaticinio\r\nvaudeville\r\nvazio\r\nveado\r\nvedico\r\nveemente\r\nvegetativo\r\nveio\r\nveja\r\nveludo\r\nvenusiano\r\nverdade\r\nverve\r\nvestuario\r\nvetusto\r\nvexatorio\r\nvezes\r\nviavel\r\nvibratorio\r\nvictor\r\nvicunha\r\nvidros\r\nvietnamita\r\nvigoroso\r\nvilipendiar\r\nvime\r\nvintem\r\nvioloncelo\r\nviquingue\r\nvirus\r\nvisualizar\r\nvituperio\r\nviuvo\r\nvivo\r\nvizir\r\nvoar\r\nvociferar\r\nvodu\r\nvogar\r\nvoile\r\nvolver\r\nvomito\r\nvontade\r\nvortice\r\nvosso\r\nvoto\r\nvovozinha\r\nvoyeuse\r\nvozes\r\nvulva\r\nvupt\r\nwestern\r\nxadrez\r\nxale\r\nxampu\r\nxango\r\nxarope\r\nxaual\r\nxavante\r\nxaxim\r\nxenonio\r\nxepa\r\nxerox\r\nxicara\r\nxifopago\r\nxiita\r\nxilogravura\r\nxinxim\r\nxistoso\r\nxixi\r\nxodo\r\nxogum\r\nxucro\r\nzabumba\r\nzagueiro\r\nzambiano\r\nzanzar\r\nzarpar\r\nzebu\r\nzefiro\r\nzeloso\r\nzenite\r\nzumbi\r\n"
  },
  {
    "path": "lib/wordlist/spanish.txt",
    "content": "ábaco\nabdomen\nabeja\nabierto\nabogado\nabono\naborto\nabrazo\nabrir\nabuelo\nabuso\nacabar\nacademia\nacceso\nacción\naceite\nacelga\nacento\naceptar\nácido\naclarar\nacné\nacoger\nacoso\nactivo\nacto\nactriz\nactuar\nacudir\nacuerdo\nacusar\nadicto\nadmitir\nadoptar\nadorno\naduana\nadulto\naéreo\nafectar\nafición\nafinar\nafirmar\nágil\nagitar\nagonía\nagosto\nagotar\nagregar\nagrio\nagua\nagudo\náguila\naguja\nahogo\nahorro\naire\naislar\najedrez\najeno\najuste\nalacrán\nalambre\nalarma\nalba\nálbum\nalcalde\naldea\nalegre\nalejar\nalerta\naleta\nalfiler\nalga\nalgodón\naliado\naliento\nalivio\nalma\nalmeja\nalmíbar\naltar\nalteza\naltivo\nalto\naltura\nalumno\nalzar\namable\namante\namapola\namargo\namasar\námbar\námbito\nameno\namigo\namistad\namor\namparo\namplio\nancho\nanciano\nancla\nandar\nandén\nanemia\nángulo\nanillo\nánimo\nanís\nanotar\nantena\nantiguo\nantojo\nanual\nanular\nanuncio\nañadir\nañejo\naño\napagar\naparato\napetito\napio\naplicar\napodo\naporte\napoyo\naprender\naprobar\napuesta\napuro\narado\naraña\narar\nárbitro\nárbol\narbusto\narchivo\narco\narder\nardilla\narduo\nárea\nárido\naries\narmonía\narnés\naroma\narpa\narpón\narreglo\narroz\narruga\narte\nartista\nasa\nasado\nasalto\nascenso\nasegurar\naseo\nasesor\nasiento\nasilo\nasistir\nasno\nasombro\náspero\nastilla\nastro\nastuto\nasumir\nasunto\natajo\nataque\natar\natento\nateo\nático\natleta\nátomo\natraer\natroz\natún\naudaz\naudio\nauge\naula\naumento\nausente\nautor\naval\navance\navaro\nave\navellana\navena\navestruz\navión\naviso\nayer\nayuda\nayuno\nazafrán\nazar\nazote\nazúcar\nazufre\nazul\nbaba\nbabor\nbache\nbahía\nbaile\nbajar\nbalanza\nbalcón\nbalde\nbambú\nbanco\nbanda\nbaño\nbarba\nbarco\nbarniz\nbarro\nbáscula\nbastón\nbasura\nbatalla\nbatería\nbatir\nbatuta\nbaúl\nbazar\nbebé\nbebida\nbello\nbesar\nbeso\nbestia\nbicho\nbien\nbingo\nblanco\nbloque\nblusa\nboa\nbobina\nbobo\nboca\nbocina\nboda\nbodega\nboina\nbola\nbolero\nbolsa\nbomba\nbondad\nbonito\nbono\nbonsái\nborde\nborrar\nbosque\nbote\nbotín\nbóveda\nbozal\nbravo\nbrazo\nbrecha\nbreve\nbrillo\nbrinco\nbrisa\nbroca\nbroma\nbronce\nbrote\nbruja\nbrusco\nbruto\nbuceo\nbucle\nbueno\nbuey\nbufanda\nbufón\nbúho\nbuitre\nbulto\nburbuja\nburla\nburro\nbuscar\nbutaca\nbuzón\ncaballo\ncabeza\ncabina\ncabra\ncacao\ncadáver\ncadena\ncaer\ncafé\ncaída\ncaimán\ncaja\ncajón\ncal\ncalamar\ncalcio\ncaldo\ncalidad\ncalle\ncalma\ncalor\ncalvo\ncama\ncambio\ncamello\ncamino\ncampo\ncáncer\ncandil\ncanela\ncanguro\ncanica\ncanto\ncaña\ncañón\ncaoba\ncaos\ncapaz\ncapitán\ncapote\ncaptar\ncapucha\ncara\ncarbón\ncárcel\ncareta\ncarga\ncariño\ncarne\ncarpeta\ncarro\ncarta\ncasa\ncasco\ncasero\ncaspa\ncastor\ncatorce\ncatre\ncaudal\ncausa\ncazo\ncebolla\nceder\ncedro\ncelda\ncélebre\nceloso\ncélula\ncemento\nceniza\ncentro\ncerca\ncerdo\ncereza\ncero\ncerrar\ncerteza\ncésped\ncetro\nchacal\nchaleco\nchampú\nchancla\nchapa\ncharla\nchico\nchiste\nchivo\nchoque\nchoza\nchuleta\nchupar\nciclón\nciego\ncielo\ncien\ncierto\ncifra\ncigarro\ncima\ncinco\ncine\ncinta\nciprés\ncirco\nciruela\ncisne\ncita\nciudad\nclamor\nclan\nclaro\nclase\nclave\ncliente\nclima\nclínica\ncobre\ncocción\ncochino\ncocina\ncoco\ncódigo\ncodo\ncofre\ncoger\ncohete\ncojín\ncojo\ncola\ncolcha\ncolegio\ncolgar\ncolina\ncollar\ncolmo\ncolumna\ncombate\ncomer\ncomida\ncómodo\ncompra\nconde\nconejo\nconga\nconocer\nconsejo\ncontar\ncopa\ncopia\ncorazón\ncorbata\ncorcho\ncordón\ncorona\ncorrer\ncoser\ncosmos\ncosta\ncráneo\ncráter\ncrear\ncrecer\ncreído\ncrema\ncría\ncrimen\ncripta\ncrisis\ncromo\ncrónica\ncroqueta\ncrudo\ncruz\ncuadro\ncuarto\ncuatro\ncubo\ncubrir\ncuchara\ncuello\ncuento\ncuerda\ncuesta\ncueva\ncuidar\nculebra\nculpa\nculto\ncumbre\ncumplir\ncuna\ncuneta\ncuota\ncupón\ncúpula\ncurar\ncurioso\ncurso\ncurva\ncutis\ndama\ndanza\ndar\ndardo\ndátil\ndeber\ndébil\ndécada\ndecir\ndedo\ndefensa\ndefinir\ndejar\ndelfín\ndelgado\ndelito\ndemora\ndenso\ndental\ndeporte\nderecho\nderrota\ndesayuno\ndeseo\ndesfile\ndesnudo\ndestino\ndesvío\ndetalle\ndetener\ndeuda\ndía\ndiablo\ndiadema\ndiamante\ndiana\ndiario\ndibujo\ndictar\ndiente\ndieta\ndiez\ndifícil\ndigno\ndilema\ndiluir\ndinero\ndirecto\ndirigir\ndisco\ndiseño\ndisfraz\ndiva\ndivino\ndoble\ndoce\ndolor\ndomingo\ndon\ndonar\ndorado\ndormir\ndorso\ndos\ndosis\ndragón\ndroga\nducha\nduda\nduelo\ndueño\ndulce\ndúo\nduque\ndurar\ndureza\nduro\nébano\nebrio\nechar\neco\necuador\nedad\nedición\nedificio\neditor\neducar\nefecto\neficaz\neje\nejemplo\nelefante\nelegir\nelemento\nelevar\nelipse\nélite\nelixir\nelogio\neludir\nembudo\nemitir\nemoción\nempate\nempeño\nempleo\nempresa\nenano\nencargo\nenchufe\nencía\nenemigo\nenero\nenfado\nenfermo\nengaño\nenigma\nenlace\nenorme\nenredo\nensayo\nenseñar\nentero\nentrar\nenvase\nenvío\népoca\nequipo\nerizo\nescala\nescena\nescolar\nescribir\nescudo\nesencia\nesfera\nesfuerzo\nespada\nespejo\nespía\nesposa\nespuma\nesquí\nestar\neste\nestilo\nestufa\netapa\neterno\nética\netnia\nevadir\nevaluar\nevento\nevitar\nexacto\nexamen\nexceso\nexcusa\nexento\nexigir\nexilio\nexistir\néxito\nexperto\nexplicar\nexponer\nextremo\nfábrica\nfábula\nfachada\nfácil\nfactor\nfaena\nfaja\nfalda\nfallo\nfalso\nfaltar\nfama\nfamilia\nfamoso\nfaraón\nfarmacia\nfarol\nfarsa\nfase\nfatiga\nfauna\nfavor\nfax\nfebrero\nfecha\nfeliz\nfeo\nferia\nferoz\nfértil\nfervor\nfestín\nfiable\nfianza\nfiar\nfibra\nficción\nficha\nfideo\nfiebre\nfiel\nfiera\nfiesta\nfigura\nfijar\nfijo\nfila\nfilete\nfilial\nfiltro\nfin\nfinca\nfingir\nfinito\nfirma\nflaco\nflauta\nflecha\nflor\nflota\nfluir\nflujo\nflúor\nfobia\nfoca\nfogata\nfogón\nfolio\nfolleto\nfondo\nforma\nforro\nfortuna\nforzar\nfosa\nfoto\nfracaso\nfrágil\nfranja\nfrase\nfraude\nfreír\nfreno\nfresa\nfrío\nfrito\nfruta\nfuego\nfuente\nfuerza\nfuga\nfumar\nfunción\nfunda\nfurgón\nfuria\nfusil\nfútbol\nfuturo\ngacela\ngafas\ngaita\ngajo\ngala\ngalería\ngallo\ngamba\nganar\ngancho\nganga\nganso\ngaraje\ngarza\ngasolina\ngastar\ngato\ngavilán\ngemelo\ngemir\ngen\ngénero\ngenio\ngente\ngeranio\ngerente\ngermen\ngesto\ngigante\ngimnasio\ngirar\ngiro\nglaciar\nglobo\ngloria\ngol\ngolfo\ngoloso\ngolpe\ngoma\ngordo\ngorila\ngorra\ngota\ngoteo\ngozar\ngrada\ngráfico\ngrano\ngrasa\ngratis\ngrave\ngrieta\ngrillo\ngripe\ngris\ngrito\ngrosor\ngrúa\ngrueso\ngrumo\ngrupo\nguante\nguapo\nguardia\nguerra\nguía\nguiño\nguion\nguiso\nguitarra\ngusano\ngustar\nhaber\nhábil\nhablar\nhacer\nhacha\nhada\nhallar\nhamaca\nharina\nhaz\nhazaña\nhebilla\nhebra\nhecho\nhelado\nhelio\nhembra\nherir\nhermano\nhéroe\nhervir\nhielo\nhierro\nhígado\nhigiene\nhijo\nhimno\nhistoria\nhocico\nhogar\nhoguera\nhoja\nhombre\nhongo\nhonor\nhonra\nhora\nhormiga\nhorno\nhostil\nhoyo\nhueco\nhuelga\nhuerta\nhueso\nhuevo\nhuida\nhuir\nhumano\nhúmedo\nhumilde\nhumo\nhundir\nhuracán\nhurto\nicono\nideal\nidioma\nídolo\niglesia\niglú\nigual\nilegal\nilusión\nimagen\nimán\nimitar\nimpar\nimperio\nimponer\nimpulso\nincapaz\níndice\ninerte\ninfiel\ninforme\ningenio\ninicio\ninmenso\ninmune\ninnato\ninsecto\ninstante\ninterés\níntimo\nintuir\ninútil\ninvierno\nira\niris\nironía\nisla\nislote\njabalí\njabón\njamón\njarabe\njardín\njarra\njaula\njazmín\njefe\njeringa\njinete\njornada\njoroba\njoven\njoya\njuerga\njueves\njuez\njugador\njugo\njuguete\njuicio\njunco\njungla\njunio\njuntar\njúpiter\njurar\njusto\njuvenil\njuzgar\nkilo\nkoala\nlabio\nlacio\nlacra\nlado\nladrón\nlagarto\nlágrima\nlaguna\nlaico\nlamer\nlámina\nlámpara\nlana\nlancha\nlangosta\nlanza\nlápiz\nlargo\nlarva\nlástima\nlata\nlátex\nlatir\nlaurel\nlavar\nlazo\nleal\nlección\nleche\nlector\nleer\nlegión\nlegumbre\nlejano\nlengua\nlento\nleña\nleón\nleopardo\nlesión\nletal\nletra\nleve\nleyenda\nlibertad\nlibro\nlicor\nlíder\nlidiar\nlienzo\nliga\nligero\nlima\nlímite\nlimón\nlimpio\nlince\nlindo\nlínea\nlingote\nlino\nlinterna\nlíquido\nliso\nlista\nlitera\nlitio\nlitro\nllaga\nllama\nllanto\nllave\nllegar\nllenar\nllevar\nllorar\nllover\nlluvia\nlobo\nloción\nloco\nlocura\nlógica\nlogro\nlombriz\nlomo\nlonja\nlote\nlucha\nlucir\nlugar\nlujo\nluna\nlunes\nlupa\nlustro\nluto\nluz\nmaceta\nmacho\nmadera\nmadre\nmaduro\nmaestro\nmafia\nmagia\nmago\nmaíz\nmaldad\nmaleta\nmalla\nmalo\nmamá\nmambo\nmamut\nmanco\nmando\nmanejar\nmanga\nmaniquí\nmanjar\nmano\nmanso\nmanta\nmañana\nmapa\nmáquina\nmar\nmarco\nmarea\nmarfil\nmargen\nmarido\nmármol\nmarrón\nmartes\nmarzo\nmasa\nmáscara\nmasivo\nmatar\nmateria\nmatiz\nmatriz\nmáximo\nmayor\nmazorca\nmecha\nmedalla\nmedio\nmédula\nmejilla\nmejor\nmelena\nmelón\nmemoria\nmenor\nmensaje\nmente\nmenú\nmercado\nmerengue\nmérito\nmes\nmesón\nmeta\nmeter\nmétodo\nmetro\nmezcla\nmiedo\nmiel\nmiembro\nmiga\nmil\nmilagro\nmilitar\nmillón\nmimo\nmina\nminero\nmínimo\nminuto\nmiope\nmirar\nmisa\nmiseria\nmisil\nmismo\nmitad\nmito\nmochila\nmoción\nmoda\nmodelo\nmoho\nmojar\nmolde\nmoler\nmolino\nmomento\nmomia\nmonarca\nmoneda\nmonja\nmonto\nmoño\nmorada\nmorder\nmoreno\nmorir\nmorro\nmorsa\nmortal\nmosca\nmostrar\nmotivo\nmover\nmóvil\nmozo\nmucho\nmudar\nmueble\nmuela\nmuerte\nmuestra\nmugre\nmujer\nmula\nmuleta\nmulta\nmundo\nmuñeca\nmural\nmuro\nmúsculo\nmuseo\nmusgo\nmúsica\nmuslo\nnácar\nnación\nnadar\nnaipe\nnaranja\nnariz\nnarrar\nnasal\nnatal\nnativo\nnatural\nnáusea\nnaval\nnave\nnavidad\nnecio\nnéctar\nnegar\nnegocio\nnegro\nneón\nnervio\nneto\nneutro\nnevar\nnevera\nnicho\nnido\nniebla\nnieto\nniñez\nniño\nnítido\nnivel\nnobleza\nnoche\nnómina\nnoria\nnorma\nnorte\nnota\nnoticia\nnovato\nnovela\nnovio\nnube\nnuca\nnúcleo\nnudillo\nnudo\nnuera\nnueve\nnuez\nnulo\nnúmero\nnutria\noasis\nobeso\nobispo\nobjeto\nobra\nobrero\nobservar\nobtener\nobvio\noca\nocaso\nocéano\nochenta\nocho\nocio\nocre\noctavo\noctubre\noculto\nocupar\nocurrir\nodiar\nodio\nodisea\noeste\nofensa\noferta\noficio\nofrecer\nogro\noído\noír\nojo\nola\noleada\nolfato\nolivo\nolla\nolmo\nolor\nolvido\nombligo\nonda\nonza\nopaco\nopción\nópera\nopinar\noponer\noptar\nóptica\nopuesto\noración\norador\noral\nórbita\norca\norden\noreja\nórgano\norgía\norgullo\noriente\norigen\norilla\noro\norquesta\noruga\nosadía\noscuro\nosezno\noso\nostra\notoño\notro\noveja\nóvulo\nóxido\noxígeno\noyente\nozono\npacto\npadre\npaella\npágina\npago\npaís\npájaro\npalabra\npalco\npaleta\npálido\npalma\npaloma\npalpar\npan\npanal\npánico\npantera\npañuelo\npapá\npapel\npapilla\npaquete\nparar\nparcela\npared\nparir\nparo\npárpado\nparque\npárrafo\nparte\npasar\npaseo\npasión\npaso\npasta\npata\npatio\npatria\npausa\npauta\npavo\npayaso\npeatón\npecado\npecera\npecho\npedal\npedir\npegar\npeine\npelar\npeldaño\npelea\npeligro\npellejo\npelo\npeluca\npena\npensar\npeñón\npeón\npeor\npepino\npequeño\npera\npercha\nperder\npereza\nperfil\nperico\nperla\npermiso\nperro\npersona\npesa\npesca\npésimo\npestaña\npétalo\npetróleo\npez\npezuña\npicar\npichón\npie\npiedra\npierna\npieza\npijama\npilar\npiloto\npimienta\npino\npintor\npinza\npiña\npiojo\npipa\npirata\npisar\npiscina\npiso\npista\npitón\npizca\nplaca\nplan\nplata\nplaya\nplaza\npleito\npleno\nplomo\npluma\nplural\npobre\npoco\npoder\npodio\npoema\npoesía\npoeta\npolen\npolicía\npollo\npolvo\npomada\npomelo\npomo\npompa\nponer\nporción\nportal\nposada\nposeer\nposible\nposte\npotencia\npotro\npozo\nprado\nprecoz\npregunta\npremio\nprensa\npreso\nprevio\nprimo\npríncipe\nprisión\nprivar\nproa\nprobar\nproceso\nproducto\nproeza\nprofesor\nprograma\nprole\npromesa\npronto\npropio\npróximo\nprueba\npúblico\npuchero\npudor\npueblo\npuerta\npuesto\npulga\npulir\npulmón\npulpo\npulso\npuma\npunto\npuñal\npuño\npupa\npupila\npuré\nquedar\nqueja\nquemar\nquerer\nqueso\nquieto\nquímica\nquince\nquitar\nrábano\nrabia\nrabo\nración\nradical\nraíz\nrama\nrampa\nrancho\nrango\nrapaz\nrápido\nrapto\nrasgo\nraspa\nrato\nrayo\nraza\nrazón\nreacción\nrealidad\nrebaño\nrebote\nrecaer\nreceta\nrechazo\nrecoger\nrecreo\nrecto\nrecurso\nred\nredondo\nreducir\nreflejo\nreforma\nrefrán\nrefugio\nregalo\nregir\nregla\nregreso\nrehén\nreino\nreír\nreja\nrelato\nrelevo\nrelieve\nrelleno\nreloj\nremar\nremedio\nremo\nrencor\nrendir\nrenta\nreparto\nrepetir\nreposo\nreptil\nres\nrescate\nresina\nrespeto\nresto\nresumen\nretiro\nretorno\nretrato\nreunir\nrevés\nrevista\nrey\nrezar\nrico\nriego\nrienda\nriesgo\nrifa\nrígido\nrigor\nrincón\nriñón\nrío\nriqueza\nrisa\nritmo\nrito\nrizo\nroble\nroce\nrociar\nrodar\nrodeo\nrodilla\nroer\nrojizo\nrojo\nromero\nromper\nron\nronco\nronda\nropa\nropero\nrosa\nrosca\nrostro\nrotar\nrubí\nrubor\nrudo\nrueda\nrugir\nruido\nruina\nruleta\nrulo\nrumbo\nrumor\nruptura\nruta\nrutina\nsábado\nsaber\nsabio\nsable\nsacar\nsagaz\nsagrado\nsala\nsaldo\nsalero\nsalir\nsalmón\nsalón\nsalsa\nsalto\nsalud\nsalvar\nsamba\nsanción\nsandía\nsanear\nsangre\nsanidad\nsano\nsanto\nsapo\nsaque\nsardina\nsartén\nsastre\nsatán\nsauna\nsaxofón\nsección\nseco\nsecreto\nsecta\nsed\nseguir\nseis\nsello\nselva\nsemana\nsemilla\nsenda\nsensor\nseñal\nseñor\nseparar\nsepia\nsequía\nser\nserie\nsermón\nservir\nsesenta\nsesión\nseta\nsetenta\nsevero\nsexo\nsexto\nsidra\nsiesta\nsiete\nsiglo\nsigno\nsílaba\nsilbar\nsilencio\nsilla\nsímbolo\nsimio\nsirena\nsistema\nsitio\nsituar\nsobre\nsocio\nsodio\nsol\nsolapa\nsoldado\nsoledad\nsólido\nsoltar\nsolución\nsombra\nsondeo\nsonido\nsonoro\nsonrisa\nsopa\nsoplar\nsoporte\nsordo\nsorpresa\nsorteo\nsostén\nsótano\nsuave\nsubir\nsuceso\nsudor\nsuegra\nsuelo\nsueño\nsuerte\nsufrir\nsujeto\nsultán\nsumar\nsuperar\nsuplir\nsuponer\nsupremo\nsur\nsurco\nsureño\nsurgir\nsusto\nsutil\ntabaco\ntabique\ntabla\ntabú\ntaco\ntacto\ntajo\ntalar\ntalco\ntalento\ntalla\ntalón\ntamaño\ntambor\ntango\ntanque\ntapa\ntapete\ntapia\ntapón\ntaquilla\ntarde\ntarea\ntarifa\ntarjeta\ntarot\ntarro\ntarta\ntatuaje\ntauro\ntaza\ntazón\nteatro\ntecho\ntecla\ntécnica\ntejado\ntejer\ntejido\ntela\nteléfono\ntema\ntemor\ntemplo\ntenaz\ntender\ntener\ntenis\ntenso\nteoría\nterapia\nterco\ntérmino\nternura\nterror\ntesis\ntesoro\ntestigo\ntetera\ntexto\ntez\ntibio\ntiburón\ntiempo\ntienda\ntierra\ntieso\ntigre\ntijera\ntilde\ntimbre\ntímido\ntimo\ntinta\ntío\ntípico\ntipo\ntira\ntirón\ntitán\ntítere\ntítulo\ntiza\ntoalla\ntobillo\ntocar\ntocino\ntodo\ntoga\ntoldo\ntomar\ntono\ntonto\ntopar\ntope\ntoque\ntórax\ntorero\ntormenta\ntorneo\ntoro\ntorpedo\ntorre\ntorso\ntortuga\ntos\ntosco\ntoser\ntóxico\ntrabajo\ntractor\ntraer\ntráfico\ntrago\ntraje\ntramo\ntrance\ntrato\ntrauma\ntrazar\ntrébol\ntregua\ntreinta\ntren\ntrepar\ntres\ntribu\ntrigo\ntripa\ntriste\ntriunfo\ntrofeo\ntrompa\ntronco\ntropa\ntrote\ntrozo\ntruco\ntrueno\ntrufa\ntubería\ntubo\ntuerto\ntumba\ntumor\ntúnel\ntúnica\nturbina\nturismo\nturno\ntutor\nubicar\núlcera\numbral\nunidad\nunir\nuniverso\nuno\nuntar\nuña\nurbano\nurbe\nurgente\nurna\nusar\nusuario\nútil\nutopía\nuva\nvaca\nvacío\nvacuna\nvagar\nvago\nvaina\nvajilla\nvale\nválido\nvalle\nvalor\nválvula\nvampiro\nvara\nvariar\nvarón\nvaso\nvecino\nvector\nvehículo\nveinte\nvejez\nvela\nvelero\nveloz\nvena\nvencer\nvenda\nveneno\nvengar\nvenir\nventa\nvenus\nver\nverano\nverbo\nverde\nvereda\nverja\nverso\nverter\nvía\nviaje\nvibrar\nvicio\nvíctima\nvida\nvídeo\nvidrio\nviejo\nviernes\nvigor\nvil\nvilla\nvinagre\nvino\nviñedo\nviolín\nviral\nvirgo\nvirtud\nvisor\nvíspera\nvista\nvitamina\nviudo\nvivaz\nvivero\nvivir\nvivo\nvolcán\nvolumen\nvolver\nvoraz\nvotar\nvoto\nvoz\nvuelo\nvulgar\nyacer\nyate\nyegua\nyema\nyerno\nyeso\nyodo\nyoga\nyogur\nzafiro\nzanja\nzapato\nzarza\nzona\nzorro\nzumo\nzurdo\n"
  },
  {
    "path": "lib/x509.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nfrom . import util\nfrom .util import profiler, bh2u, get_cert_path\nimport ecdsa\nimport hashlib\n\n# algo OIDs\nALGO_RSA_SHA1 = '1.2.840.113549.1.1.5'\nALGO_RSA_SHA256 = '1.2.840.113549.1.1.11'\nALGO_RSA_SHA384 = '1.2.840.113549.1.1.12'\nALGO_RSA_SHA512 = '1.2.840.113549.1.1.13'\nALGO_ECDSA_SHA256 = '1.2.840.10045.4.3.2'\n\n# prefixes, see http://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm\nPREFIX_RSA_SHA256 = bytearray(\n    [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20])\nPREFIX_RSA_SHA384 = bytearray(\n    [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30])\nPREFIX_RSA_SHA512 = bytearray(\n    [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40])\n\n# types used in ASN1 structured data\nASN1_TYPES = {\n    'BOOLEAN'          : 0x01,\n    'INTEGER'          : 0x02,\n    'BIT STRING'       : 0x03,\n    'OCTET STRING'     : 0x04,\n    'NULL'             : 0x05,\n    'OBJECT IDENTIFIER': 0x06,\n    'SEQUENCE'         : 0x70,\n    'SET'              : 0x71,\n    'PrintableString'  : 0x13,\n    'IA5String'        : 0x16,\n    'UTCTime'          : 0x17,\n    'GeneralizedTime'  : 0x18,\n    'ENUMERATED'       : 0x0A,\n    'UTF8String'       : 0x0C,\n}\n\n\nclass CertificateError(Exception):\n    pass\n\n\n# helper functions\ndef bitstr_to_bytestr(s):\n    if s[0] != 0x00:\n        raise TypeError('no padding')\n    return s[1:]\n\n\ndef bytestr_to_int(s):\n    i = 0\n    for char in s:\n        i <<= 8\n        i |= char\n    return i\n\n\ndef decode_OID(s):\n    r = []\n    r.append(s[0] // 40)\n    r.append(s[0] % 40)\n    k = 0\n    for i in s[1:]:\n        if i < 128:\n            r.append(i + 128 * k)\n            k = 0\n        else:\n            k = (i - 128) + 128 * k\n    return '.'.join(map(str, r))\n\n\ndef encode_OID(oid):\n    x = [int(i) for i in oid.split('.')]\n    s = chr(x[0] * 40 + x[1])\n    for i in x[2:]:\n        ss = chr(i % 128)\n        while i > 128:\n            i //= 128\n            ss = chr(128 + i % 128) + ss\n        s += ss\n    return s\n\n\nclass ASN1_Node(bytes):\n    def get_node(self, ix):\n        # return index of first byte, first content byte and last byte.\n        first = self[ix + 1]\n        if (first & 0x80) == 0:\n            length = first\n            ixf = ix + 2\n            ixl = ixf + length - 1\n        else:\n            lengthbytes = first & 0x7F\n            length = bytestr_to_int(self[ix + 2:ix + 2 + lengthbytes])\n            ixf = ix + 2 + lengthbytes\n            ixl = ixf + length - 1\n        return ix, ixf, ixl\n\n    def root(self):\n        return self.get_node(0)\n\n    def next_node(self, node):\n        ixs, ixf, ixl = node\n        return self.get_node(ixl + 1)\n\n    def first_child(self, node):\n        ixs, ixf, ixl = node\n        if self[ixs] & 0x20 != 0x20:\n            raise TypeError('Can only open constructed types.', hex(self[ixs]))\n        return self.get_node(ixf)\n\n    def is_child_of(node1, node2):\n        ixs, ixf, ixl = node1\n        jxs, jxf, jxl = node2\n        return ((ixf <= jxs) and (jxl <= ixl)) or ((jxf <= ixs) and (ixl <= jxl))\n\n    def get_all(self, node):\n        # return type + length + value\n        ixs, ixf, ixl = node\n        return self[ixs:ixl + 1]\n\n    def get_value_of_type(self, node, asn1_type):\n        # verify type byte and return content\n        ixs, ixf, ixl = node\n        if ASN1_TYPES[asn1_type] != self[ixs]:\n            raise TypeError('Wrong type:', hex(self[ixs]), hex(ASN1_TYPES[asn1_type]))\n        return self[ixf:ixl + 1]\n\n    def get_value(self, node):\n        ixs, ixf, ixl = node\n        return self[ixf:ixl + 1]\n\n    def get_children(self, node):\n        nodes = []\n        ii = self.first_child(node)\n        nodes.append(ii)\n        while ii[2] < node[2]:\n            ii = self.next_node(ii)\n            nodes.append(ii)\n        return nodes\n\n    def get_sequence(self):\n        return list(map(lambda j: self.get_value(j), self.get_children(self.root())))\n\n    def get_dict(self, node):\n        p = {}\n        for ii in self.get_children(node):\n            for iii in self.get_children(ii):\n                iiii = self.first_child(iii)\n                oid = decode_OID(self.get_value_of_type(iiii, 'OBJECT IDENTIFIER'))\n                iiii = self.next_node(iiii)\n                value = self.get_value(iiii)\n                p[oid] = value\n        return p\n\n\nclass X509(object):\n    def __init__(self, b):\n\n        self.bytes = bytearray(b)\n\n        der = ASN1_Node(b)\n        root = der.root()\n        cert = der.first_child(root)\n        # data for signature\n        self.data = der.get_all(cert)\n\n        # optional version field\n        if der.get_value(cert)[0] == 0xa0:\n            version = der.first_child(cert)\n            serial_number = der.next_node(version)\n        else:\n            serial_number = der.first_child(cert)\n        self.serial_number = bytestr_to_int(der.get_value_of_type(serial_number, 'INTEGER'))\n\n        # signature algorithm\n        sig_algo = der.next_node(serial_number)\n        ii = der.first_child(sig_algo)\n        self.sig_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))\n\n        # issuer\n        issuer = der.next_node(sig_algo)\n        self.issuer = der.get_dict(issuer)\n\n        # validity\n        validity = der.next_node(issuer)\n        ii = der.first_child(validity)\n        try:\n            self.notBefore = der.get_value_of_type(ii, 'UTCTime')\n        except TypeError:\n            self.notBefore = der.get_value_of_type(ii, 'GeneralizedTime')[2:]  # strip year\n        ii = der.next_node(ii)\n        try:\n            self.notAfter = der.get_value_of_type(ii, 'UTCTime')\n        except TypeError:\n            self.notAfter = der.get_value_of_type(ii, 'GeneralizedTime')[2:]  # strip year\n\n        # subject\n        subject = der.next_node(validity)\n        self.subject = der.get_dict(subject)\n        subject_pki = der.next_node(subject)\n        public_key_algo = der.first_child(subject_pki)\n        ii = der.first_child(public_key_algo)\n        self.public_key_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))\n\n        if self.public_key_algo != '1.2.840.10045.2.1':  # for non EC public key\n            # pubkey modulus and exponent\n            subject_public_key = der.next_node(public_key_algo)\n            spk = der.get_value_of_type(subject_public_key, 'BIT STRING')\n            spk = ASN1_Node(bitstr_to_bytestr(spk))\n            r = spk.root()\n            modulus = spk.first_child(r)\n            exponent = spk.next_node(modulus)\n            rsa_n = spk.get_value_of_type(modulus, 'INTEGER')\n            rsa_e = spk.get_value_of_type(exponent, 'INTEGER')\n            self.modulus = ecdsa.util.string_to_number(rsa_n)\n            self.exponent = ecdsa.util.string_to_number(rsa_e)\n        else:\n            subject_public_key = der.next_node(public_key_algo)\n            spk = der.get_value_of_type(subject_public_key, 'BIT STRING')\n            self.ec_public_key = spk\n\n        # extensions\n        self.CA = False\n        self.AKI = None\n        self.SKI = None\n        i = subject_pki\n        while i[2] < cert[2]:\n            i = der.next_node(i)\n            d = der.get_dict(i)\n            for oid, value in d.items():\n                value = ASN1_Node(value)\n                if oid == '2.5.29.19':\n                    # Basic Constraints\n                    self.CA = bool(value)\n                elif oid == '2.5.29.14':\n                    # Subject Key Identifier\n                    r = value.root()\n                    value = value.get_value_of_type(r, 'OCTET STRING')\n                    self.SKI = bh2u(value)\n                elif oid == '2.5.29.35':\n                    # Authority Key Identifier\n                    self.AKI = bh2u(value.get_sequence()[0])\n                else:\n                    pass\n\n        # cert signature\n        cert_sig_algo = der.next_node(cert)\n        ii = der.first_child(cert_sig_algo)\n        self.cert_sig_algo = decode_OID(der.get_value_of_type(ii, 'OBJECT IDENTIFIER'))\n        cert_sig = der.next_node(cert_sig_algo)\n        self.signature = der.get_value(cert_sig)[1:]\n\n    def get_keyID(self):\n        # http://security.stackexchange.com/questions/72077/validating-an-ssl-certificate-chain-according-to-rfc-5280-am-i-understanding-th\n        return self.SKI if self.SKI else repr(self.subject)\n\n    def get_issuer_keyID(self):\n        return self.AKI if self.AKI else repr(self.issuer)\n\n    def get_common_name(self):\n        return self.subject.get('2.5.4.3', 'unknown').decode()\n\n    def get_signature(self):\n        return self.cert_sig_algo, self.signature, self.data\n\n    def check_ca(self):\n        return self.CA\n\n    def check_date(self):\n        import time\n        now = time.time()\n        TIMESTAMP_FMT = '%y%m%d%H%M%SZ'\n        not_before = time.mktime(time.strptime(self.notBefore.decode('ascii'), TIMESTAMP_FMT))\n        not_after = time.mktime(time.strptime(self.notAfter.decode('ascii'), TIMESTAMP_FMT))\n        if not_before > now:\n            raise CertificateError('Certificate has not entered its valid date range. (%s)' % self.get_common_name())\n        if not_after <= now:\n            raise CertificateError('Certificate has expired. (%s)' % self.get_common_name())\n\n    def getFingerprint(self):\n        return hashlib.sha1(self.bytes).digest()\n\n\n@profiler\ndef load_certificates(ca_path):\n    from . import pem\n    ca_list = {}\n    ca_keyID = {}\n    # ca_path = '/tmp/tmp.txt'\n    with open(ca_path, 'r') as f:\n        s = f.read()\n    bList = pem.dePemList(s, \"CERTIFICATE\")\n    for b in bList:\n        try:\n            x = X509(b)\n            x.check_date()\n        except BaseException as e:\n            # with open('/tmp/tmp.txt', 'w') as f:\n            #     f.write(pem.pem(b, 'CERTIFICATE').decode('ascii'))\n            util.print_error(\"cert error:\", e)\n            continue\n\n        fp = x.getFingerprint()\n        ca_list[fp] = x\n        ca_keyID[x.get_keyID()] = fp\n\n    return ca_list, ca_keyID\n\n\nif __name__ == \"__main__\":\n\n    util.set_verbosity(True)\n    ca_path = get_cert_path()\n\n    ca_list, ca_keyID = load_certificates(ca_path)\n"
  },
  {
    "path": "packages.txt",
    "content": "python3-pip\npython3-setuptools\npython3-dev\npython3-pyqt5\npython3-dnspython\npyqt5-dev-tools\nprotobuf-compiler\npython-requests\ngettext\nwine-development\ndirmngr\ngnupg2\nlibudev-dev\nlibusb-1.0-0-dev\ndesktop-file-utils\n"
  },
  {
    "path": "plugins/README",
    "content": "Plugin rules:\n\n * The plugin system of Electrum is designed to allow the development\n   of new features without increasing the core code of Electrum.\n\n * Electrum is written in pure python. if you want to add a feature\n   that requires non-python libraries, then it must be submitted as a\n   plugin. If the feature you want to add requires communication with\n   a remote server (not an Electrum server), then it should be a\n   plugin as well. If the feature you want to add introduces new\n   dependencies in the code, then it should probably be a plugin.\n\n * We expect plugin developers to maintain their plugin code. However,\n   once a plugin is merged in Electrum, we will have to maintain it\n   too, because changes in the Electrum code often require updates in\n   the plugin code. Therefore, plugins have to be easy to maintain. If\n   we believe that a plugin will create too much maintenance work in\n   the future, it will be rejected.\n\n * Plugins should be compatible with Electrum's conventions. If your\n   plugin does not fit with Electrum's architecture, or if we believe\n   that it will create too much maintenance work, it will not be\n   accepted. In particular, do not duplicate existing Electrum code in\n   your plugin.\n\n * We may decide to remove a plugin after it has been merged in\n   Electrum. For this reason, a plugin must be easily removable,\n   without putting at risk the user's bitcoins. If we feel that a\n   plugin cannot be removed without threatening users who rely on it,\n   we will not merge it.\n\n"
  },
  {
    "path": "plugins/__init__.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\n\n"
  },
  {
    "path": "plugins/audio_modem/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname =  _('Audio MODEM')\ndescription = _('Provides support for air-gapped transaction signing.')\nrequires = [('amodem', 'http://github.com/romanz/amodem/')]\navailable_for = ['qt']\n\n"
  },
  {
    "path": "plugins/audio_modem/qt.py",
    "content": "from functools import partial\nimport zlib\nimport json\nfrom io import BytesIO\nimport sys\nimport platform\n\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum_gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog\nfrom electrum.util import print_msg, print_error\nfrom electrum.i18n import _\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton)\n\ntry:\n    import amodem.audio\n    import amodem.main\n    import amodem.config\n    print_error('Audio MODEM is available.')\n    amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))\n    amodem.log.setLevel(amodem.logging.INFO)\nexcept ImportError:\n    amodem = None\n    print_error('Audio MODEM is not found.')\n\n\nclass Plugin(BasePlugin):\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        if self.is_available():\n            self.modem_config = amodem.config.slowest()\n            self.library_name = {\n                'Linux': 'libportaudio.so'\n            }[platform.system()]\n\n    def is_available(self):\n        return amodem is not None\n\n    def requires_settings(self):\n        return True\n\n    def settings_widget(self, window):\n        return EnterButton(_('Settings'), partial(self.settings_dialog, window))\n\n    def settings_dialog(self, window):\n        d = WindowModalDialog(window, _(\"Audio Modem Settings\"))\n\n        layout = QGridLayout(d)\n        layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0)\n\n        bitrates = list(sorted(amodem.config.bitrates.keys()))\n\n        def _index_changed(index):\n            bitrate = bitrates[index]\n            self.modem_config = amodem.config.bitrates[bitrate]\n\n        combo = QComboBox()\n        combo.addItems([str(x) for x in bitrates])\n        combo.currentIndexChanged.connect(_index_changed)\n        layout.addWidget(combo, 0, 1)\n\n        ok_button = QPushButton(_(\"OK\"))\n        ok_button.clicked.connect(d.accept)\n        layout.addWidget(ok_button, 1, 1)\n\n        return bool(d.exec_())\n\n    @hook\n    def transaction_dialog(self, dialog):\n        b = QPushButton()\n        b.setIcon(QIcon(\":icons/speaker.png\"))\n\n        def handler():\n            blob = json.dumps(dialog.tx.as_dict())\n            self._send(parent=dialog, blob=blob)\n        b.clicked.connect(handler)\n        dialog.sharing_buttons.insert(-1, b)\n\n    @hook\n    def scan_text_edit(self, parent):\n        parent.addButton(':icons/microphone.png', partial(self._recv, parent),\n                         _(\"Read from microphone\"))\n\n    @hook\n    def show_text_edit(self, parent):\n        def handler():\n            blob = str(parent.toPlainText())\n            self._send(parent=parent, blob=blob)\n        parent.addButton(':icons/speaker.png', handler, _(\"Send to speaker\"))\n\n    def _audio_interface(self):\n        interface = amodem.audio.Interface(config=self.modem_config)\n        return interface.load(self.library_name)\n\n    def _send(self, parent, blob):\n        def sender_thread():\n            with self._audio_interface() as interface:\n                src = BytesIO(blob)\n                dst = interface.player()\n                amodem.main.send(config=self.modem_config, src=src, dst=dst)\n\n        print_msg('Sending:', repr(blob))\n        blob = zlib.compress(blob.encode('ascii'))\n\n        kbps = self.modem_config.modem_bps / 1e3\n        msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps)\n        WaitingDialog(parent, msg, sender_thread)\n\n    def _recv(self, parent):\n        def receiver_thread():\n            with self._audio_interface() as interface:\n                src = interface.recorder()\n                dst = BytesIO()\n                amodem.main.recv(config=self.modem_config, src=src, dst=dst)\n                return dst.getvalue()\n\n        def on_finished(blob):\n            if blob:\n                blob = zlib.decompress(blob).decode('ascii')\n                print_msg('Received:', repr(blob))\n                parent.setText(blob)\n\n        kbps = self.modem_config.modem_bps / 1e3\n        msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps)\n        WaitingDialog(parent, msg, receiver_thread, on_finished)\n"
  },
  {
    "path": "plugins/cosigner_pool/__init__.py",
    "content": "from electrum.i18n import _\nfullname = _('Cosigner Pool')\ndescription = ' '.join([\n    _(\"This plugin facilitates the use of multi-signatures wallets.\"),\n    _(\"It sends and receives partially signed transactions from/to your cosigner wallet.\"),\n    _(\"Transactions are encrypted and stored on a remote server.\")\n])\n#requires_wallet_type = ['2of2', '2of3']\navailable_for = ['qt']\n"
  },
  {
    "path": "plugins/cosigner_pool/qt.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport time\nfrom xmlrpc.client import ServerProxy\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import QPushButton\n\nfrom electrum import bitcoin, util\nfrom electrum import transaction\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.i18n import _\nfrom electrum.wallet import Multisig_Wallet\nfrom electrum.util import bh2u, bfh\n\nfrom electrum_gui.qt.transaction_dialog import show_transaction\n\nimport sys\nimport traceback\n\n\nPORT = 12344\nHOST = 'cosigner.electrum.org'\nserver = ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True)\n\n\nclass Listener(util.DaemonThread):\n\n    def __init__(self, parent):\n        util.DaemonThread.__init__(self)\n        self.daemon = True\n        self.parent = parent\n        self.received = set()\n        self.keyhashes = []\n\n    def set_keyhashes(self, keyhashes):\n        self.keyhashes = keyhashes\n\n    def clear(self, keyhash):\n        server.delete(keyhash)\n        self.received.remove(keyhash)\n\n    def run(self):\n        while self.running:\n            if not self.keyhashes:\n                time.sleep(2)\n                continue\n            for keyhash in self.keyhashes:\n                if keyhash in self.received:\n                    continue\n                try:\n                    message = server.get(keyhash)\n                except Exception as e:\n                    self.print_error(\"cannot contact cosigner pool\")\n                    time.sleep(30)\n                    continue\n                if message:\n                    self.received.add(keyhash)\n                    self.print_error(\"received message for\", keyhash)\n                    self.parent.obj.cosigner_receive_signal.emit(\n                        keyhash, message)\n            # poll every 30 seconds\n            time.sleep(30)\n\n\nclass QReceiveSignalObject(QObject):\n    cosigner_receive_signal = pyqtSignal(object, object)\n\n\nclass Plugin(BasePlugin):\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        self.listener = None\n        self.obj = QReceiveSignalObject()\n        self.obj.cosigner_receive_signal.connect(self.on_receive)\n        self.keys = []\n        self.cosigner_list = []\n\n    @hook\n    def init_qt(self, gui):\n        for window in gui.windows:\n            self.on_new_window(window)\n\n    @hook\n    def on_new_window(self, window):\n        self.update(window)\n\n    @hook\n    def on_close_window(self, window):\n        self.update(window)\n\n    def is_available(self):\n        return True\n\n    def update(self, window):\n        wallet = window.wallet\n        if type(wallet) != Multisig_Wallet:\n            return\n        if self.listener is None:\n            self.print_error(\"starting listener\")\n            self.listener = Listener(self)\n            self.listener.start()\n        elif self.listener:\n            self.print_error(\"shutting down listener\")\n            self.listener.stop()\n            self.listener = None\n        self.keys = []\n        self.cosigner_list = []\n        for key, keystore in wallet.keystores.items():\n            xpub = keystore.get_master_public_key()\n            K = bitcoin.deserialize_xpub(xpub)[-1]\n            _hash = bh2u(bitcoin.Hash(K))\n            if not keystore.is_watching_only():\n                self.keys.append((key, _hash, window))\n            else:\n                self.cosigner_list.append((window, xpub, K, _hash))\n        if self.listener:\n            self.listener.set_keyhashes([t[1] for t in self.keys])\n\n    @hook\n    def transaction_dialog(self, d):\n        d.cosigner_send_button = b = QPushButton(_(\"Send to cosigner\"))\n        b.clicked.connect(lambda: self.do_send(d.tx))\n        d.buttons.insert(0, b)\n        self.transaction_dialog_update(d)\n\n    @hook\n    def transaction_dialog_update(self, d):\n        if d.tx.is_complete() or d.wallet.can_sign(d.tx):\n            d.cosigner_send_button.hide()\n            return\n        for window, xpub, K, _hash in self.cosigner_list:\n            if window.wallet == d.wallet and self.cosigner_can_sign(d.tx, xpub):\n                d.cosigner_send_button.show()\n                break\n        else:\n            d.cosigner_send_button.hide()\n\n    def cosigner_can_sign(self, tx, cosigner_xpub):\n        from electrum.keystore import is_xpubkey, parse_xpubkey\n        xpub_set = set([])\n        for txin in tx.inputs():\n            for x_pubkey in txin['x_pubkeys']:\n                if is_xpubkey(x_pubkey):\n                    xpub, s = parse_xpubkey(x_pubkey)\n                    xpub_set.add(xpub)\n        return cosigner_xpub in xpub_set\n\n    def do_send(self, tx):\n        for window, xpub, K, _hash in self.cosigner_list:\n            if not self.cosigner_can_sign(tx, xpub):\n                continue\n            message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii')\n            try:\n                server.put(_hash, message)\n            except Exception as e:\n                traceback.print_exc(file=sys.stdout)\n                window.show_message(\"Failed to send transaction to cosigning pool.\")\n                return\n            window.show_message(\"Your transaction was sent to the cosigning pool.\\nOpen your cosigner wallet to retrieve it.\")\n\n    def on_receive(self, keyhash, message):\n        self.print_error(\"signal arrived for\", keyhash)\n        for key, _hash, window in self.keys:\n            if _hash == keyhash:\n                break\n        else:\n            self.print_error(\"keyhash not found\")\n            return\n\n        wallet = window.wallet\n        if wallet.has_password():\n            password = window.password_dialog('An encrypted transaction was retrieved from cosigning pool.\\nPlease enter your password to decrypt it.')\n            if not password:\n                return\n        else:\n            password = None\n            if not window.question(_(\"An encrypted transaction was retrieved from cosigning pool.\\nDo you want to open it now?\")):\n                return\n\n        xprv = wallet.keystore.get_master_private_key(password)\n        if not xprv:\n            return\n        try:\n            k = bh2u(bitcoin.deserialize_xprv(xprv)[-1])\n            EC = bitcoin.EC_KEY(bfh(k))\n            message = bh2u(EC.decrypt_message(message))\n        except Exception as e:\n            traceback.print_exc(file=sys.stdout)\n            window.show_message(str(e))\n            return\n\n        self.listener.clear(keyhash)\n        tx = transaction.Transaction(message)\n        show_transaction(tx, window, prompt_if_unsaved=True)\n"
  },
  {
    "path": "plugins/digitalbitbox/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'Digital Bitbox'\ndescription = _('Provides support for Digital Bitbox hardware wallet')\nregisters_keystore = ('hardware', 'digitalbitbox', _(\"Digital Bitbox wallet\"))\navailable_for = ['qt', 'cmdline']\n"
  },
  {
    "path": "plugins/digitalbitbox/cmdline.py",
    "content": "from electrum.plugins import hook\nfrom .digitalbitbox import DigitalBitboxPlugin\nfrom ..hw_wallet import CmdLineHandler\n\nclass Plugin(DigitalBitboxPlugin):\n    handler = CmdLineHandler()\n    @hook\n    def init_keystore(self, keystore):\n        if not isinstance(keystore, self.keystore_class):\n            return\n        keystore.handler = self.handler\n"
  },
  {
    "path": "plugins/digitalbitbox/digitalbitbox.py",
    "content": "# ----------------------------------------------------------------------------------\n# Electrum plugin for the Digital Bitbox hardware wallet by Shift Devices AG\n# digitalbitbox.com\n#\n\ntry:\n    import electrum\n    from electrum.bitcoin import TYPE_ADDRESS, push_script, var_int, msg_magic, Hash, verify_message, pubkey_from_signature, point_to_ser, public_key_to_p2pkh, EncodeAES, DecodeAES, MyVerifyingKey\n    from electrum.bitcoin import serialize_xpub, deserialize_xpub\n    from electrum.transaction import Transaction\n    from electrum.i18n import _\n    from electrum.keystore import Hardware_KeyStore\n    from ..hw_wallet import HW_PluginBase\n    from electrum.util import print_error, to_string, UserCancelled\n    from electrum.base_wizard import ScriptTypeNotSupported\n\n    import time\n    import hid\n    import json\n    import math\n    import binascii\n    import struct\n    import hashlib\n    import requests\n    import base64\n    import os\n    import sys\n    from ecdsa.ecdsa import generator_secp256k1\n    from ecdsa.util import sigencode_der\n    from ecdsa.curves import SECP256k1\n    DIGIBOX = True\nexcept ImportError as e:\n    DIGIBOX = False\n\n\n\n# ----------------------------------------------------------------------------------\n# USB HID interface\n#\n\ndef to_hexstr(s):\n    return binascii.hexlify(s).decode('ascii')\n\nclass DigitalBitbox_Client():\n\n    def __init__(self, plugin, hidDevice):\n        self.plugin = plugin\n        self.dbb_hid = hidDevice\n        self.opened = True\n        self.password = None\n        self.isInitialized = False\n        self.setupRunning = False\n        self.usbReportSize = 64 # firmware > v2.0.0\n\n\n    def close(self):\n        if self.opened:\n            try:\n                self.dbb_hid.close()\n            except:\n                pass\n        self.opened = False\n\n\n    def timeout(self, cutoff):\n        pass\n\n\n    def label(self):\n        return \" \"\n\n\n    def is_pairable(self):\n        return True\n\n\n    def is_initialized(self):\n        return self.dbb_has_password()\n\n\n    def is_paired(self):\n        return self.password is not None\n\n    def _get_xpub(self, bip32_path):\n        if self.check_device_dialog():\n            return self.hid_send_encrypt(b'{\"xpub\": \"%s\"}' % bip32_path.encode('utf8'))\n\n\n    def get_xpub(self, bip32_path, xtype):\n        assert xtype in ('standard', 'p2wpkh-p2sh')\n        reply = self._get_xpub(bip32_path)\n        if reply:\n            xpub = reply['xpub']\n            # Change type of xpub to the requested type. The firmware\n            # only ever returns the standard type, but it is agnostic\n            # to the type when signing.\n            if xtype != 'standard':\n                _, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)\n                xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)\n            return xpub\n        else:\n            raise BaseException('no reply')\n\n\n    def dbb_has_password(self):\n        reply = self.hid_send_plain(b'{\"ping\":\"\"}')\n        if 'ping' not in reply:\n            raise Exception('Device communication error. Please unplug and replug your Digital Bitbox.')\n        if reply['ping'] == 'password':\n            return True\n        return False\n\n\n    def stretch_key(self, key):\n        import pbkdf2, hmac\n        return binascii.hexlify(pbkdf2.PBKDF2(key, b'Digital Bitbox', iterations = 20480, macmodule = hmac, digestmodule = hashlib.sha512).read(64))\n\n\n    def backup_password_dialog(self):\n        msg = _(\"Enter the password used when the backup was created:\")\n        while True:\n            password = self.handler.get_passphrase(msg, False)\n            if password is None:\n                return None\n            if len(password) < 4:\n                msg = _(\"Password must have at least 4 characters.\\r\\n\\r\\nEnter password:\")\n            elif len(password) > 64:\n                msg = _(\"Password must have less than 64 characters.\\r\\n\\r\\nEnter password:\")\n            else:\n                return password.encode('utf8')\n\n\n    def password_dialog(self, msg):\n        while True:\n            password = self.handler.get_passphrase(msg, False)\n            if password is None:\n                return False\n            if len(password) < 4:\n                msg = _(\"Password must have at least 4 characters.\\r\\n\\r\\nEnter password:\")\n            elif len(password) > 64:\n                msg = _(\"Password must have less than 64 characters.\\r\\n\\r\\nEnter password:\")\n            else:\n                self.password = password.encode('utf8')\n                return True\n\n\n    def check_device_dialog(self):\n        # Set password if fresh device\n        if self.password is None and not self.dbb_has_password():\n            if not self.setupRunning:\n                return False # A fresh device cannot connect to an existing wallet\n            msg = _(\"An uninitialized Digital Bitbox is detected. \" \\\n                    \"Enter a new password below.\\r\\n\\r\\n REMEMBER THE PASSWORD!\\r\\n\\r\\n\" \\\n                    \"You cannot access your coins or a backup without the password.\\r\\n\" \\\n                    \"A backup is saved automatically when generating a new wallet.\")\n            if self.password_dialog(msg):\n                reply = self.hid_send_plain(b'{\"password\":\"' + self.password + b'\"}')\n            else:\n                return False\n\n        # Get password from user if not yet set\n        msg = _(\"Enter your Digital Bitbox password:\")\n        while self.password is None:\n            if not self.password_dialog(msg):\n                return False\n            reply = self.hid_send_encrypt(b'{\"led\":\"blink\"}')\n            if 'error' in reply:\n                self.password = None\n                if reply['error']['code'] == 109:\n                    msg = _(\"Incorrect password entered.\\r\\n\\r\\n\"  \\\n                            + reply['error']['message'] + \"\\r\\n\\r\\n\" \\\n                            \"Enter your Digital Bitbox password:\")\n                else:\n                    # Should never occur\n                    msg = _(\"Unexpected error occurred.\\r\\n\\r\\n\"  \\\n                            + reply['error']['message'] + \"\\r\\n\\r\\n\" \\\n                            \"Enter your Digital Bitbox password:\")\n\n        # Initialize device if not yet initialized\n        if not self.setupRunning:\n            self.isInitialized = True # Wallet exists. Electrum code later checks if the device matches the wallet\n        elif not self.isInitialized:\n            reply = self.hid_send_encrypt(b'{\"device\":\"info\"}')\n            if reply['device']['id'] != \"\":\n                self.recover_or_erase_dialog() # Already seeded\n            else:\n                self.seed_device_dialog() # Seed if not initialized\n            self.mobile_pairing_dialog()\n        return self.isInitialized\n\n\n    def recover_or_erase_dialog(self):\n        msg = _(\"The Digital Bitbox is already seeded. Choose an option:\\n\")\n        choices = [\n            (_(\"Create a wallet using the current seed\")),\n            (_(\"Load a wallet from the micro SD card (the current seed is overwritten)\")),\n            (_(\"Erase the Digital Bitbox\"))\n        ]\n        try:\n            reply = self.handler.win.query_choice(msg, choices)\n        except Exception:\n            return # Back button pushed\n        if reply == 2:\n            self.dbb_erase()\n        elif reply == 1:\n            if not self.dbb_load_backup():\n                return\n        else:\n            if self.hid_send_encrypt(b'{\"device\":\"info\"}')['device']['lock']:\n                raise Exception(\"Full 2FA enabled. This is not supported yet.\")\n            # Use existing seed\n        self.isInitialized = True\n\n\n    def seed_device_dialog(self):\n        msg = _(\"Choose how to initialize your Digital Bitbox:\\n\")\n        choices = [\n            (_(\"Generate a new random wallet\")),\n            (_(\"Load a wallet from the micro SD card\"))\n        ]\n        try:\n            reply = self.handler.win.query_choice(msg, choices)\n        except Exception:\n            return # Back button pushed\n        if reply == 0:\n            self.dbb_generate_wallet()\n        else:\n            if not self.dbb_load_backup(show_msg=False):\n                return\n        self.isInitialized = True\n\n    def mobile_pairing_dialog(self):\n        dbb_user_dir = None\n        if sys.platform == 'darwin':\n            dbb_user_dir = os.path.join(os.environ.get(\"HOME\", \"\"), \"Library\", \"Application Support\", \"DBB\")\n        elif sys.platform == 'win32':\n            dbb_user_dir = os.path.join(os.environ[\"APPDATA\"], \"DBB\")\n        else:\n            dbb_user_dir = os.path.join(os.environ[\"HOME\"], \".dbb\")\n\n        if not dbb_user_dir:\n            return\n\n        try:\n            with open(os.path.join(dbb_user_dir, \"config.dat\")) as f:\n                dbb_config = json.load(f)\n        except (FileNotFoundError, json.JSONDecodeError):\n            return\n\n        if 'encryptionprivkey' not in dbb_config or 'comserverchannelid' not in dbb_config:\n            return\n\n        choices = [\n            _('Do not pair'),\n            _('Import pairing from the digital bitbox desktop app'),\n        ]\n        try:\n            reply = self.handler.win.query_choice(_('Mobile pairing options'), choices)\n        except Exception:\n            return # Back button pushed\n\n        if reply == 0:\n            if self.plugin.is_mobile_paired():\n                del self.plugin.digitalbitbox_config['encryptionprivkey']\n                del self.plugin.digitalbitbox_config['comserverchannelid']\n        elif reply == 1:\n            # import pairing from dbb app\n            self.plugin.digitalbitbox_config['encryptionprivkey'] = dbb_config['encryptionprivkey']\n            self.plugin.digitalbitbox_config['comserverchannelid'] = dbb_config['comserverchannelid']\n        self.plugin.config.set_key('digitalbitbox', self.plugin.digitalbitbox_config)\n\n    def dbb_generate_wallet(self):\n        key = self.stretch_key(self.password)\n        filename = (\"Electrum-\" + time.strftime(\"%Y-%m-%d-%H-%M-%S\") + \".pdf\").encode('utf8')\n        msg = b'{\"seed\":{\"source\": \"create\", \"key\": \"%s\", \"filename\": \"%s\", \"entropy\": \"%s\"}}' % (key, filename, b'Digital Bitbox Electrum Plugin')\n        reply = self.hid_send_encrypt(msg)\n        if 'error' in reply:\n            raise Exception(reply['error']['message'])\n\n\n    def dbb_erase(self):\n        self.handler.show_message(_(\"Are you sure you want to erase the Digital Bitbox?\\r\\n\\r\\n\" \\\n                                    \"To continue, touch the Digital Bitbox's light for 3 seconds.\\r\\n\\r\\n\" \\\n                                    \"To cancel, briefly touch the light or wait for the timeout.\"))\n        hid_reply = self.hid_send_encrypt(b'{\"reset\":\"__ERASE__\"}')\n        self.handler.finished()\n        if 'error' in hid_reply:\n            raise Exception(hid_reply['error']['message'])\n        else:\n            self.password = None\n            raise Exception('Device erased')\n\n\n    def dbb_load_backup(self, show_msg=True):\n        backups = self.hid_send_encrypt(b'{\"backup\":\"list\"}')\n        if 'error' in backups:\n            raise Exception(backups['error']['message'])\n        try:\n            f = self.handler.win.query_choice(_(\"Choose a backup file:\"), backups['backup'])\n        except Exception:\n            return False # Back button pushed\n        key = self.backup_password_dialog()\n        if key is None:\n            raise Exception('Canceled by user')\n        key = self.stretch_key(key)\n        if show_msg:\n            self.handler.show_message(_(\"Loading backup...\\r\\n\\r\\n\" \\\n                                        \"To continue, touch the Digital Bitbox's light for 3 seconds.\\r\\n\\r\\n\" \\\n                                        \"To cancel, briefly touch the light or wait for the timeout.\"))\n        msg = b'{\"seed\":{\"source\": \"backup\", \"key\": \"%s\", \"filename\": \"%s\"}}' % (key, backups['backup'][f].encode('utf8'))\n        hid_reply = self.hid_send_encrypt(msg)\n        self.handler.finished()\n        if 'error' in hid_reply:\n            raise Exception(hid_reply['error']['message'])\n        return True\n\n\n    def hid_send_frame(self, data):\n        HWW_CID = 0xFF000000\n        HWW_CMD = 0x80 + 0x40 + 0x01\n        data_len = len(data)\n        seq = 0;\n        idx = 0;\n        write = []\n        while idx < data_len:\n            if idx == 0:\n                # INIT frame\n                write = data[idx : idx + min(data_len, self.usbReportSize - 7)]\n                self.dbb_hid.write(b'\\0' + struct.pack(\">IBH\", HWW_CID, HWW_CMD, data_len & 0xFFFF) + write + b'\\xEE' * (self.usbReportSize - 7 - len(write)))\n            else:\n                # CONT frame\n                write = data[idx : idx + min(data_len, self.usbReportSize - 5)]\n                self.dbb_hid.write(b'\\0' + struct.pack(\">IB\", HWW_CID, seq) + write + b'\\xEE' * (self.usbReportSize - 5 - len(write)))\n                seq += 1\n            idx += len(write)\n\n\n    def hid_read_frame(self):\n        # INIT response\n        read = bytearray(self.dbb_hid.read(self.usbReportSize))\n        cid = ((read[0] * 256 + read[1]) * 256 + read[2]) * 256 + read[3]\n        cmd = read[4]\n        data_len = read[5] * 256 + read[6]\n        data = read[7:]\n        idx = len(read) - 7;\n        while idx < data_len:\n            # CONT response\n            read = bytearray(self.dbb_hid.read(self.usbReportSize))\n            data += read[5:]\n            idx += len(read) - 5\n        return data\n\n\n    def hid_send_plain(self, msg):\n        reply = \"\"\n        try:\n            serial_number = self.dbb_hid.get_serial_number_string()\n            if \"v2.0.\" in serial_number or \"v1.\" in serial_number:\n                hidBufSize = 4096\n                self.dbb_hid.write('\\0' + msg + '\\0' * (hidBufSize - len(msg)))\n                r = bytearray()\n                while len(r) < hidBufSize:\n                    r += bytearray(self.dbb_hid.read(hidBufSize))\n            else:\n                self.hid_send_frame(msg)\n                r = self.hid_read_frame()\n            r = r.rstrip(b' \\t\\r\\n\\0')\n            r = r.replace(b\"\\0\", b'')\n            r = to_string(r, 'utf8')\n            reply = json.loads(r)\n        except Exception as e:\n            print_error('Exception caught ' + str(e))\n        return reply\n\n\n    def hid_send_encrypt(self, msg):\n        reply = \"\"\n        try:\n            secret = Hash(self.password)\n            msg = EncodeAES(secret, msg)\n            reply = self.hid_send_plain(msg)\n            if 'ciphertext' in reply:\n                reply = DecodeAES(secret, ''.join(reply[\"ciphertext\"]))\n                reply = to_string(reply, 'utf8')\n                reply = json.loads(reply)\n            if 'error' in reply:\n                self.password = None\n        except Exception as e:\n            print_error('Exception caught ' + str(e))\n        return reply\n\n\n\n# ----------------------------------------------------------------------------------\n#\n#\n\nclass DigitalBitbox_KeyStore(Hardware_KeyStore):\n    hw_type = 'digitalbitbox'\n    device = 'DigitalBitbox'\n\n\n    def __init__(self, d):\n        Hardware_KeyStore.__init__(self, d)\n        self.force_watching_only = False\n        self.maxInputs = 14 # maximum inputs per single sign command\n\n\n    def get_derivation(self):\n        return str(self.derivation)\n\n\n    def is_p2pkh(self):\n        return self.derivation.startswith(\"m/44'/\")\n\n\n    def give_error(self, message, clear_client = False):\n        if clear_client:\n            self.client = None\n        raise Exception(message)\n\n\n    def decrypt_message(self, pubkey, message, password):\n        raise RuntimeError(_('Encryption and decryption are currently not supported for %s') % self.device)\n\n\n    def sign_message(self, sequence, message, password):\n        sig = None\n        try:\n            message = message.encode('utf8')\n            inputPath = self.get_derivation() + \"/%d/%d\" % sequence\n            msg_hash = Hash(msg_magic(message))\n            inputHash = to_hexstr(msg_hash)\n            hasharray = []\n            hasharray.append({'hash': inputHash, 'keypath': inputPath})\n            hasharray = json.dumps(hasharray)\n\n            msg = b'{\"sign\":{\"meta\":\"sign message\", \"data\":%s}}' % hasharray.encode('utf8')\n\n            dbb_client = self.plugin.get_client(self)\n\n            if not dbb_client.is_paired():\n                raise Exception(\"Could not sign message.\")\n\n            reply = dbb_client.hid_send_encrypt(msg)\n            self.handler.show_message(_(\"Signing message ...\\r\\n\\r\\n\" \\\n                                        \"To continue, touch the Digital Bitbox's blinking light for 3 seconds.\\r\\n\\r\\n\" \\\n                                        \"To cancel, briefly touch the blinking light or wait for the timeout.\"))\n            reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)\n            self.handler.finished()\n\n            if 'error' in reply:\n                raise Exception(reply['error']['message'])\n\n            if 'sign' not in reply:\n                raise Exception(\"Could not sign message.\")\n\n            if 'recid' in reply['sign'][0]:\n                # firmware > v2.1.1\n                sig = bytes([27 + int(reply['sign'][0]['recid'], 16) + 4]) + binascii.unhexlify(reply['sign'][0]['sig'])\n                pk, compressed = pubkey_from_signature(sig, msg_hash)\n                pk = point_to_ser(pk.pubkey.point, compressed)\n                addr = public_key_to_p2pkh(pk)\n                if verify_message(addr, sig, message) is False:\n                    raise Exception(\"Could not sign message\")\n            elif 'pubkey' in reply['sign'][0]:\n                # firmware <= v2.1.1\n                for i in range(4):\n                    sig = bytes([27 + i + 4]) + binascii.unhexlify(reply['sign'][0]['sig'])\n                    try:\n                        addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey']))\n                        if verify_message(addr, sig, message):\n                            break\n                    except Exception:\n                        continue\n                else:\n                    raise Exception(\"Could not sign message\")\n\n\n        except BaseException as e:\n            self.give_error(e)\n        return sig\n\n\n    def sign_transaction(self, tx, password):\n        if tx.is_complete():\n            return\n\n        try:\n            p2pkhTransaction = True\n            derivations = self.get_tx_derivations(tx)\n            inputhasharray = []\n            hasharray = []\n            pubkeyarray = []\n\n            # Build hasharray from inputs\n            for i, txin in enumerate(tx.inputs()):\n                if txin['type'] == 'coinbase':\n                    self.give_error(\"Coinbase not supported\") # should never happen\n\n                if txin['type'] != 'p2pkh':\n                    p2pkhTransaction = False\n\n                for x_pubkey in txin['x_pubkeys']:\n                    if x_pubkey in derivations:\n                        index = derivations.get(x_pubkey)\n                        inputPath = \"%s/%d/%d\" % (self.get_derivation(), index[0], index[1])\n                        inputHash = Hash(binascii.unhexlify(tx.serialize_preimage(i)))\n                        hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}\n                        hasharray.append(hasharray_i)\n                        inputhasharray.append(inputHash)\n                        break\n                else:\n                    self.give_error(\"No matching x_key for sign_transaction\") # should never happen\n\n            # Build pubkeyarray from outputs\n            for _type, address, amount in tx.outputs():\n                assert _type == TYPE_ADDRESS\n                info = tx.output_info.get(address)\n                if info is not None:\n                    index, xpubs, m = info\n                    changePath = self.get_derivation() + \"/%d/%d\" % index\n                    changePubkey = self.derive_pubkey(index[0], index[1])\n                    pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath}\n                    pubkeyarray.append(pubkeyarray_i)\n\n            # Special serialization of the unsigned transaction for\n            # the mobile verification app.\n            # At the moment, verification only works for p2pkh transactions.\n            if p2pkhTransaction:\n                class CustomTXSerialization(Transaction):\n                    @classmethod\n                    def input_script(self, txin, estimate_size=False):\n                        if txin['type'] == 'p2pkh':\n                            return Transaction.get_preimage_script(txin)\n                        if txin['type'] == 'p2sh':\n                            # Multisig verification has partial support, but is disabled. This is the\n                            # expected serialization though, so we leave it here until we activate it.\n                            return '00' + push_script(Transaction.get_preimage_script(txin))\n                        raise Exception(\"unsupported type %s\" % txin['type'])\n                tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize()\n            else:\n                # We only need this for the signing echo / verification.\n                tx_dbb_serialized = None\n\n            # Build sign command\n            dbb_signatures = []\n            steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)\n            for step in range(int(steps)):\n                hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs]\n\n                msg = {\n                    \"sign\": {\n                        \"data\": hashes,\n                        \"checkpub\": pubkeyarray,\n                    },\n                }\n                if tx_dbb_serialized is not None:\n                    msg[\"sign\"][\"meta\"] = to_hexstr(Hash(tx_dbb_serialized))\n                msg = json.dumps(msg).encode('ascii')\n                dbb_client = self.plugin.get_client(self)\n\n                if not dbb_client.is_paired():\n                    raise Exception(\"Could not sign transaction.\")\n\n                reply = dbb_client.hid_send_encrypt(msg)\n                if 'error' in reply:\n                    raise Exception(reply['error']['message'])\n\n                if 'echo' not in reply:\n                    raise Exception(\"Could not sign transaction.\")\n\n                if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None:\n                    reply['tx'] = tx_dbb_serialized\n                    self.plugin.comserver_post_notification(reply)\n\n                if steps > 1:\n                    self.handler.show_message(_(\"Signing large transaction. Please be patient ...\\r\\n\\r\\n\" \\\n                                                \"To continue, touch the Digital Bitbox's blinking light for 3 seconds. \" \\\n                                                \"(Touch \" + str(step + 1) + \" of \" + str(int(steps)) + \")\\r\\n\\r\\n\" \\\n                                                \"To cancel, briefly touch the blinking light or wait for the timeout.\\r\\n\\r\\n\"))\n                else:\n                    self.handler.show_message(_(\"Signing transaction ...\\r\\n\\r\\n\" \\\n                                                \"To continue, touch the Digital Bitbox's blinking light for 3 seconds.\\r\\n\\r\\n\" \\\n                                                \"To cancel, briefly touch the blinking light or wait for the timeout.\"))\n\n                # Send twice, first returns an echo for smart verification\n                reply = dbb_client.hid_send_encrypt(msg)\n                self.handler.finished()\n\n                if 'error' in reply:\n                    if reply[\"error\"].get('code') in (600, 601):\n                        # aborted via LED short touch or timeout\n                        raise UserCancelled()\n                    raise Exception(reply['error']['message'])\n\n                if 'sign' not in reply:\n                    raise Exception(\"Could not sign transaction.\")\n\n                dbb_signatures.extend(reply['sign'])\n\n            # Fill signatures\n            if len(dbb_signatures) != len(tx.inputs()):\n                raise Exception(\"Incorrect number of transactions signed.\") # Should never occur\n            for i, txin in enumerate(tx.inputs()):\n                num = txin['num_sig']\n                for pubkey in txin['pubkeys']:\n                    signatures = list(filter(None, txin['signatures']))\n                    if len(signatures) == num:\n                        break # txin is complete\n                    ii = txin['pubkeys'].index(pubkey)\n                    signed = dbb_signatures[i]\n                    if 'recid' in signed:\n                        # firmware > v2.1.1\n                        recid = int(signed['recid'], 16)\n                        s = binascii.unhexlify(signed['sig'])\n                        h = inputhasharray[i]\n                        pk = MyVerifyingKey.from_signature(s, recid, h, curve = SECP256k1)\n                        pk = to_hexstr(point_to_ser(pk.pubkey.point, True))\n                    elif 'pubkey' in signed:\n                        # firmware <= v2.1.1\n                        pk = signed['pubkey']\n                    if pk != pubkey:\n                        continue\n                    sig_r = int(signed['sig'][:64], 16)\n                    sig_s = int(signed['sig'][64:], 16)\n                    sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order())\n                    txin['signatures'][ii] = to_hexstr(sig) + '01'\n                    tx._inputs[i] = txin\n        except UserCancelled:\n            raise\n        except BaseException as e:\n            self.give_error(e, True)\n        else:\n            print_error(\"Transaction is_complete\", tx.is_complete())\n            tx.raw = tx.serialize()\n\n\nclass DigitalBitboxPlugin(HW_PluginBase):\n\n    libraries_available = DIGIBOX\n    keystore_class = DigitalBitbox_KeyStore\n    client = None\n    DEVICE_IDS = [\n                   (0x03eb, 0x2402) # Digital Bitbox\n                 ]\n\n    def __init__(self, parent, config, name):\n        HW_PluginBase.__init__(self, parent, config, name)\n        if self.libraries_available:\n            self.device_manager().register_devices(self.DEVICE_IDS)\n\n        self.digitalbitbox_config = self.config.get('digitalbitbox', {})\n\n\n    def get_dbb_device(self, device):\n        dev = hid.device()\n        dev.open_path(device.path)\n        return dev\n\n\n    def create_client(self, device, handler):\n        if device.interface_number == 0 or device.usage_page == 0xffff:\n            self.handler = handler\n            client = self.get_dbb_device(device)\n            if client is not None:\n                client = DigitalBitbox_Client(self, client)\n            return client\n        else:\n            return None\n\n\n    def setup_device(self, device_info, wizard):\n        devmgr = self.device_manager()\n        device_id = device_info.device.id_\n        client = devmgr.client_by_id(device_id)\n        client.handler = self.create_handler(wizard)\n        client.setupRunning = True\n        client.get_xpub(\"m/44'/0'\", 'standard')\n\n\n    def is_mobile_paired(self):\n        return 'encryptionprivkey' in self.digitalbitbox_config\n\n\n    def comserver_post_notification(self, payload):\n        assert self.is_mobile_paired(), \"unexpected mobile pairing error\"\n        url = 'https://digitalbitbox.com/smartverification/index.php'\n        key_s = base64.b64decode(self.digitalbitbox_config['encryptionprivkey'])\n        args = 'c=data&s=0&dt=0&uuid=%s&pl=%s' % (\n            self.digitalbitbox_config['comserverchannelid'],\n            EncodeAES(key_s, json.dumps(payload).encode('ascii')).decode('ascii'),\n        )\n        try:\n            requests.post(url, args)\n        except Exception as e:\n            self.handler.show_error(str(e))\n\n\n    def get_xpub(self, device_id, derivation, xtype, wizard):\n        if xtype not in ('standard', 'p2wpkh-p2sh'):\n            raise ScriptTypeNotSupported(_('This type of script is not supported with the Digital Bitbox.'))\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n        client.handler = self.create_handler(wizard)\n        client.check_device_dialog()\n        xpub = client.get_xpub(derivation, xtype)\n        return xpub\n\n\n    def get_client(self, keystore, force_pair=True):\n        devmgr = self.device_manager()\n        handler = keystore.handler\n        with devmgr.hid_lock:\n            client = devmgr.client_for_keystore(self, handler, keystore, force_pair)\n        if client is not None:\n            client.check_device_dialog()\n        return client\n"
  },
  {
    "path": "plugins/digitalbitbox/qt.py",
    "content": "from ..hw_wallet.qt import QtHandlerBase, QtPluginBase\nfrom .digitalbitbox import DigitalBitboxPlugin\n\nfrom electrum.i18n import _\nfrom electrum.plugins import hook\nfrom electrum.wallet import Standard_Wallet\n\n\nclass Plugin(DigitalBitboxPlugin, QtPluginBase):\n    icon_unpaired = \":icons/digitalbitbox_unpaired.png\"\n    icon_paired = \":icons/digitalbitbox.png\"\n\n    def create_handler(self, window):\n        return DigitalBitbox_Handler(window)\n\n    @hook\n    def receive_menu(self, menu, addrs, wallet):\n        if type(wallet) is not Standard_Wallet:\n            return\n\n        keystore = wallet.get_keystore()\n        if type(keystore) is not self.keystore_class:\n            return\n\n        if not self.is_mobile_paired():\n            return\n\n        if not keystore.is_p2pkh():\n            return\n\n        if len(addrs) == 1:\n            def show_address():\n                change, index = wallet.get_address_index(addrs[0])\n                keypath = '%s/%d/%d' % (keystore.derivation, change, index)\n                xpub = self.get_client(keystore)._get_xpub(keypath)\n                verify_request_payload = {\n                    \"type\": 'p2pkh',\n                    \"echo\": xpub['echo'],\n                    }\n                self.comserver_post_notification(verify_request_payload)\n\n            menu.addAction(_(\"Show on %s\") % self.device, show_address)\n\n\nclass DigitalBitbox_Handler(QtHandlerBase):\n\n    def __init__(self, win):\n        super(DigitalBitbox_Handler, self).__init__(win, 'Digital Bitbox')\n"
  },
  {
    "path": "plugins/email_requests/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = _('Email')\ndescription = _(\"Send and receive payment request with an email account\")\navailable_for = ['qt']\n"
  },
  {
    "path": "plugins/email_requests/qt.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - Lightweight Bitcoin Client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport time\nimport threading\nimport base64\nfrom functools import partial\n\nimport smtplib\nimport imaplib\nimport email\nfrom email.mime.multipart import MIMEMultipart\nfrom email.mime.base import MIMEBase\nfrom email.encoders import encode_base64\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nimport PyQt5.QtGui as QtGui\nfrom PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit)\n\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.paymentrequest import PaymentRequest\nfrom electrum.i18n import _\nfrom electrum_gui.qt.util import EnterButton, Buttons, CloseButton\nfrom electrum_gui.qt.util import OkButton, WindowModalDialog\n\n\nclass Processor(threading.Thread):\n    polling_interval = 5*60\n\n    def __init__(self, imap_server, username, password, callback):\n        threading.Thread.__init__(self)\n        self.daemon = True\n        self.username = username\n        self.password = password\n        self.imap_server = imap_server\n        self.on_receive = callback\n\n    def poll(self):\n        try:\n            self.M.select()\n        except:\n            return\n        typ, data = self.M.search(None, 'ALL')\n        for num in data[0].split():\n            typ, msg_data = self.M.fetch(num, '(RFC822)')\n            msg = email.message_from_string(msg_data[0][1])\n            p = msg.get_payload()\n            if not msg.is_multipart():\n                p = [p]\n                continue\n            for item in p:\n                if item.get_content_type() == \"application/bitcoin-paymentrequest\":\n                    pr_str = item.get_payload()\n                    pr_str = base64.b64decode(pr_str)\n                    self.on_receive(pr_str)\n\n    def run(self):\n        self.M = imaplib.IMAP4_SSL(self.imap_server)\n        self.M.login(self.username, self.password)\n        while True:\n            self.poll()\n            time.sleep(self.polling_interval)\n        self.M.close()\n        self.M.logout()\n\n    def send(self, recipient, message, payment_request):\n        msg = MIMEMultipart()\n        msg['Subject'] = message\n        msg['To'] = recipient\n        msg['From'] = self.username\n        part = MIMEBase('application', \"bitcoin-paymentrequest\")\n        part.set_payload(payment_request)\n        encode_base64(part)\n        part.add_header('Content-Disposition', 'attachment; filename=\"payreq.btc\"')\n        msg.attach(part)\n        s = smtplib.SMTP_SSL(self.imap_server, timeout=2)\n        s.login(self.username, self.password)\n        s.sendmail(self.username, [recipient], msg.as_string())\n        s.quit()\n\n\nclass QEmailSignalObject(QObject):\n    email_new_invoice_signal = pyqtSignal()\n\n\nclass Plugin(BasePlugin):\n\n    def fullname(self):\n        return 'Email'\n\n    def description(self):\n        return _(\"Send and receive payment requests via email\")\n\n    def is_available(self):\n        return True\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        self.imap_server = self.config.get('email_server', '')\n        self.username = self.config.get('email_username', '')\n        self.password = self.config.get('email_password', '')\n        if self.imap_server and self.username and self.password:\n            self.processor = Processor(self.imap_server, self.username, self.password, self.on_receive)\n            self.processor.start()\n        self.obj = QEmailSignalObject()\n        self.obj.email_new_invoice_signal.connect(self.new_invoice)\n\n    def on_receive(self, pr_str):\n        self.print_error('received payment request')\n        self.pr = PaymentRequest(pr_str)\n        self.obj.email_new_invoice_signal.emit()\n\n    def new_invoice(self):\n        self.parent.invoices.add(self.pr)\n        #window.update_invoices_list()\n\n    @hook\n    def receive_list_menu(self, menu, addr):\n        window = menu.parentWidget()\n        menu.addAction(_(\"Send via e-mail\"), lambda: self.send(window, addr))\n\n    def send(self, window, addr):\n        from electrum import paymentrequest\n        r = window.wallet.receive_requests.get(addr)\n        message = r.get('memo', '')\n        if r.get('signature'):\n            pr = paymentrequest.serialize_request(r)\n        else:\n            pr = paymentrequest.make_request(self.config, r)\n        if not pr:\n            return\n        recipient, ok = QtGui.QInputDialog.getText(window, 'Send request', 'Email invoice to:')\n        if not ok:\n            return\n        recipient = str(recipient)\n        payload = pr.SerializeToString()\n        self.print_error('sending mail to', recipient)\n        try:\n            self.processor.send(recipient, message, payload)\n        except BaseException as e:\n            window.show_message(str(e))\n            return\n\n        window.show_message(_('Request sent.'))\n\n\n    def requires_settings(self):\n        return True\n\n    def settings_widget(self, window):\n        return EnterButton(_('Settings'), partial(self.settings_dialog, window))\n\n    def settings_dialog(self, window):\n        d = WindowModalDialog(window, _(\"Email settings\"))\n        d.setMinimumSize(500, 200)\n\n        vbox = QVBoxLayout(d)\n        vbox.addWidget(QLabel(_('Server hosting your email acount')))\n        grid = QGridLayout()\n        vbox.addLayout(grid)\n        grid.addWidget(QLabel('Server (IMAP)'), 0, 0)\n        server_e = QLineEdit()\n        server_e.setText(self.imap_server)\n        grid.addWidget(server_e, 0, 1)\n\n        grid.addWidget(QLabel('Username'), 1, 0)\n        username_e = QLineEdit()\n        username_e.setText(self.username)\n        grid.addWidget(username_e, 1, 1)\n\n        grid.addWidget(QLabel('Password'), 2, 0)\n        password_e = QLineEdit()\n        password_e.setText(self.password)\n        grid.addWidget(password_e, 2, 1)\n\n        vbox.addStretch()\n        vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))\n\n        if not d.exec_():\n            return\n\n        server = str(server_e.text())\n        self.config.set_key('email_server', server)\n\n        username = str(username_e.text())\n        self.config.set_key('email_username', username)\n\n        password = str(password_e.text())\n        self.config.set_key('email_password', password)\n"
  },
  {
    "path": "plugins/greenaddress_instant/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'GreenAddress instant'\ndescription = _(\"Allows validating if your transactions have instant confirmations by GreenAddress\")\navailable_for = ['qt']\n"
  },
  {
    "path": "plugins/greenaddress_instant/qt.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2014 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport base64\nimport urllib.parse\nimport sys\nimport requests\n\nfrom PyQt5.QtWidgets import QApplication, QPushButton\n\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.i18n import _\n\n\n\nclass Plugin(BasePlugin):\n\n    button_label = _(\"Verify GA instant\")\n\n    @hook\n    def transaction_dialog(self, d):\n        d.verify_button = QPushButton(self.button_label)\n        d.verify_button.clicked.connect(lambda: self.do_verify(d))\n        d.buttons.insert(0, d.verify_button)\n        self.transaction_dialog_update(d)\n\n    def get_my_addr(self, d):\n        \"\"\"Returns the address for given tx which can be used to request\n        instant confirmation verification from GreenAddress\"\"\"\n        for addr, _ in d.tx.get_outputs():\n            if d.wallet.is_mine(addr):\n                return addr\n        return None\n\n    @hook\n    def transaction_dialog_update(self, d):\n        if d.tx.is_complete() and self.get_my_addr(d):\n            d.verify_button.show()\n        else:\n            d.verify_button.hide()\n\n    def do_verify(self, d):\n        tx = d.tx\n        wallet = d.wallet\n        window = d.main_window\n        # 1. get the password and sign the verification request\n        password = None\n        if wallet.has_password():\n            msg = _('GreenAddress requires your signature \\n'\n                    'to verify that transaction is instant.\\n'\n                    'Please enter your password to sign a\\n'\n                    'verification request.')\n            password = window.password_dialog(msg, parent=d)\n            if not password:\n                return\n        try:\n            d.verify_button.setText(_('Verifying...'))\n            QApplication.processEvents()  # update the button label\n\n            addr = self.get_my_addr(d)\n            message = \"Please verify if %s is GreenAddress instant confirmed\" % tx.txid()\n            sig = wallet.sign_message(addr, message, password)\n            sig = base64.b64encode(sig).decode('ascii')\n\n            # 2. send the request\n            response = requests.request(\"GET\", (\"https://greenaddress.it/verify/?signature=%s&txhash=%s\" % (urllib.parse.quote(sig), tx.txid())),\n                                        headers = {'User-Agent': 'Electrum'})\n            response = response.json()\n\n            # 3. display the result\n            if response.get('verified'):\n                d.show_message(_('%s is covered by GreenAddress instant confirmation') % (tx.txid()), title=_('Verification successful!'))\n            else:\n                d.show_critical(_('%s is not covered by GreenAddress instant confirmation') % (tx.txid()), title=_('Verification failed!'))\n        except BaseException as e:\n            import traceback\n            traceback.print_exc(file=sys.stdout)\n            d.show_error(str(e))\n        finally:\n            d.verify_button.setText(self.button_label)\n"
  },
  {
    "path": "plugins/hw_wallet/__init__.py",
    "content": "from .plugin import HW_PluginBase\nfrom .cmdline import CmdLineHandler\n"
  },
  {
    "path": "plugins/hw_wallet/cmdline.py",
    "content": "from electrum.util import print_msg, print_error, raw_input\n\nclass CmdLineHandler:\n\n    def get_passphrase(self, msg, confirm):\n        import getpass\n        print_msg(msg)\n        return getpass.getpass('')\n\n    def get_pin(self, msg):\n        t = { 'a':'7', 'b':'8', 'c':'9', 'd':'4', 'e':'5', 'f':'6', 'g':'1', 'h':'2', 'i':'3'}\n        print_msg(msg)\n        print_msg(\"a b c\\nd e f\\ng h i\\n-----\")\n        o = raw_input()\n        return ''.join(map(lambda x: t[x], o))\n\n    def prompt_auth(self, msg):\n        import getpass\n        print_msg(msg)\n        response = getpass.getpass('')\n        if len(response) == 0:\n            return None\n        return response\n\n    def yes_no_question(self, msg):\n        print_msg(msg)\n        return raw_input() in 'yY'\n\n    def stop(self):\n        pass\n\n    def show_message(self, msg, on_cancel=None):\n        print_msg(msg)\n\n    def update_status(self, b):\n        print_error('trezor status', b)\n\n    def finished(self):\n        pass\n"
  },
  {
    "path": "plugins/hw_wallet/plugin.py",
    "content": "#!/usr/bin/env python2\n# -*- mode: python -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2016  The Electrum developers\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.i18n import _\n\n\nclass HW_PluginBase(BasePlugin):\n    # Derived classes provide:\n    #\n    #  class-static variables: client_class, firmware_URL, handler_class,\n    #     libraries_available, libraries_URL, minimum_firmware,\n    #     wallet_class, ckd_public, types, HidTransport\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        self.device = self.keystore_class.device\n        self.keystore_class.plugin = self\n\n    def is_enabled(self):\n        return True\n\n    def device_manager(self):\n        return self.parent.device_manager\n\n    @hook\n    def close_wallet(self, wallet):\n        for keystore in wallet.get_keystores():\n            if isinstance(keystore, self.keystore_class):\n                self.device_manager().unpair_xpub(keystore.xpub)\n"
  },
  {
    "path": "plugins/hw_wallet/qt.py",
    "content": "#!/usr/bin/env python2\n# -*- mode: python -*-\n#\n# Electrum - lightweight Bitcoin client\n# Copyright (C) 2016  The Electrum developers\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport threading\n\nfrom PyQt5.Qt import QVBoxLayout, QLabel\nfrom electrum_gui.qt.password_dialog import PasswordDialog, PW_PASSPHRASE\nfrom electrum_gui.qt.util import *\n\nfrom electrum.i18n import _\nfrom electrum.util import PrintError\n\n# The trickiest thing about this handler was getting windows properly\n# parented on MacOSX.\nclass QtHandlerBase(QObject, PrintError):\n    '''An interface between the GUI (here, QT) and the device handling\n    logic for handling I/O.'''\n\n    passphrase_signal = pyqtSignal(object, object)\n    message_signal = pyqtSignal(object, object)\n    error_signal = pyqtSignal(object)\n    word_signal = pyqtSignal(object)\n    clear_signal = pyqtSignal()\n    query_signal = pyqtSignal(object, object)\n    yes_no_signal = pyqtSignal(object)\n    status_signal = pyqtSignal(object)\n\n    def __init__(self, win, device):\n        super(QtHandlerBase, self).__init__()\n        self.clear_signal.connect(self.clear_dialog)\n        self.error_signal.connect(self.error_dialog)\n        self.message_signal.connect(self.message_dialog)\n        self.passphrase_signal.connect(self.passphrase_dialog)\n        self.word_signal.connect(self.word_dialog)\n        self.query_signal.connect(self.win_query_choice)\n        self.yes_no_signal.connect(self.win_yes_no_question)\n        self.status_signal.connect(self._update_status)\n        self.win = win\n        self.device = device\n        self.dialog = None\n        self.done = threading.Event()\n\n    def top_level_window(self):\n        return self.win.top_level_window()\n\n    def update_status(self, paired):\n        self.status_signal.emit(paired)\n\n    def _update_status(self, paired):\n        button = self.button\n        icon = button.icon_paired if paired else button.icon_unpaired\n        button.setIcon(QIcon(icon))\n\n    def query_choice(self, msg, labels):\n        self.done.clear()\n        self.query_signal.emit(msg, labels)\n        self.done.wait()\n        return self.choice\n\n    def yes_no_question(self, msg):\n        self.done.clear()\n        self.yes_no_signal.emit(msg)\n        self.done.wait()\n        return self.ok\n\n    def show_message(self, msg, on_cancel=None):\n        self.message_signal.emit(msg, on_cancel)\n\n    def show_error(self, msg):\n        self.error_signal.emit(msg)\n\n    def finished(self):\n        self.clear_signal.emit()\n\n    def get_word(self, msg):\n        self.done.clear()\n        self.word_signal.emit(msg)\n        self.done.wait()\n        return self.word\n\n    def get_passphrase(self, msg, confirm):\n        self.done.clear()\n        self.passphrase_signal.emit(msg, confirm)\n        self.done.wait()\n        return self.passphrase\n\n    def passphrase_dialog(self, msg, confirm):\n        # If confirm is true, require the user to enter the passphrase twice\n        parent = self.top_level_window()\n        if confirm:\n            d = PasswordDialog(parent, None, msg, PW_PASSPHRASE)\n            confirmed, p, passphrase = d.run()\n        else:\n            d = WindowModalDialog(parent, _(\"Enter Passphrase\"))\n            pw = QLineEdit()\n            pw.setEchoMode(2)\n            pw.setMinimumWidth(200)\n            vbox = QVBoxLayout()\n            vbox.addWidget(WWLabel(msg))\n            vbox.addWidget(pw)\n            vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))\n            d.setLayout(vbox)\n            passphrase = pw.text() if d.exec_() else None\n        self.passphrase = passphrase\n        self.done.set()\n\n    def word_dialog(self, msg):\n        dialog = WindowModalDialog(self.top_level_window(), \"\")\n        hbox = QHBoxLayout(dialog)\n        hbox.addWidget(QLabel(msg))\n        text = QLineEdit()\n        text.setMaximumWidth(100)\n        text.returnPressed.connect(dialog.accept)\n        hbox.addWidget(text)\n        hbox.addStretch(1)\n        dialog.exec_()  # Firmware cannot handle cancellation\n        self.word = text.text()\n        self.done.set()\n\n    def message_dialog(self, msg, on_cancel):\n        # Called more than once during signing, to confirm output and fee\n        self.clear_dialog()\n        title = _('Please check your %s device') % self.device\n        self.dialog = dialog = WindowModalDialog(self.top_level_window(), title)\n        l = QLabel(msg)\n        vbox = QVBoxLayout(dialog)\n        vbox.addWidget(l)\n        if on_cancel:\n            dialog.rejected.connect(on_cancel)\n            vbox.addLayout(Buttons(CancelButton(dialog)))\n        dialog.show()\n\n    def error_dialog(self, msg):\n        self.win.show_error(msg, parent=self.top_level_window())\n\n    def clear_dialog(self):\n        if self.dialog:\n            self.dialog.accept()\n            self.dialog = None\n\n    def win_query_choice(self, msg, labels):\n        self.choice = self.win.query_choice(msg, labels)\n        self.done.set()\n\n    def win_yes_no_question(self, msg):\n        self.ok = self.win.question(msg)\n        self.done.set()\n\n\n\nfrom electrum.plugins import hook\nfrom electrum.util import UserCancelled\nfrom electrum_gui.qt.main_window import StatusBarButton\n\nclass QtPluginBase(object):\n\n    @hook\n    def load_wallet(self, wallet, window):\n        for keystore in wallet.get_keystores():\n            if not isinstance(keystore, self.keystore_class):\n                continue\n            if not self.libraries_available:\n                window.show_error(\n                    _(\"Cannot find python library for\") + \" '%s'.\\n\" % self.name \\\n                    + _(\"Make sure you install it with python3\")\n                )\n                return\n            tooltip = self.device + '\\n' + (keystore.label or 'unnamed')\n            cb = partial(self.show_settings_dialog, window, keystore)\n            button = StatusBarButton(QIcon(self.icon_unpaired), tooltip, cb)\n            button.icon_paired = self.icon_paired\n            button.icon_unpaired = self.icon_unpaired\n            window.statusBar().addPermanentWidget(button)\n            handler = self.create_handler(window)\n            handler.button = button\n            keystore.handler = handler\n            keystore.thread = TaskThread(window, window.on_error)\n            # Trigger a pairing\n            keystore.thread.add(partial(self.get_client, keystore))\n\n    def choose_device(self, window, keystore):\n        '''This dialog box should be usable even if the user has\n        forgotten their PIN or it is in bootloader mode.'''\n        device_id = self.device_manager().xpub_id(keystore.xpub)\n        if not device_id:\n            try:\n                info = self.device_manager().select_device(self, keystore.handler, keystore)\n            except UserCancelled:\n                return\n            device_id = info.device.id_\n        return device_id\n\n    def show_settings_dialog(self, window, keystore):\n        device_id = self.choose_device(window, keystore)\n"
  },
  {
    "path": "plugins/keepkey/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'KeepKey'\ndescription = _('Provides support for KeepKey hardware wallet')\nrequires = [('keepkeylib','github.com/keepkey/python-keepkey')]\nregisters_keystore = ('hardware', 'keepkey', _(\"KeepKey wallet\"))\navailable_for = ['qt', 'cmdline']\n"
  },
  {
    "path": "plugins/keepkey/client.py",
    "content": "from keepkeylib.client import proto, BaseClient, ProtocolMixin\nfrom .clientbase import KeepKeyClientBase\n\nclass KeepKeyClient(KeepKeyClientBase, ProtocolMixin, BaseClient):\n    def __init__(self, transport, handler, plugin):\n        BaseClient.__init__(self, transport)\n        ProtocolMixin.__init__(self, transport)\n        KeepKeyClientBase.__init__(self, handler, plugin, proto)\n\n    def recovery_device(self, *args):\n        ProtocolMixin.recovery_device(self, False, *args)\n\n\nKeepKeyClientBase.wrap_methods(KeepKeyClient)\n"
  },
  {
    "path": "plugins/keepkey/clientbase.py",
    "content": "import time\nfrom struct import pack\n\nfrom electrum.i18n import _\nfrom electrum.util import PrintError, UserCancelled\nfrom electrum.keystore import bip39_normalize_passphrase\nfrom electrum.bitcoin import serialize_xpub\n\n\nclass GuiMixin(object):\n    # Requires: self.proto, self.device\n\n    messages = {\n        3: _(\"Confirm the transaction output on your %s device\"),\n        4: _(\"Confirm internal entropy on your %s device to begin\"),\n        5: _(\"Write down the seed word shown on your %s\"),\n        6: _(\"Confirm on your %s that you want to wipe it clean\"),\n        7: _(\"Confirm on your %s device the message to sign\"),\n        8: _(\"Confirm the total amount spent and the transaction fee on your \"\n             \"%s device\"),\n        10: _(\"Confirm wallet address on your %s device\"),\n        'default': _(\"Check your %s device to continue\"),\n    }\n\n    def callback_Failure(self, msg):\n        # BaseClient's unfortunate call() implementation forces us to\n        # raise exceptions on failure in order to unwind the stack.\n        # However, making the user acknowledge they cancelled\n        # gets old very quickly, so we suppress those.  The NotInitialized\n        # one is misnamed and indicates a passphrase request was cancelled.\n        if msg.code in (self.types.Failure_PinCancelled,\n                        self.types.Failure_ActionCancelled,\n                        self.types.Failure_NotInitialized):\n            raise UserCancelled()\n        raise RuntimeError(msg.message)\n\n    def callback_ButtonRequest(self, msg):\n        message = self.msg\n        if not message:\n            message = self.messages.get(msg.code, self.messages['default'])\n        self.handler.show_message(message % self.device, self.cancel)\n        return self.proto.ButtonAck()\n\n    def callback_PinMatrixRequest(self, msg):\n        if msg.type == 2:\n            msg = _(\"Enter a new PIN for your %s:\")\n        elif msg.type == 3:\n            msg = (_(\"Re-enter the new PIN for your %s.\\n\\n\"\n                     \"NOTE: the positions of the numbers have changed!\"))\n        else:\n            msg = _(\"Enter your current %s PIN:\")\n        pin = self.handler.get_pin(msg % self.device)\n        if not pin:\n            return self.proto.Cancel()\n        return self.proto.PinMatrixAck(pin=pin)\n\n    def callback_PassphraseRequest(self, req):\n        if self.creating_wallet:\n            msg = _(\"Enter a passphrase to generate this wallet.  Each time \"\n                    \"you use this wallet your %s will prompt you for the \"\n                    \"passphrase.  If you forget the passphrase you cannot \"\n                    \"access the bitcoins in the wallet.\") % self.device\n        else:\n            msg = _(\"Enter the passphrase to unlock this wallet:\")\n        passphrase = self.handler.get_passphrase(msg, self.creating_wallet)\n        if passphrase is None:\n            return self.proto.Cancel()\n        passphrase = bip39_normalize_passphrase(passphrase)\n        return self.proto.PassphraseAck(passphrase=passphrase)\n\n    def callback_WordRequest(self, msg):\n        self.step += 1\n        msg = _(\"Step %d/24.  Enter seed word as explained on \"\n                \"your %s:\") % (self.step, self.device)\n        word = self.handler.get_word(msg)\n        # Unfortunately the device can't handle self.proto.Cancel()\n        return self.proto.WordAck(word=word)\n\n    def callback_CharacterRequest(self, msg):\n        char_info = self.handler.get_char(msg)\n        if not char_info:\n            return self.proto.Cancel()\n        return self.proto.CharacterAck(**char_info)\n\n\nclass KeepKeyClientBase(GuiMixin, PrintError):\n\n    def __init__(self, handler, plugin, proto):\n        assert hasattr(self, 'tx_api')  # ProtocolMixin already constructed?\n        self.proto = proto\n        self.device = plugin.device\n        self.handler = handler\n        self.tx_api = plugin\n        self.types = plugin.types\n        self.msg = None\n        self.creating_wallet = False\n        self.used()\n\n    def __str__(self):\n        return \"%s/%s\" % (self.label(), self.features.device_id)\n\n    def label(self):\n        '''The name given by the user to the device.'''\n        return self.features.label\n\n    def is_initialized(self):\n        '''True if initialized, False if wiped.'''\n        return self.features.initialized\n\n    def is_pairable(self):\n        return not self.features.bootloader_mode\n\n    def used(self):\n        self.last_operation = time.time()\n\n    def prevent_timeouts(self):\n        self.last_operation = float('inf')\n\n    def timeout(self, cutoff):\n        '''Time out the client if the last operation was before cutoff.'''\n        if self.last_operation < cutoff:\n            self.print_error(\"timed out\")\n            self.clear_session()\n\n    @staticmethod\n    def expand_path(n):\n        '''Convert bip32 path to list of uint32 integers with prime flags\n        0/-1/1' -> [0, 0x80000001, 0x80000001]'''\n        # This code is similar to code in trezorlib where it unforunately\n        # is not declared as a staticmethod.  Our n has an extra element.\n        PRIME_DERIVATION_FLAG = 0x80000000\n        path = []\n        for x in n.split('/')[1:]:\n            prime = 0\n            if x.endswith(\"'\"):\n                x = x.replace('\\'', '')\n                prime = PRIME_DERIVATION_FLAG\n            if x.startswith('-'):\n                prime = PRIME_DERIVATION_FLAG\n            path.append(abs(int(x)) | prime)\n        return path\n\n    def cancel(self):\n        '''Provided here as in keepkeylib but not trezorlib.'''\n        self.transport.write(self.proto.Cancel())\n\n    def i4b(self, x):\n        return pack('>I', x)\n\n    def get_xpub(self, bip32_path, xtype):\n        address_n = self.expand_path(bip32_path)\n        creating = False\n        node = self.get_public_node(address_n, creating).node\n        return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))\n\n    def toggle_passphrase(self):\n        if self.features.passphrase_protection:\n            self.msg = _(\"Confirm on your %s device to disable passphrases\")\n        else:\n            self.msg = _(\"Confirm on your %s device to enable passphrases\")\n        enabled = not self.features.passphrase_protection\n        self.apply_settings(use_passphrase=enabled)\n\n    def change_label(self, label):\n        self.msg = _(\"Confirm the new label on your %s device\")\n        self.apply_settings(label=label)\n\n    def change_homescreen(self, homescreen):\n        self.msg = _(\"Confirm on your %s device to change your home screen\")\n        self.apply_settings(homescreen=homescreen)\n\n    def set_pin(self, remove):\n        if remove:\n            self.msg = _(\"Confirm on your %s device to disable PIN protection\")\n        elif self.features.pin_protection:\n            self.msg = _(\"Confirm on your %s device to change your PIN\")\n        else:\n            self.msg = _(\"Confirm on your %s device to set a PIN\")\n        self.change_pin(remove)\n\n    def clear_session(self):\n        '''Clear the session to force pin (and passphrase if enabled)\n        re-entry.  Does not leak exceptions.'''\n        self.print_error(\"clear session:\", self)\n        self.prevent_timeouts()\n        try:\n            super(KeepKeyClientBase, self).clear_session()\n        except BaseException as e:\n            # If the device was removed it has the same effect...\n            self.print_error(\"clear_session: ignoring error\", str(e))\n            pass\n\n    def get_public_node(self, address_n, creating):\n        self.creating_wallet = creating\n        return super(KeepKeyClientBase, self).get_public_node(address_n)\n\n    def close(self):\n        '''Called when Our wallet was closed or the device removed.'''\n        self.print_error(\"closing client\")\n        self.clear_session()\n        # Release the device\n        self.transport.close()\n\n    def firmware_version(self):\n        f = self.features\n        return (f.major_version, f.minor_version, f.patch_version)\n\n    def atleast_version(self, major, minor=0, patch=0):\n        return self.firmware_version() >= (major, minor, patch)\n\n    @staticmethod\n    def wrapper(func):\n        '''Wrap methods to clear any message box they opened.'''\n\n        def wrapped(self, *args, **kwargs):\n            try:\n                self.prevent_timeouts()\n                return func(self, *args, **kwargs)\n            finally:\n                self.used()\n                self.handler.finished()\n                self.creating_wallet = False\n                self.msg = None\n\n        return wrapped\n\n    @staticmethod\n    def wrap_methods(cls):\n        for method in ['apply_settings', 'change_pin',\n                       'get_address', 'get_public_node',\n                       'load_device_by_mnemonic', 'load_device_by_xprv',\n                       'recovery_device', 'reset_device', 'sign_message',\n                       'sign_tx', 'wipe_device']:\n            setattr(cls, method, cls.wrapper(getattr(cls, method)))\n"
  },
  {
    "path": "plugins/keepkey/cmdline.py",
    "content": "from electrum.plugins import hook\nfrom .keepkey import KeepKeyPlugin\nfrom ..hw_wallet import CmdLineHandler\n\nclass Plugin(KeepKeyPlugin):\n    handler = CmdLineHandler()\n    @hook\n    def init_keystore(self, keystore):\n        if not isinstance(keystore, self.keystore_class):\n            return\n        keystore.handler = self.handler\n"
  },
  {
    "path": "plugins/keepkey/keepkey.py",
    "content": "from .plugin import KeepKeyCompatiblePlugin, KeepKeyCompatibleKeyStore\n\n\nclass KeepKey_KeyStore(KeepKeyCompatibleKeyStore):\n    hw_type = 'keepkey'\n    device = 'KeepKey'\n\n\nclass KeepKeyPlugin(KeepKeyCompatiblePlugin):\n    firmware_URL = 'https://www.keepkey.com'\n    libraries_URL = 'https://github.com/keepkey/python-keepkey'\n    minimum_firmware = (1, 0, 0)\n    keystore_class = KeepKey_KeyStore\n\n    def __init__(self, *args):\n        try:\n            from . import client\n            import keepkeylib\n            import keepkeylib.ckd_public\n            import keepkeylib.transport_hid\n            self.client_class = client.KeepKeyClient\n            self.ckd_public = keepkeylib.ckd_public\n            self.types = keepkeylib.client.types\n            self.DEVICE_IDS = keepkeylib.transport_hid.DEVICE_IDS\n            self.libraries_available = True\n        except ImportError:\n            self.libraries_available = False\n        KeepKeyCompatiblePlugin.__init__(self, *args)\n\n    def hid_transport(self, pair):\n        from keepkeylib.transport_hid import HidTransport\n        return HidTransport(pair)\n\n    def bridge_transport(self, d):\n        raise NotImplementedError('')\n"
  },
  {
    "path": "plugins/keepkey/plugin.py",
    "content": "import threading\n\nfrom binascii import hexlify, unhexlify\n\nfrom electrum.util import bfh, bh2u\nfrom electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey,\n                              TYPE_ADDRESS, TYPE_SCRIPT, NetworkConstants,\n                              is_segwit_address)\nfrom electrum.i18n import _\nfrom electrum.plugins import BasePlugin\nfrom electrum.transaction import deserialize\nfrom electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey\nfrom electrum.base_wizard import ScriptTypeNotSupported\n\nfrom ..hw_wallet import HW_PluginBase\n\n\n# TREZOR initialization methods\nTIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)\n\nclass KeepKeyCompatibleKeyStore(Hardware_KeyStore):\n\n    def get_derivation(self):\n        return self.derivation\n\n    def is_segwit(self):\n        return self.derivation.startswith(\"m/49'/\")\n\n    def get_client(self, force_pair=True):\n        return self.plugin.get_client(self, force_pair)\n\n    def decrypt_message(self, sequence, message, password):\n        raise RuntimeError(_('Encryption and decryption are not implemented by %s') % self.device)\n\n    def sign_message(self, sequence, message, password):\n        client = self.get_client()\n        address_path = self.get_derivation() + \"/%d/%d\"%sequence\n        address_n = client.expand_path(address_path)\n        msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)\n        return msg_sig.signature\n\n    def sign_transaction(self, tx, password):\n        if tx.is_complete():\n            return\n        # previous transactions used as inputs\n        prev_tx = {}\n        # path of the xpubs that are involved\n        xpub_path = {}\n        for txin in tx.inputs():\n            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)\n            tx_hash = txin['prevout_hash']\n            prev_tx[tx_hash] = txin['prev_tx']\n            for x_pubkey in x_pubkeys:\n                if not is_xpubkey(x_pubkey):\n                    continue\n                xpub, s = parse_xpubkey(x_pubkey)\n                if xpub == self.get_master_public_key():\n                    xpub_path[xpub] = self.get_derivation()\n\n        self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)\n\n\nclass KeepKeyCompatiblePlugin(HW_PluginBase):\n    # Derived classes provide:\n    #\n    #  class-static variables: client_class, firmware_URL, handler_class,\n    #     libraries_available, libraries_URL, minimum_firmware,\n    #     wallet_class, ckd_public, types, HidTransport\n\n    MAX_LABEL_LEN = 32\n\n    def __init__(self, parent, config, name):\n        HW_PluginBase.__init__(self, parent, config, name)\n        self.main_thread = threading.current_thread()\n        # FIXME: move to base class when Ledger is fixed\n        if self.libraries_available:\n            self.device_manager().register_devices(self.DEVICE_IDS)\n\n    def _try_hid(self, device):\n        self.print_error(\"Trying to connect over USB...\")\n        if device.interface_number == 1:\n            pair = [None, device.path]\n        else:\n            pair = [device.path, None]\n\n        try:\n            return self.hid_transport(pair)\n        except BaseException as e:\n            # see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114\n            # raise\n            self.print_error(\"cannot connect at\", device.path, str(e))\n            return None\n\n    def _try_bridge(self, device):\n        self.print_error(\"Trying to connect over Trezor Bridge...\")\n        try:\n            return self.bridge_transport({'path': hexlify(device.path)})\n        except BaseException as e:\n            self.print_error(\"cannot connect to bridge\", str(e))\n            return None\n\n    def create_client(self, device, handler):\n        # disable bridge because it seems to never returns if keepkey is plugged\n        #transport = self._try_bridge(device) or self._try_hid(device)\n        transport = self._try_hid(device)\n        if not transport:\n            self.print_error(\"cannot connect to device\")\n            return\n\n        self.print_error(\"connected to device at\", device.path)\n\n        client = self.client_class(transport, handler, self)\n\n        # Try a ping for device sanity\n        try:\n            client.ping('t')\n        except BaseException as e:\n            self.print_error(\"ping failed\", str(e))\n            return None\n\n        if not client.atleast_version(*self.minimum_firmware):\n            msg = (_('Outdated %s firmware for device labelled %s. Please '\n                     'download the updated firmware from %s') %\n                   (self.device, client.label(), self.firmware_URL))\n            self.print_error(msg)\n            handler.show_error(msg)\n            return None\n\n        return client\n\n    def get_client(self, keystore, force_pair=True):\n        devmgr = self.device_manager()\n        handler = keystore.handler\n        with devmgr.hid_lock:\n            client = devmgr.client_for_keystore(self, handler, keystore, force_pair)\n        # returns the client for a given keystore. can use xpub\n        if client:\n            client.used()\n        return client\n\n    def get_coin_name(self):\n        return \"Testnet\" if NetworkConstants.TESTNET else \"Bitcoin\"\n\n    def initialize_device(self, device_id, wizard, handler):\n        # Initialization method\n        msg = _(\"Choose how you want to initialize your %s.\\n\\n\"\n                \"The first two methods are secure as no secret information \"\n                \"is entered into your computer.\\n\\n\"\n                \"For the last two methods you input secrets on your keyboard \"\n                \"and upload them to your %s, and so you should \"\n                \"only do those on a computer you know to be trustworthy \"\n                \"and free of malware.\"\n        ) % (self.device, self.device)\n        choices = [\n            # Must be short as QT doesn't word-wrap radio button text\n            (TIM_NEW, _(\"Let the device generate a completely new seed randomly\")),\n            (TIM_RECOVER, _(\"Recover from a seed you have previously written down\")),\n            (TIM_MNEMONIC, _(\"Upload a BIP39 mnemonic to generate the seed\")),\n            (TIM_PRIVKEY, _(\"Upload a master private key\"))\n        ]\n        def f(method):\n            import threading\n            settings = self.request_trezor_init_settings(wizard, method, self.device)\n            t = threading.Thread(target = self._initialize_device, args=(settings, method, device_id, wizard, handler))\n            t.setDaemon(True)\n            t.start()\n            wizard.loop.exec_()\n        wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)\n\n    def _initialize_device(self, settings, method, device_id, wizard, handler):\n        item, label, pin_protection, passphrase_protection = settings\n\n        language = 'english'\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n\n        if method == TIM_NEW:\n            strength = 64 * (item + 2)  # 128, 192 or 256\n            client.reset_device(True, strength, passphrase_protection,\n                                pin_protection, label, language)\n        elif method == TIM_RECOVER:\n            word_count = 6 * (item + 2)  # 12, 18 or 24\n            client.step = 0\n            client.recovery_device(word_count, passphrase_protection,\n                                       pin_protection, label, language)\n        elif method == TIM_MNEMONIC:\n            pin = pin_protection  # It's the pin, not a boolean\n            client.load_device_by_mnemonic(str(item), pin,\n                                           passphrase_protection,\n                                           label, language)\n        else:\n            pin = pin_protection  # It's the pin, not a boolean\n            client.load_device_by_xprv(item, pin, passphrase_protection,\n                                       label, language)\n        wizard.loop.exit(0)\n\n    def setup_device(self, device_info, wizard):\n        '''Called when creating a new wallet.  Select the device to use.  If\n        the device is uninitialized, go through the intialization\n        process.'''\n        devmgr = self.device_manager()\n        device_id = device_info.device.id_\n        client = devmgr.client_by_id(device_id)\n        # fixme: we should use: client.handler = wizard\n        client.handler = self.create_handler(wizard)\n        if not device_info.initialized:\n            self.initialize_device(device_id, wizard, client.handler)\n        client.get_xpub('m', 'standard')\n        client.used()\n\n    def get_xpub(self, device_id, derivation, xtype, wizard):\n        if xtype not in ('standard',):\n            raise ScriptTypeNotSupported(_('This type of script is not supported with KeepKey.'))\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n        client.handler = wizard\n        xpub = client.get_xpub(derivation, xtype)\n        client.used()\n        return xpub\n\n    def sign_transaction(self, keystore, tx, prev_tx, xpub_path):\n        self.prev_tx = prev_tx\n        self.xpub_path = xpub_path\n        client = self.get_client(keystore)\n        inputs = self.tx_inputs(tx, True, keystore.is_segwit())\n        outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.is_segwit())\n        signed_tx = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[1]\n        raw = bh2u(signed_tx)\n        tx.update_signatures(raw)\n\n    def show_address(self, wallet, address):\n        client = self.get_client(wallet.keystore)\n        if not client.atleast_version(1, 3):\n            wallet.keystore.handler.show_error(_(\"Your device firmware is too old\"))\n            return\n        change, index = wallet.get_address_index(address)\n        derivation = wallet.keystore.derivation\n        address_path = \"%s/%d/%d\"%(derivation, change, index)\n        address_n = client.expand_path(address_path)\n        segwit = wallet.keystore.is_segwit()\n        script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS\n        client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)\n\n    def tx_inputs(self, tx, for_sig=False, segwit=False):\n        inputs = []\n        for txin in tx.inputs():\n            txinputtype = self.types.TxInputType()\n            if txin['type'] == 'coinbase':\n                prev_hash = \"\\0\"*32\n                prev_index = 0xffffffff  # signed int -1\n            else:\n                if for_sig:\n                    x_pubkeys = txin['x_pubkeys']\n                    if len(x_pubkeys) == 1:\n                        x_pubkey = x_pubkeys[0]\n                        xpub, s = parse_xpubkey(x_pubkey)\n                        xpub_n = self.client_class.expand_path(self.xpub_path[xpub])\n                        txinputtype.address_n.extend(xpub_n + s)\n                        txinputtype.script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS\n                    else:\n                        def f(x_pubkey):\n                            if is_xpubkey(x_pubkey):\n                                xpub, s = parse_xpubkey(x_pubkey)\n                            else:\n                                xpub = xpub_from_pubkey(0, bfh(x_pubkey))\n                                s = []\n                            node = self.ckd_public.deserialize(xpub)\n                            return self.types.HDNodePathType(node=node, address_n=s)\n                        pubkeys = map(f, x_pubkeys)\n                        multisig = self.types.MultisigRedeemScriptType(\n                            pubkeys=pubkeys,\n                            signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')),\n                            m=txin.get('num_sig'),\n                        )\n                        script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDMULTISIG\n                        txinputtype = self.types.TxInputType(\n                            script_type=script_type,\n                            multisig=multisig\n                        )\n                        # find which key is mine\n                        for x_pubkey in x_pubkeys:\n                            if is_xpubkey(x_pubkey):\n                                xpub, s = parse_xpubkey(x_pubkey)\n                                if xpub in self.xpub_path:\n                                    xpub_n = self.client_class.expand_path(self.xpub_path[xpub])\n                                    txinputtype.address_n.extend(xpub_n + s)\n                                    break\n\n                prev_hash = unhexlify(txin['prevout_hash'])\n                prev_index = txin['prevout_n']\n\n            if 'value' in txin:\n                txinputtype.amount = txin['value']\n            txinputtype.prev_hash = prev_hash\n            txinputtype.prev_index = prev_index\n\n            if 'scriptSig' in txin:\n                script_sig = bfh(txin['scriptSig'])\n                txinputtype.script_sig = script_sig\n\n            txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)\n\n            inputs.append(txinputtype)\n\n        return inputs\n\n    def tx_outputs(self, derivation, tx, segwit=False):\n        outputs = []\n        has_change = False\n\n        for _type, address, amount in tx.outputs():\n            info = tx.output_info.get(address)\n            if info is not None and not has_change:\n                has_change = True # no more than one change address\n                addrtype, hash_160 = b58_address_to_hash160(address)\n                index, xpubs, m = info\n                if len(xpubs) == 1:\n                    script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS\n                    address_n = self.client_class.expand_path(derivation + \"/%d/%d\"%index)\n                    txoutputtype = self.types.TxOutputType(\n                        amount = amount,\n                        script_type = script_type,\n                        address_n = address_n,\n                    )\n                else:\n                    script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG\n                    address_n = self.client_class.expand_path(\"/%d/%d\"%index)\n                    nodes = map(self.ckd_public.deserialize, xpubs)\n                    pubkeys = [ self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes]\n                    multisig = self.types.MultisigRedeemScriptType(\n                        pubkeys = pubkeys,\n                        signatures = [b''] * len(pubkeys),\n                        m = m)\n                    txoutputtype = self.types.TxOutputType(\n                        multisig = multisig,\n                        amount = amount,\n                        address_n = self.client_class.expand_path(derivation + \"/%d/%d\"%index),\n                        script_type = script_type)\n            else:\n                txoutputtype = self.types.TxOutputType()\n                txoutputtype.amount = amount\n                if _type == TYPE_SCRIPT:\n                    txoutputtype.script_type = self.types.PAYTOOPRETURN\n                    txoutputtype.op_return_data = address[2:]\n                elif _type == TYPE_ADDRESS:\n                    if is_segwit_address(address):\n                        txoutputtype.script_type = self.types.PAYTOWITNESS\n                    else:\n                        addrtype, hash_160 = b58_address_to_hash160(address)\n                        if addrtype == NetworkConstants.ADDRTYPE_P2PKH:\n                            txoutputtype.script_type = self.types.PAYTOADDRESS\n                        elif addrtype == NetworkConstants.ADDRTYPE_P2SH:\n                            txoutputtype.script_type = self.types.PAYTOSCRIPTHASH\n                        else:\n                            raise BaseException('addrtype: ' + str(addrtype))\n                    txoutputtype.address = address\n\n            outputs.append(txoutputtype)\n\n        return outputs\n\n    def electrum_tx_to_txtype(self, tx):\n        t = self.types.TransactionType()\n        d = deserialize(tx.raw)\n        t.version = d['version']\n        t.lock_time = d['lockTime']\n        inputs = self.tx_inputs(tx)\n        t.inputs.extend(inputs)\n        for vout in d['outputs']:\n            o = t.bin_outputs.add()\n            o.amount = vout['value']\n            o.script_pubkey = bfh(vout['scriptPubKey'])\n        return t\n\n    # This function is called from the trezor libraries (via tx_api)\n    def get_tx(self, tx_hash):\n        tx = self.prev_tx[tx_hash]\n        return self.electrum_tx_to_txtype(tx)\n"
  },
  {
    "path": "plugins/keepkey/qt.py",
    "content": "from .qt_generic import QtPlugin\nfrom .keepkey import KeepKeyPlugin\n\n\nclass Plugin(KeepKeyPlugin, QtPlugin):\n    icon_paired = \":icons/keepkey.png\"\n    icon_unpaired = \":icons/keepkey_unpaired.png\"\n\n    @classmethod\n    def pin_matrix_widget_class(self):\n        from keepkeylib.qt.pinmatrix import PinMatrixWidget\n        return PinMatrixWidget\n"
  },
  {
    "path": "plugins/keepkey/qt_generic.py",
    "content": "from functools import partial\nimport threading\n\nfrom PyQt5.Qt import Qt\nfrom PyQt5.Qt import QGridLayout, QInputDialog, QPushButton\nfrom PyQt5.Qt import QVBoxLayout, QLabel\nfrom electrum_gui.qt.util import *\nfrom .plugin import TIM_NEW, TIM_RECOVER, TIM_MNEMONIC\nfrom ..hw_wallet.qt import QtHandlerBase, QtPluginBase\n\nfrom electrum.i18n import _\nfrom electrum.plugins import hook, DeviceMgr\nfrom electrum.util import PrintError, UserCancelled, bh2u\nfrom electrum.wallet import Wallet, Standard_Wallet\n\nPASSPHRASE_HELP_SHORT =_(\n    \"Passphrases allow you to access new wallets, each \"\n    \"hidden behind a particular case-sensitive passphrase.\")\nPASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + \"  \" + _(\n    \"You need to create a separate Electrum wallet for each passphrase \"\n    \"you use as they each generate different addresses.  Changing \"\n    \"your passphrase does not lose other wallets, each is still \"\n    \"accessible behind its own passphrase.\")\nRECOMMEND_PIN = _(\n    \"You should enable PIN protection.  Your PIN is the only protection \"\n    \"for your bitcoins if your device is lost or stolen.\")\nPASSPHRASE_NOT_PIN = _(\n    \"If you forget a passphrase you will be unable to access any \"\n    \"bitcoins in the wallet behind it.  A passphrase is not a PIN. \"\n    \"Only change this if you are sure you understand it.\")\nCHARACTER_RECOVERY = (\n    \"Use the recovery cipher shown on your device to input your seed words.  \"\n    \"The cipher changes with every keypress.\\n\"\n    \"After at most 4 letters the device will auto-complete a word.\\n\"\n    \"Press SPACE or the Accept Word button to accept the device's auto-\"\n    \"completed word and advance to the next one.\\n\"\n    \"Press BACKSPACE to go back a character or word.\\n\"\n    \"Press ENTER or the Seed Entered button once the last word in your \"\n    \"seed is auto-completed.\")\n\nclass CharacterButton(QPushButton):\n    def __init__(self, text=None):\n        QPushButton.__init__(self, text)\n\n    def keyPressEvent(self, event):\n        event.setAccepted(False)   # Pass through Enter and Space keys\n\n\nclass CharacterDialog(WindowModalDialog):\n\n    def __init__(self, parent):\n        super(CharacterDialog, self).__init__(parent)\n        self.setWindowTitle(_(\"KeepKey Seed Recovery\"))\n        self.character_pos = 0\n        self.word_pos = 0\n        self.loop = QEventLoop()\n        self.word_help = QLabel()\n        self.char_buttons = []\n\n        vbox = QVBoxLayout(self)\n        vbox.addWidget(WWLabel(CHARACTER_RECOVERY))\n        hbox = QHBoxLayout()\n        hbox.addWidget(self.word_help)\n        for i in range(4):\n            char_button = CharacterButton('*')\n            char_button.setMaximumWidth(36)\n            self.char_buttons.append(char_button)\n            hbox.addWidget(char_button)\n        self.accept_button = CharacterButton(_(\"Accept Word\"))\n        self.accept_button.clicked.connect(partial(self.process_key, 32))\n        self.rejected.connect(partial(self.loop.exit, 1))\n        hbox.addWidget(self.accept_button)\n        hbox.addStretch(1)\n        vbox.addLayout(hbox)\n\n        self.finished_button = QPushButton(_(\"Seed Entered\"))\n        self.cancel_button = QPushButton(_(\"Cancel\"))\n        self.finished_button.clicked.connect(partial(self.process_key,\n                                                     Qt.Key_Return))\n        self.cancel_button.clicked.connect(self.rejected)\n        buttons = Buttons(self.finished_button, self.cancel_button)\n        vbox.addSpacing(40)\n        vbox.addLayout(buttons)\n        self.refresh()\n        self.show()\n\n    def refresh(self):\n        self.word_help.setText(\"Enter seed word %2d:\" % (self.word_pos + 1))\n        self.accept_button.setEnabled(self.character_pos >= 3)\n        self.finished_button.setEnabled((self.word_pos in (11, 17, 23)\n                                         and self.character_pos >= 3))\n        for n, button in enumerate(self.char_buttons):\n            button.setEnabled(n == self.character_pos)\n            if n == self.character_pos:\n                button.setFocus()\n\n    def is_valid_alpha_space(self, key):\n        # Auto-completion requires at least 3 characters\n        if key == ord(' ') and self.character_pos >= 3:\n            return True\n        # Firmware aborts protocol if the 5th character is non-space\n        if self.character_pos >= 4:\n            return False\n        return (key >= ord('a') and key <= ord('z')\n                or (key >= ord('A') and key <= ord('Z')))\n\n    def process_key(self, key):\n        self.data = None\n        if key == Qt.Key_Return and self.finished_button.isEnabled():\n            self.data = {'done': True}\n        elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos):\n            self.data = {'delete': True}\n        elif self.is_valid_alpha_space(key):\n            self.data = {'character': chr(key).lower()}\n        if self.data:\n            self.loop.exit(0)\n\n    def keyPressEvent(self, event):\n        self.process_key(event.key())\n        if not self.data:\n            QDialog.keyPressEvent(self, event)\n\n    def get_char(self, word_pos, character_pos):\n        self.word_pos = word_pos\n        self.character_pos = character_pos\n        self.refresh()\n        if self.loop.exec_():\n            self.data = None  # User cancelled\n\n\nclass QtHandler(QtHandlerBase):\n\n    char_signal = pyqtSignal(object)\n    pin_signal = pyqtSignal(object)\n\n    def __init__(self, win, pin_matrix_widget_class, device):\n        super(QtHandler, self).__init__(win, device)\n        self.char_signal.connect(self.update_character_dialog)\n        self.pin_signal.connect(self.pin_dialog)\n        self.pin_matrix_widget_class = pin_matrix_widget_class\n        self.character_dialog = None\n\n    def get_char(self, msg):\n        self.done.clear()\n        self.char_signal.emit(msg)\n        self.done.wait()\n        data = self.character_dialog.data\n        if not data or 'done' in data:\n            self.character_dialog.accept()\n            self.character_dialog = None\n        return data\n\n    def get_pin(self, msg):\n        self.done.clear()\n        self.pin_signal.emit(msg)\n        self.done.wait()\n        return self.response\n\n    def pin_dialog(self, msg):\n        # Needed e.g. when resetting a device\n        self.clear_dialog()\n        dialog = WindowModalDialog(self.top_level_window(), _(\"Enter PIN\"))\n        matrix = self.pin_matrix_widget_class()\n        vbox = QVBoxLayout()\n        vbox.addWidget(QLabel(msg))\n        vbox.addWidget(matrix)\n        vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))\n        dialog.setLayout(vbox)\n        dialog.exec_()\n        self.response = str(matrix.get_value())\n        self.done.set()\n\n    def update_character_dialog(self, msg):\n        if not self.character_dialog:\n            self.character_dialog = CharacterDialog(self.top_level_window())\n        self.character_dialog.get_char(msg.word_pos, msg.character_pos)\n        self.done.set()\n\n\n\nclass QtPlugin(QtPluginBase):\n    # Derived classes must provide the following class-static variables:\n    #   icon_file\n    #   pin_matrix_widget_class\n\n    def create_handler(self, window):\n        return QtHandler(window, self.pin_matrix_widget_class(), self.device)\n\n    @hook\n    def receive_menu(self, menu, addrs, wallet):\n        if type(wallet) is not Standard_Wallet:\n            return\n        keystore = wallet.get_keystore()\n        if type(keystore) == self.keystore_class and len(addrs) == 1:\n            def show_address():\n                keystore.thread.add(partial(self.show_address, wallet, addrs[0]))\n            menu.addAction(_(\"Show on %s\") % self.device, show_address)\n\n    def show_settings_dialog(self, window, keystore):\n        device_id = self.choose_device(window, keystore)\n        if device_id:\n            SettingsDialog(window, self, keystore, device_id).exec_()\n\n    def request_trezor_init_settings(self, wizard, method, device):\n        vbox = QVBoxLayout()\n        next_enabled = True\n        label = QLabel(_(\"Enter a label to name your device:\"))\n        name = QLineEdit()\n        hl = QHBoxLayout()\n        hl.addWidget(label)\n        hl.addWidget(name)\n        hl.addStretch(1)\n        vbox.addLayout(hl)\n\n        def clean_text(widget):\n            text = widget.toPlainText().strip()\n            return ' '.join(text.split())\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            gb = QGroupBox()\n            hbox1 = QHBoxLayout()\n            gb.setLayout(hbox1)\n            # KeepKey recovery doesn't need a word count\n            if method == TIM_NEW or self.device == 'TREZOR':\n                vbox.addWidget(gb)\n            gb.setTitle(_(\"Select your seed length:\"))\n            bg = QButtonGroup()\n            for i, count in enumerate([12, 18, 24]):\n                rb = QRadioButton(gb)\n                rb.setText(_(\"%d words\") % count)\n                bg.addButton(rb)\n                bg.setId(rb, i)\n                hbox1.addWidget(rb)\n                rb.setChecked(True)\n            cb_pin = QCheckBox(_('Enable PIN protection'))\n            cb_pin.setChecked(True)\n        else:\n            text = QTextEdit()\n            text.setMaximumHeight(60)\n            if method == TIM_MNEMONIC:\n                msg = _(\"Enter your BIP39 mnemonic:\")\n            else:\n                msg = _(\"Enter the master private key beginning with xprv:\")\n                def set_enabled():\n                    from electrum.keystore import is_xprv\n                    wizard.next_button.setEnabled(is_xprv(clean_text(text)))\n                text.textChanged.connect(set_enabled)\n                next_enabled = False\n\n            vbox.addWidget(QLabel(msg))\n            vbox.addWidget(text)\n            pin = QLineEdit()\n            pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}')))\n            pin.setMaximumWidth(100)\n            hbox_pin = QHBoxLayout()\n            hbox_pin.addWidget(QLabel(_(\"Enter your PIN (digits 1-9):\")))\n            hbox_pin.addWidget(pin)\n            hbox_pin.addStretch(1)\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            vbox.addWidget(WWLabel(RECOMMEND_PIN))\n            vbox.addWidget(cb_pin)\n        else:\n            vbox.addLayout(hbox_pin)\n\n        passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)\n        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)\n        passphrase_warning.setStyleSheet(\"color: red\")\n        cb_phrase = QCheckBox(_('Enable passphrases'))\n        cb_phrase.setChecked(False)\n        vbox.addWidget(passphrase_msg)\n        vbox.addWidget(passphrase_warning)\n        vbox.addWidget(cb_phrase)\n\n        wizard.exec_layout(vbox, next_enabled=next_enabled)\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            item = bg.checkedId()\n            pin = cb_pin.isChecked()\n        else:\n            item = ' '.join(str(clean_text(text)).split())\n            pin = str(pin.text())\n\n        return (item, name.text(), pin, cb_phrase.isChecked())\n\n\n\n\nclass SettingsDialog(WindowModalDialog):\n    '''This dialog doesn't require a device be paired with a wallet.\n    We want users to be able to wipe a device even if they've forgotten\n    their PIN.'''\n\n    def __init__(self, window, plugin, keystore, device_id):\n        title = _(\"%s Settings\") % plugin.device\n        super(SettingsDialog, self).__init__(window, title)\n        self.setMaximumWidth(540)\n\n        devmgr = plugin.device_manager()\n        config = devmgr.config\n        handler = keystore.handler\n        thread = keystore.thread\n        hs_rows, hs_cols = (64, 128)\n\n        def invoke_client(method, *args, **kw_args):\n            unpair_after = kw_args.pop('unpair_after', False)\n\n            def task():\n                client = devmgr.client_by_id(device_id)\n                if not client:\n                    raise RuntimeError(\"Device not connected\")\n                if method:\n                    getattr(client, method)(*args, **kw_args)\n                if unpair_after:\n                    devmgr.unpair_id(device_id)\n                return client.features\n\n            thread.add(task, on_success=update)\n\n        def update(features):\n            self.features = features\n            set_label_enabled()\n            bl_hash = bh2u(features.bootloader_hash)\n            bl_hash = \"\\n\".join([bl_hash[:32], bl_hash[32:]])\n            noyes = [_(\"No\"), _(\"Yes\")]\n            endis = [_(\"Enable Passphrases\"), _(\"Disable Passphrases\")]\n            disen = [_(\"Disabled\"), _(\"Enabled\")]\n            setchange = [_(\"Set a PIN\"), _(\"Change PIN\")]\n\n            version = \"%d.%d.%d\" % (features.major_version,\n                                    features.minor_version,\n                                    features.patch_version)\n            coins = \", \".join(coin.coin_name for coin in features.coins)\n\n            device_label.setText(features.label)\n            pin_set_label.setText(noyes[features.pin_protection])\n            passphrases_label.setText(disen[features.passphrase_protection])\n            bl_hash_label.setText(bl_hash)\n            label_edit.setText(features.label)\n            device_id_label.setText(features.device_id)\n            initialized_label.setText(noyes[features.initialized])\n            version_label.setText(version)\n            coins_label.setText(coins)\n            clear_pin_button.setVisible(features.pin_protection)\n            clear_pin_warning.setVisible(features.pin_protection)\n            pin_button.setText(setchange[features.pin_protection])\n            pin_msg.setVisible(not features.pin_protection)\n            passphrase_button.setText(endis[features.passphrase_protection])\n            language_label.setText(features.language)\n\n        def set_label_enabled():\n            label_apply.setEnabled(label_edit.text() != self.features.label)\n\n        def rename():\n            invoke_client('change_label', label_edit.text())\n\n        def toggle_passphrase():\n            title = _(\"Confirm Toggle Passphrase Protection\")\n            currently_enabled = self.features.passphrase_protection\n            if currently_enabled:\n                msg = _(\"After disabling passphrases, you can only pair this \"\n                        \"Electrum wallet if it had an empty passphrase.  \"\n                        \"If its passphrase was not empty, you will need to \"\n                        \"create a new wallet with the install wizard.  You \"\n                        \"can use this wallet again at any time by re-enabling \"\n                        \"passphrases and entering its passphrase.\")\n            else:\n                msg = _(\"Your current Electrum wallet can only be used with \"\n                        \"an empty passphrase.  You must create a separate \"\n                        \"wallet with the install wizard for other passphrases \"\n                        \"as each one generates a new set of addresses.\")\n            msg += \"\\n\\n\" + _(\"Are you sure you want to proceed?\")\n            if not self.question(msg, title=title):\n                return\n            invoke_client('toggle_passphrase', unpair_after=currently_enabled)\n\n        def change_homescreen():\n            from PIL import Image  # FIXME\n            dialog = QFileDialog(self, _(\"Choose Homescreen\"))\n            filename, __ = dialog.getOpenFileName()\n            if filename:\n                im = Image.open(str(filename))\n                if im.size != (hs_cols, hs_rows):\n                    raise Exception('Image must be 64 x 128 pixels')\n                im = im.convert('1')\n                pix = im.load()\n                img = ''\n                for j in range(hs_rows):\n                    for i in range(hs_cols):\n                        img += '1' if pix[i, j] else '0'\n                img = ''.join(chr(int(img[i:i + 8], 2))\n                              for i in range(0, len(img), 8))\n                invoke_client('change_homescreen', img)\n\n        def clear_homescreen():\n            invoke_client('change_homescreen', '\\x00')\n\n        def set_pin():\n            invoke_client('set_pin', remove=False)\n\n        def clear_pin():\n            invoke_client('set_pin', remove=True)\n\n        def wipe_device():\n            wallet = window.wallet\n            if wallet and sum(wallet.get_balance()):\n                title = _(\"Confirm Device Wipe\")\n                msg = _(\"Are you SURE you want to wipe the device?\\n\"\n                        \"Your wallet still has bitcoins in it!\")\n                if not self.question(msg, title=title,\n                                     icon=QMessageBox.Critical):\n                    return\n            invoke_client('wipe_device', unpair_after=True)\n\n        def slider_moved():\n            mins = timeout_slider.sliderPosition()\n            timeout_minutes.setText(_(\"%2d minutes\") % mins)\n\n        def slider_released():\n            config.set_session_timeout(timeout_slider.sliderPosition() * 60)\n\n        # Information tab\n        info_tab = QWidget()\n        info_layout = QVBoxLayout(info_tab)\n        info_glayout = QGridLayout()\n        info_glayout.setColumnStretch(2, 1)\n        device_label = QLabel()\n        pin_set_label = QLabel()\n        passphrases_label = QLabel()\n        version_label = QLabel()\n        device_id_label = QLabel()\n        bl_hash_label = QLabel()\n        bl_hash_label.setWordWrap(True)\n        coins_label = QLabel()\n        coins_label.setWordWrap(True)\n        language_label = QLabel()\n        initialized_label = QLabel()\n        rows = [\n            (_(\"Device Label\"), device_label),\n            (_(\"PIN set\"), pin_set_label),\n            (_(\"Passphrases\"), passphrases_label),\n            (_(\"Firmware Version\"), version_label),\n            (_(\"Device ID\"), device_id_label),\n            (_(\"Bootloader Hash\"), bl_hash_label),\n            (_(\"Supported Coins\"), coins_label),\n            (_(\"Language\"), language_label),\n            (_(\"Initialized\"), initialized_label),\n        ]\n        for row_num, (label, widget) in enumerate(rows):\n            info_glayout.addWidget(QLabel(label), row_num, 0)\n            info_glayout.addWidget(widget, row_num, 1)\n        info_layout.addLayout(info_glayout)\n\n        # Settings tab\n        settings_tab = QWidget()\n        settings_layout = QVBoxLayout(settings_tab)\n        settings_glayout = QGridLayout()\n\n        # Settings tab - Label\n        label_msg = QLabel(_(\"Name this %s.  If you have mutiple devices \"\n                             \"their labels help distinguish them.\")\n                           % plugin.device)\n        label_msg.setWordWrap(True)\n        label_label = QLabel(_(\"Device Label\"))\n        label_edit = QLineEdit()\n        label_edit.setMinimumWidth(150)\n        label_edit.setMaxLength(plugin.MAX_LABEL_LEN)\n        label_apply = QPushButton(_(\"Apply\"))\n        label_apply.clicked.connect(rename)\n        label_edit.textChanged.connect(set_label_enabled)\n        settings_glayout.addWidget(label_label, 0, 0)\n        settings_glayout.addWidget(label_edit, 0, 1, 1, 2)\n        settings_glayout.addWidget(label_apply, 0, 3)\n        settings_glayout.addWidget(label_msg, 1, 1, 1, -1)\n\n        # Settings tab - PIN\n        pin_label = QLabel(_(\"PIN Protection\"))\n        pin_button = QPushButton()\n        pin_button.clicked.connect(set_pin)\n        settings_glayout.addWidget(pin_label, 2, 0)\n        settings_glayout.addWidget(pin_button, 2, 1)\n        pin_msg = QLabel(_(\"PIN protection is strongly recommended.  \"\n                           \"A PIN is your only protection against someone \"\n                           \"stealing your bitcoins if they obtain physical \"\n                           \"access to your %s.\") % plugin.device)\n        pin_msg.setWordWrap(True)\n        pin_msg.setStyleSheet(\"color: red\")\n        settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)\n\n        # Settings tab - Homescreen\n        if plugin.device != 'KeepKey':   # Not yet supported by KK firmware\n            homescreen_layout = QHBoxLayout()\n            homescreen_label = QLabel(_(\"Homescreen\"))\n            homescreen_change_button = QPushButton(_(\"Change...\"))\n            homescreen_clear_button = QPushButton(_(\"Reset\"))\n            homescreen_change_button.clicked.connect(change_homescreen)\n            homescreen_clear_button.clicked.connect(clear_homescreen)\n            homescreen_msg = QLabel(_(\"You can set the homescreen on your \"\n                                      \"device to personalize it.  You must \"\n                                      \"choose a %d x %d monochrome black and \"\n                                      \"white image.\") % (hs_rows, hs_cols))\n            homescreen_msg.setWordWrap(True)\n            settings_glayout.addWidget(homescreen_label, 4, 0)\n            settings_glayout.addWidget(homescreen_change_button, 4, 1)\n            settings_glayout.addWidget(homescreen_clear_button, 4, 2)\n            settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)\n\n        # Settings tab - Session Timeout\n        timeout_label = QLabel(_(\"Session Timeout\"))\n        timeout_minutes = QLabel()\n        timeout_slider = QSlider(Qt.Horizontal)\n        timeout_slider.setRange(1, 60)\n        timeout_slider.setSingleStep(1)\n        timeout_slider.setTickInterval(5)\n        timeout_slider.setTickPosition(QSlider.TicksBelow)\n        timeout_slider.setTracking(True)\n        timeout_msg = QLabel(\n            _(\"Clear the session after the specified period \"\n              \"of inactivity.  Once a session has timed out, \"\n              \"your PIN and passphrase (if enabled) must be \"\n              \"re-entered to use the device.\"))\n        timeout_msg.setWordWrap(True)\n        timeout_slider.setSliderPosition(config.get_session_timeout() // 60)\n        slider_moved()\n        timeout_slider.valueChanged.connect(slider_moved)\n        timeout_slider.sliderReleased.connect(slider_released)\n        settings_glayout.addWidget(timeout_label, 6, 0)\n        settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)\n        settings_glayout.addWidget(timeout_minutes, 6, 4)\n        settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)\n        settings_layout.addLayout(settings_glayout)\n        settings_layout.addStretch(1)\n\n        # Advanced tab\n        advanced_tab = QWidget()\n        advanced_layout = QVBoxLayout(advanced_tab)\n        advanced_glayout = QGridLayout()\n\n        # Advanced tab - clear PIN\n        clear_pin_button = QPushButton(_(\"Disable PIN\"))\n        clear_pin_button.clicked.connect(clear_pin)\n        clear_pin_warning = QLabel(\n            _(\"If you disable your PIN, anyone with physical access to your \"\n              \"%s device can spend your bitcoins.\") % plugin.device)\n        clear_pin_warning.setWordWrap(True)\n        clear_pin_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(clear_pin_button, 0, 2)\n        advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)\n\n        # Advanced tab - toggle passphrase protection\n        passphrase_button = QPushButton()\n        passphrase_button.clicked.connect(toggle_passphrase)\n        passphrase_msg = WWLabel(PASSPHRASE_HELP)\n        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)\n        passphrase_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(passphrase_button, 3, 2)\n        advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)\n        advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)\n\n        # Advanced tab - wipe device\n        wipe_device_button = QPushButton(_(\"Wipe Device\"))\n        wipe_device_button.clicked.connect(wipe_device)\n        wipe_device_msg = QLabel(\n            _(\"Wipe the device, removing all data from it.  The firmware \"\n              \"is left unchanged.\"))\n        wipe_device_msg.setWordWrap(True)\n        wipe_device_warning = QLabel(\n            _(\"Only wipe a device if you have the recovery seed written down \"\n              \"and the device wallet(s) are empty, otherwise the bitcoins \"\n              \"will be lost forever.\"))\n        wipe_device_warning.setWordWrap(True)\n        wipe_device_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(wipe_device_button, 6, 2)\n        advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)\n        advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)\n        advanced_layout.addLayout(advanced_glayout)\n        advanced_layout.addStretch(1)\n\n        tabs = QTabWidget(self)\n        tabs.addTab(info_tab, _(\"Information\"))\n        tabs.addTab(settings_tab, _(\"Settings\"))\n        tabs.addTab(advanced_tab, _(\"Advanced\"))\n        dialog_vbox = QVBoxLayout(self)\n        dialog_vbox.addWidget(tabs)\n        dialog_vbox.addLayout(Buttons(CloseButton(self)))\n\n        # Update information\n        invoke_client(None)\n"
  },
  {
    "path": "plugins/labels/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = _('LabelSync')\ndescription = ' '.join([\n    _(\"Save your wallet labels on a remote server, and synchronize them across multiple devices where you use Electrum.\"),\n    _(\"Labels, transactions IDs and addresses are encrypted before they are sent to the remote server.\")\n])\navailable_for = ['qt', 'kivy']\n\n"
  },
  {
    "path": "plugins/labels/kivy.py",
    "content": "from .labels import LabelsPlugin\nfrom electrum.plugins import hook\n\nclass Plugin(LabelsPlugin):\n\n    @hook\n    def load_wallet(self, wallet, window):\n        self.window = window\n        self.start_wallet(wallet)\n\n    def on_pulled(self, wallet):\n        self.print_error('on pulled')\n        self.window._trigger_update_history()\n\n"
  },
  {
    "path": "plugins/labels/labels.py",
    "content": "import hashlib\nimport requests\nimport threading\nimport json\nimport sys\nimport traceback\n\nimport base64\n\nimport electrum\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.i18n import _\n\n\nclass LabelsPlugin(BasePlugin):\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        self.target_host = 'labels.bauerj.eu'\n        self.wallets = {}\n\n    def encode(self, wallet, msg):\n        password, iv, wallet_id = self.wallets[wallet]\n        encrypted = electrum.bitcoin.aes_encrypt_with_iv(password, iv,\n                                                         msg.encode('utf8'))\n        return base64.b64encode(encrypted).decode()\n\n    def decode(self, wallet, message):\n        password, iv, wallet_id = self.wallets[wallet]\n        decoded = base64.b64decode(message)\n        decrypted = electrum.bitcoin.aes_decrypt_with_iv(password, iv, decoded)\n        return decrypted.decode('utf8')\n\n    def get_nonce(self, wallet):\n        # nonce is the nonce to be used with the next change\n        nonce = wallet.storage.get('wallet_nonce')\n        if nonce is None:\n            nonce = 1\n            self.set_nonce(wallet, nonce)\n        return nonce\n\n    def set_nonce(self, wallet, nonce):\n        self.print_error(\"set\", wallet.basename(), \"nonce to\", nonce)\n        wallet.storage.put(\"wallet_nonce\", nonce)\n\n    @hook\n    def set_label(self, wallet, item, label):\n        if not wallet in self.wallets:\n            return\n        if not item:\n            return\n        nonce = self.get_nonce(wallet)\n        wallet_id = self.wallets[wallet][2]\n        bundle = {\"walletId\": wallet_id,\n                  \"walletNonce\": nonce,\n                  \"externalId\": self.encode(wallet, item),\n                  \"encryptedLabel\": self.encode(wallet, label)}\n        t = threading.Thread(target=self.do_request,\n                             args=[\"POST\", \"/label\", False, bundle])\n        t.setDaemon(True)\n        t.start()\n        # Caller will write the wallet\n        self.set_nonce(wallet, nonce + 1)\n\n    def do_request(self, method, url = \"/labels\", is_batch=False, data=None):\n        url = 'https://' + self.target_host + url\n        kwargs = {'headers': {}}\n        if method == 'GET' and data:\n            kwargs['params'] = data\n        elif method == 'POST' and data:\n            kwargs['data'] = json.dumps(data)\n            kwargs['headers']['Content-Type'] = 'application/json'\n        response = requests.request(method, url, **kwargs)\n        if response.status_code != 200:\n            raise BaseException(response.status_code, response.text)\n        response = response.json()\n        if \"error\" in response:\n            raise BaseException(response[\"error\"])\n        return response\n\n    def push_thread(self, wallet):\n        wallet_id = self.wallets[wallet][2]\n        bundle = {\"labels\": [],\n                  \"walletId\": wallet_id,\n                  \"walletNonce\": self.get_nonce(wallet)}\n        for key, value in wallet.labels.items():\n            try:\n                encoded_key = self.encode(wallet, key)\n                encoded_value = self.encode(wallet, value)\n            except:\n                self.print_error('cannot encode', repr(key), repr(value))\n                continue\n            bundle[\"labels\"].append({'encryptedLabel': encoded_value,\n                                     'externalId': encoded_key})\n        self.do_request(\"POST\", \"/labels\", True, bundle)\n\n    def pull_thread(self, wallet, force):\n        wallet_id = self.wallets[wallet][2]\n        nonce = 1 if force else self.get_nonce(wallet) - 1\n        self.print_error(\"asking for labels since nonce\", nonce)\n        try:\n            response = self.do_request(\"GET\", (\"/labels/since/%d/for/%s\" % (nonce, wallet_id) ))\n            if response[\"labels\"] is None:\n                self.print_error('no new labels')\n                return\n            result = {}\n            for label in response[\"labels\"]:\n                try:\n                    key = self.decode(wallet, label[\"externalId\"])\n                    value = self.decode(wallet, label[\"encryptedLabel\"])\n                except:\n                    continue\n                try:\n                    json.dumps(key)\n                    json.dumps(value)\n                except:\n                    self.print_error('error: no json', key)\n                    continue\n                result[key] = value\n\n            for key, value in result.items():\n                if force or not wallet.labels.get(key):\n                    wallet.labels[key] = value\n\n            self.print_error(\"received %d labels\" % len(response))\n            # do not write to disk because we're in a daemon thread\n            wallet.storage.put('labels', wallet.labels)\n            self.set_nonce(wallet, response[\"nonce\"] + 1)\n            self.on_pulled(wallet)\n\n        except Exception as e:\n            traceback.print_exc(file=sys.stderr)\n            self.print_error(\"could not retrieve labels\")\n\n    def start_wallet(self, wallet):\n        nonce = self.get_nonce(wallet)\n        self.print_error(\"wallet\", wallet.basename(), \"nonce is\", nonce)\n        mpk = wallet.get_fingerprint()\n        if not mpk:\n            return\n        mpk = mpk.encode('ascii')\n        password = hashlib.sha1(mpk).hexdigest()[:32].encode('ascii')\n        iv = hashlib.sha256(password).digest()[:16]\n        wallet_id = hashlib.sha256(mpk).hexdigest()\n        self.wallets[wallet] = (password, iv, wallet_id)\n        # If there is an auth token we can try to actually start syncing\n        t = threading.Thread(target=self.pull_thread, args=(wallet, False))\n        t.setDaemon(True)\n        t.start()\n\n    def stop_wallet(self, wallet):\n        self.wallets.pop(wallet, None)\n"
  },
  {
    "path": "plugins/labels/qt.py",
    "content": "from functools import partial\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\nfrom PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout)\n\nfrom electrum.plugins import hook\nfrom electrum.i18n import _\nfrom electrum_gui.qt import EnterButton\nfrom electrum_gui.qt.util import ThreadedButton, Buttons\nfrom electrum_gui.qt.util import WindowModalDialog, OkButton\n\nfrom .labels import LabelsPlugin\n\n\nclass QLabelsSignalObject(QObject):\n    labels_changed_signal = pyqtSignal(object)\n\n\nclass Plugin(LabelsPlugin):\n\n    def __init__(self, *args):\n        LabelsPlugin.__init__(self, *args)\n        self.obj = QLabelsSignalObject()\n\n    def requires_settings(self):\n        return True\n\n    def settings_widget(self, window):\n        return EnterButton(_('Settings'),\n                           partial(self.settings_dialog, window))\n\n    def settings_dialog(self, window):\n        wallet = window.parent().wallet\n        d = WindowModalDialog(window, _(\"Label Settings\"))\n        hbox = QHBoxLayout()\n        hbox.addWidget(QLabel(\"Label sync options:\"))\n        upload = ThreadedButton(\"Force upload\",\n                                partial(self.push_thread, wallet),\n                                partial(self.done_processing, d))\n        download = ThreadedButton(\"Force download\",\n                                  partial(self.pull_thread, wallet, True),\n                                  partial(self.done_processing, d))\n        vbox = QVBoxLayout()\n        vbox.addWidget(upload)\n        vbox.addWidget(download)\n        hbox.addLayout(vbox)\n        vbox = QVBoxLayout(d)\n        vbox.addLayout(hbox)\n        vbox.addSpacing(20)\n        vbox.addLayout(Buttons(OkButton(d)))\n        return bool(d.exec_())\n\n    def on_pulled(self, wallet):\n        self.obj.labels_changed_signal.emit(wallet)\n\n    def done_processing(self, dialog, result):\n        dialog.show_message(_(\"Your labels have been synchronised.\"))\n\n    @hook\n    def on_new_window(self, window):\n        self.obj.labels_changed_signal.connect(window.update_tabs)\n        self.start_wallet(window.wallet)\n\n    @hook\n    def on_close_window(self, window):\n        self.stop_wallet(window.wallet)\n"
  },
  {
    "path": "plugins/ledger/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'Ledger Wallet'\ndescription = 'Provides support for Ledger hardware wallet'\nrequires = [('btchip', 'github.com/ledgerhq/btchip-python')]\nregisters_keystore = ('hardware', 'ledger', _(\"Ledger wallet\"))\navailable_for = ['qt', 'cmdline']\n"
  },
  {
    "path": "plugins/ledger/auth2fa.py",
    "content": "from binascii import hexlify, unhexlify\n\nfrom PyQt5.Qt import QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel\nimport PyQt5.QtCore as QtCore\nfrom PyQt5.QtWidgets import *\n\nfrom electrum.i18n import _\nfrom electrum_gui.qt.util import *\nfrom electrum.util import print_msg\n\nimport os, hashlib, websocket, logging, json, copy\nfrom electrum_gui.qt.qrcodewidget import QRCodeWidget\nfrom btchip.btchip import *\n\nDEBUG = False\n\nhelpTxt = [_(\"Your Ledger Wallet wants to tell you a one-time PIN code.<br><br>\" \\\n            \"For best security you should unplug your device, open a text editor on another computer, \" \\\n            \"put your cursor into it, and plug your device into that computer. \" \\\n            \"It will output a summary of the transaction being signed and a one-time PIN.<br><br>\" \\\n            \"Verify the transaction summary and type the PIN code here.<br><br>\" \\\n            \"Before pressing enter, plug the device back into this computer.<br>\" ),\n        _(\"Verify the address below.<br>Type the character from your security card corresponding to the <u><b>BOLD</b></u> character.\"),\n        _(\"Waiting for authentication on your mobile phone\"),\n        _(\"Transaction accepted by mobile phone. Waiting for confirmation.\"),\n        _(\"Click Pair button to begin pairing a mobile phone.\"),\n        _(\"Scan this QR code with your LedgerWallet phone app to pair it with this Ledger device.<br>\" \n            \"To complete pairing you will need your security card to answer a challenge.\" )\n        ]\n\nclass LedgerAuthDialog(QDialog):\n    def __init__(self, handler, data):\n        '''Ask user for 2nd factor authentication. Support text, security card and paired mobile methods.\n        Use last method from settings, but support new pairing and downgrade.\n        '''\n        QDialog.__init__(self, handler.top_level_window())\n        self.handler = handler\n        self.txdata = data\n        self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''\n        self.setMinimumWidth(600)\n        self.setWindowTitle(_(\"Ledger Wallet Authentication\"))\n        self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)\n        self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle\n        self.ws = None\n        self.pin = ''\n        \n        self.devmode = self.getDevice2FAMode()\n        if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:\n            self.cfg['mode'] = 0\n        \n        vbox = QVBoxLayout()\n        self.setLayout(vbox)\n        \n        def on_change_mode(idx):\n            if idx < 2 and self.ws:\n                self.ws.stop()\n                self.ws = None\n            self.cfg['mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1\n            if self.cfg['mode'] > 1 and self.cfg['pair'] and not self.ws:\n                self.req_validation()\n            if self.cfg['mode'] > 0:\n                self.handler.win.wallet.get_keystore().cfg = self.cfg\n                self.handler.win.wallet.save_keystore()\n            self.update_dlg()\n        def add_pairing():\n            self.do_pairing()\n        def return_pin():\n            self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text() \n            if self.cfg['mode'] == 1:\n                self.pin = ''.join(chr(int(str(i),16)) for i in self.pin)\n            self.accept()\n        \n        self.modebox = QWidget()\n        modelayout = QHBoxLayout()\n        self.modebox.setLayout(modelayout)\n        modelayout.addWidget(QLabel(_(\"Method:\")))\n        self.modes = QComboBox()\n        modelayout.addWidget(self.modes, 2)\n        self.addPair = QPushButton(_(\"Pair\"))\n        self.addPair.setMaximumWidth(60)\n        modelayout.addWidget(self.addPair)\n        modelayout.addStretch(1)\n        self.modebox.setMaximumHeight(50)\n        vbox.addWidget(self.modebox)\n        \n        self.populate_modes()\n        self.modes.currentIndexChanged.connect(on_change_mode)\n        self.addPair.clicked.connect(add_pairing)\n        \n        self.helpmsg = QTextEdit()\n        self.helpmsg.setStyleSheet(\"QTextEdit { background-color: lightgray; }\")\n        self.helpmsg.setReadOnly(True)\n        vbox.addWidget(self.helpmsg)\n        \n        self.pinbox = QWidget()\n        pinlayout = QHBoxLayout()\n        self.pinbox.setLayout(pinlayout)\n        self.pintxt = QLineEdit()\n        self.pintxt.setEchoMode(2)\n        self.pintxt.setMaxLength(4)\n        self.pintxt.returnPressed.connect(return_pin)\n        pinlayout.addWidget(QLabel(_(\"Enter PIN:\")))\n        pinlayout.addWidget(self.pintxt)\n        pinlayout.addWidget(QLabel(_(\"NOT DEVICE PIN - see above\")))\n        pinlayout.addStretch(1)\n        self.pinbox.setVisible(self.cfg['mode'] == 0)\n        vbox.addWidget(self.pinbox)\n                    \n        self.cardbox = QWidget()\n        card = QVBoxLayout()\n        self.cardbox.setLayout(card)\n        self.addrtext = QTextEdit()\n        self.addrtext.setStyleSheet(\"QTextEdit { color:blue; background-color:lightgray; padding:15px 10px; border:none; font-size:20pt; }\")\n        self.addrtext.setReadOnly(True)\n        self.addrtext.setMaximumHeight(120)\n        card.addWidget(self.addrtext)\n        \n        def pin_changed(s):\n            if len(s) < len(self.idxs):\n                i = self.idxs[len(s)]\n                addr = self.txdata['address']\n                addr = addr[:i] + '<u><b>' + addr[i:i+1] + '</u></b>' + addr[i+1:]\n                self.addrtext.setHtml(str(addr))\n            else:\n                self.addrtext.setHtml(_(\"Press Enter\"))\n                \n        pin_changed('')    \n        cardpin = QHBoxLayout()\n        cardpin.addWidget(QLabel(_(\"Enter PIN:\")))\n        self.cardtxt = QLineEdit()\n        self.cardtxt.setEchoMode(2)\n        self.cardtxt.setMaxLength(len(self.idxs))\n        self.cardtxt.textChanged.connect(pin_changed)\n        self.cardtxt.returnPressed.connect(return_pin)\n        cardpin.addWidget(self.cardtxt)\n        cardpin.addWidget(QLabel(_(\"NOT DEVICE PIN - see above\")))\n        cardpin.addStretch(1)\n        card.addLayout(cardpin)\n        self.cardbox.setVisible(self.cfg['mode'] == 1)\n        vbox.addWidget(self.cardbox)\n        \n        self.pairbox = QWidget()\n        pairlayout = QVBoxLayout()\n        self.pairbox.setLayout(pairlayout)\n        pairhelp = QTextEdit(helpTxt[5])\n        pairhelp.setStyleSheet(\"QTextEdit { background-color: lightgray; }\")\n        pairhelp.setReadOnly(True)\n        pairlayout.addWidget(pairhelp, 1)\n        self.pairqr = QRCodeWidget()\n        pairlayout.addWidget(self.pairqr, 4)\n        self.pairbox.setVisible(False)\n        vbox.addWidget(self.pairbox)\n        self.update_dlg()\n        \n        if self.cfg['mode'] > 1 and not self.ws:\n            self.req_validation()\n        \n    def populate_modes(self):\n        self.modes.blockSignals(True)\n        self.modes.clear()\n        self.modes.addItem(_(\"Summary Text PIN (requires dongle replugging)\") if self.txdata['confirmationType'] == 1 else _(\"Summary Text PIN is Disabled\"))\n        if self.txdata['confirmationType'] > 1:\n            self.modes.addItem(_(\"Security Card Challenge\"))\n            if not self.cfg['pair']:\n                self.modes.addItem(_(\"Mobile - Not paired\")) \n            else:\n                self.modes.addItem(_(\"Mobile - %s\") % self.cfg['pair'][1]) \n        self.modes.blockSignals(False)\n        \n    def update_dlg(self):\n        self.modes.setCurrentIndex(self.cfg['mode'])\n        self.modebox.setVisible(True)\n        self.addPair.setText(_(\"Pair\") if not self.cfg['pair'] else _(\"Re-Pair\"))\n        self.addPair.setVisible(self.txdata['confirmationType'] > 2)\n        self.helpmsg.setText(helpTxt[self.cfg['mode'] if self.cfg['mode'] < 2 else 2 if self.cfg['pair'] else 4])\n        self.helpmsg.setMinimumHeight(180 if self.txdata['confirmationType'] == 1 else 100)\n        self.pairbox.setVisible(False)\n        self.helpmsg.setVisible(True)\n        self.pinbox.setVisible(self.cfg['mode'] == 0)\n        self.cardbox.setVisible(self.cfg['mode'] == 1)\n        self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)\n        self.setMaximumHeight(200)\n        \n    def do_pairing(self):\n        rng = os.urandom(16)\n        pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8')\n        self.pairqr.setData(pairID)\n        self.modebox.setVisible(False)\n        self.helpmsg.setVisible(False)\n        self.pinbox.setVisible(False)\n        self.cardbox.setVisible(False)\n        self.pairbox.setVisible(True)\n        self.pairqr.setMinimumSize(300,300)\n        if self.ws:\n            self.ws.stop()\n        self.ws = LedgerWebSocket(self, pairID)\n        self.ws.pairing_done.connect(self.pairing_done)\n        self.ws.start() \n               \n    def pairing_done(self, data):\n        if data is not None:\n            self.cfg['pair'] = [ data['pairid'], data['name'], data['platform'] ]\n            self.cfg['mode'] = 2\n            self.handler.win.wallet.get_keystore().cfg = self.cfg\n            self.handler.win.wallet.save_keystore()\n        self.pin = 'paired'\n        self.accept()\n    \n    def req_validation(self):\n        if self.cfg['pair'] and 'secureScreenData' in self.txdata:\n            if self.ws:\n                self.ws.stop()\n            self.ws = LedgerWebSocket(self, self.cfg['pair'][0], self.txdata)\n            self.ws.req_updated.connect(self.req_updated)\n            self.ws.start()\n              \n    def req_updated(self, pin):\n        if pin == 'accepted':\n            self.helpmsg.setText(helpTxt[3])\n        else:\n            self.pin = str(pin)\n            self.accept()\n        \n    def getDevice2FAMode(self):\n        apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode\n        try:\n            mode = self.dongle.exchange( bytearray(apdu) )\n            return mode\n        except BTChipException as e:\n            debug_msg('Device getMode Failed')\n        return 0x11\n    \n    def closeEvent(self, evnt):\n        debug_msg(\"CLOSE - Stop WS\")\n        if self.ws:\n            self.ws.stop()\n        if self.pairbox.isVisible():\n            evnt.ignore()\n            self.update_dlg()\n\nclass LedgerWebSocket(QThread):\n    pairing_done = pyqtSignal(object)\n    req_updated = pyqtSignal(str)\n    \n    def __init__(self, dlg, pairID, txdata=None):\n        QThread.__init__(self)\n        self.stopping = False\n        self.pairID = pairID\n        self.txreq = '{\"type\":\"request\",\"second_factor_data\":\"' + hexlify(txdata['secureScreenData']).decode('utf-8') + '\"}' if txdata else None\n        self.dlg = dlg\n        self.dongle = self.dlg.dongle\n        self.data = None\n             \n        #websocket.enableTrace(True)\n        logging.basicConfig(level=logging.INFO)\n        self.ws = websocket.WebSocketApp('wss://ws.ledgerwallet.com/2fa/channels', \n                                on_message = self.on_message, on_error = self.on_error,\n                                on_close = self.on_close, on_open = self.on_open)\n                                \n    def run(self):\n        while not self.stopping:\n            self.ws.run_forever()\n    def stop(self):\n        debug_msg(\"WS: Stopping\")\n        self.stopping = True\n        self.ws.close()\n\n    def on_message(self, ws, msg):\n        data = json.loads(msg)\n        if data['type'] == 'identify':\n            debug_msg('Identify')\n            apdu = [0xe0, 0x12, 0x01, 0x00, 0x41] # init pairing\n            apdu.extend(unhexlify(data['public_key']))\n            try:\n                challenge = self.dongle.exchange( bytearray(apdu) )\n                ws.send( '{\"type\":\"challenge\",\"data\":\"%s\" }' % hexlify(challenge).decode('utf-8') )\n                self.data = data\n            except BTChipException as e:\n                debug_msg('Identify Failed')\n                \n        if data['type'] == 'challenge':\n            debug_msg('Challenge')\n            apdu = [0xe0, 0x12, 0x02, 0x00, 0x10] # confirm pairing\n            apdu.extend(unhexlify(data['data']))\n            try:\n                self.dongle.exchange( bytearray(apdu) )\n                debug_msg('Pairing Successful')\n                ws.send( '{\"type\":\"pairing\",\"is_successful\":\"true\"}' )\n                self.data['pairid'] = self.pairID\n                self.pairing_done.emit(self.data)\n            except BTChipException as e:\n                debug_msg('Pairing Failed')\n                ws.send( '{\"type\":\"pairing\",\"is_successful\":\"false\"}' ) \n                self.pairing_done.emit(None)\n            ws.send( '{\"type\":\"disconnect\"}' )\n            self.stopping = True\n            ws.close()\n        \n        if data['type'] == 'accept':\n            debug_msg('Accepted')\n            self.req_updated.emit('accepted')\n        if data['type'] == 'response':\n            debug_msg('Responded', data)\n            self.req_updated.emit(str(data['pin']) if data['is_accepted'] else '')\n            self.txreq = None\n            self.stopping = True\n            ws.close()\n            \n        if data['type'] == 'repeat':\n            debug_msg('Repeat')\n            if self.txreq:\n                ws.send( self.txreq )\n                debug_msg(\"Req Sent\", self.txreq)\n        if data['type'] == 'connect':\n            debug_msg('Connected')\n            if self.txreq:\n                ws.send( self.txreq )\n                debug_msg(\"Req Sent\", self.txreq)\n        if data['type'] == 'disconnect':\n            debug_msg('Disconnected')\n            ws.close()\n            \n    def on_error(self, ws, error):\n        message = getattr(error, 'strerror', '')\n        if not message:\n            message = getattr(error, 'message', '')\n        debug_msg(\"WS: %s\" % message)\n    \n    def on_close(self, ws):\n        debug_msg(\"WS: ### socket closed ###\")\n    \n    def on_open(self, ws):\n        debug_msg(\"WS: ### socket open ###\")\n        debug_msg(\"Joining with pairing ID\", self.pairID)\n        ws.send( '{\"type\":\"join\",\"room\":\"%s\"}' % self.pairID )\n        ws.send( '{\"type\":\"repeat\"}' )\n        if self.txreq:\n            ws.send( self.txreq )\n            debug_msg(\"Req Sent\", self.txreq)\n\ndef debug_msg(*args):\n    if DEBUG:\n        print_msg(*args)        \n\n    \n        \n        \n        \n"
  },
  {
    "path": "plugins/ledger/cmdline.py",
    "content": "from electrum.plugins import hook\nfrom .ledger import LedgerPlugin\nfrom ..hw_wallet import CmdLineHandler\n\nclass Plugin(LedgerPlugin):\n    handler = CmdLineHandler()\n    @hook\n    def init_keystore(self, keystore):\n        if not isinstance(keystore, self.keystore_class):\n            return\n        keystore.handler = self.handler\n"
  },
  {
    "path": "plugins/ledger/ledger.py",
    "content": "from struct import pack, unpack\nimport hashlib\nimport sys\nimport traceback\n\nfrom electrum import bitcoin\nfrom electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int\nfrom electrum.i18n import _\nfrom electrum.plugins import BasePlugin\nfrom electrum.keystore import Hardware_KeyStore\nfrom electrum.transaction import Transaction\nfrom ..hw_wallet import HW_PluginBase\nfrom electrum.util import print_error, is_verbose, bfh, bh2u\n\ntry:\n    import hid\n    from btchip.btchipComm import HIDDongleHIDAPI, DongleWait\n    from btchip.btchip import btchip\n    from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script\n    from btchip.bitcoinTransaction import bitcoinTransaction\n    from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware\n    from btchip.btchipException import BTChipException\n    BTCHIP = True\n    BTCHIP_DEBUG = is_verbose\nexcept ImportError:\n    BTCHIP = False\n\nMSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \\\n                      ' https://www.ledgerwallet.com'\nMSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or \"Bitcoin\" app) too old for Segwit support. Please update at') + \\\n                      ' https://www.ledgerwallet.com'\nMULTI_OUTPUT_SUPPORT = '1.1.4'\nSEGWIT_SUPPORT = '1.1.10'\nSEGWIT_SUPPORT_SPECIAL = '1.0.4'\n\n\nclass Ledger_Client():\n    def __init__(self, hidDevice):\n        self.dongleObject = btchip(hidDevice)\n        self.preflightDone = False\n\n    def is_pairable(self):\n        return True\n\n    def close(self):\n        self.dongleObject.dongle.close()\n\n    def timeout(self, cutoff):\n        pass\n\n    def is_initialized(self):\n        return True\n\n    def label(self):\n        return \"\"\n\n    def i4b(self, x):\n        return pack('>I', x)\n\n    def versiontuple(self, v):\n        return tuple(map(int, (v.split(\".\"))))\n\n    def test_pin_unlocked(func):\n        \"\"\"Function decorator to test the Ledger for being unlocked, and if not,\n        raise a human-readable exception.\n        \"\"\"\n        def catch_exception(self, *args, **kwargs):\n            try:\n                return func(self, *args, **kwargs)\n            except BTChipException as e:\n                if e.sw == 0x6982:\n                    raise Exception(_('Your Ledger is locked. Please unlock it.'))\n                else:\n                    raise\n        return catch_exception\n\n    @test_pin_unlocked\n    def get_xpub(self, bip32_path, xtype):\n        self.checkDevice()\n        # bip32_path is of the form 44'/0'/1'\n        # S-L-O-W - we don't handle the fingerprint directly, so compute\n        # it manually from the previous node\n        # This only happens once so it's bearable\n        #self.get_client() # prompt for the PIN before displaying the dialog if necessary\n        #self.handler.show_message(\"Computing master public key\")\n        if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():\n            raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)\n        if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():\n            raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)\n        splitPath = bip32_path.split('/')\n        if splitPath[0] == 'm':\n            splitPath = splitPath[1:]\n            bip32_path = bip32_path[2:]\n        fingerprint = 0\n        if len(splitPath) > 1:\n            prevPath = \"/\".join(splitPath[0:len(splitPath) - 1])\n            nodeData = self.dongleObject.getWalletPublicKey(prevPath)\n            publicKey = compress_public_key(nodeData['publicKey'])\n            h = hashlib.new('ripemd160')\n            h.update(hashlib.sha256(publicKey).digest())\n            fingerprint = unpack(\">I\", h.digest()[0:4])[0]\n        nodeData = self.dongleObject.getWalletPublicKey(bip32_path)\n        publicKey = compress_public_key(nodeData['publicKey'])\n        depth = len(splitPath)\n        lastChild = splitPath[len(splitPath) - 1].split('\\'')\n        childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0])\n        xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum))\n        return xpub\n\n    def has_detached_pin_support(self, client):\n        try:\n            client.getVerifyPinRemainingAttempts()\n            return True\n        except BTChipException as e:\n            if e.sw == 0x6d00:\n                return False\n            raise e\n\n    def is_pin_validated(self, client):\n        try:\n            # Invalid SET OPERATION MODE to verify the PIN status\n            client.dongle.exchange(bytearray([0xe0, 0x26, 0x00, 0x00, 0x01, 0xAB]))\n        except BTChipException as e:\n            if (e.sw == 0x6982):\n                return False\n            if (e.sw == 0x6A80):\n                return True\n            raise e\n\n    def supports_multi_output(self):\n        return self.multiOutputSupported\n\n    def supports_segwit(self):\n        return self.segwitSupported\n\n    def supports_native_segwit(self):\n        return self.nativeSegwitSupported\n\n    def perform_hw1_preflight(self):\n        try:\n            firmwareInfo = self.dongleObject.getFirmwareVersion()\n            firmware = firmwareInfo['version']\n            self.multiOutputSupported = self.versiontuple(firmware) >= self.versiontuple(MULTI_OUTPUT_SUPPORT)\n            self.nativeSegwitSupported = self.versiontuple(firmware) >= self.versiontuple(SEGWIT_SUPPORT)\n            self.segwitSupported = self.nativeSegwitSupported or (firmwareInfo['specialVersion'] == 0x20 and self.versiontuple(firmware) >= self.versiontuple(SEGWIT_SUPPORT_SPECIAL))\n\n            if not checkFirmware(firmwareInfo):\n                self.dongleObject.dongle.close()\n                raise Exception(MSG_NEEDS_FW_UPDATE_GENERIC)\n            try:\n                self.dongleObject.getOperationMode()\n            except BTChipException as e:\n                if (e.sw == 0x6985):\n                    self.dongleObject.dongle.close()\n                    self.handler.get_setup( )\n                    # Acquire the new client on the next run\n                else:\n                    raise e\n            if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject) and (self.handler is not None):\n                remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts()\n                if remaining_attempts != 1:\n                    msg = \"Enter your Ledger PIN - remaining attempts : \" + str(remaining_attempts)\n                else:\n                    msg = \"Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped.\"\n                confirmed, p, pin = self.password_dialog(msg)\n                if not confirmed:\n                    raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')\n                pin = pin.encode()\n                self.dongleObject.verifyPin(pin)\n        except BTChipException as e:\n            if (e.sw == 0x6faa):\n                raise Exception(\"Dongle is temporarily locked - please unplug it and replug it again\")\n            if ((e.sw & 0xFFF0) == 0x63c0):\n                raise Exception(\"Invalid PIN - please unplug the dongle and plug it again before retrying\")\n            raise e\n\n    def checkDevice(self):\n        if not self.preflightDone:\n            try:\n                self.perform_hw1_preflight()\n            except BTChipException as e:\n                if (e.sw == 0x6d00):\n                    raise BaseException(\"Device not in BTCP mode\")\n                raise e\n            self.preflightDone = True\n\n    def password_dialog(self, msg=None):\n        response = self.handler.get_word(msg)\n        if response is None:\n            return False, None, None\n        return True, response, response\n\n\nclass Ledger_KeyStore(Hardware_KeyStore):\n    hw_type = 'ledger'\n    device = 'Ledger'\n\n    def __init__(self, d):\n        Hardware_KeyStore.__init__(self, d)\n        # Errors and other user interaction is done through the wallet's\n        # handler.  The handler is per-window and preserved across\n        # device reconnects\n        self.force_watching_only = False\n        self.signing = False\n        self.cfg = d.get('cfg', {'mode':0,'pair':''})\n\n    def dump(self):\n        obj = Hardware_KeyStore.dump(self)\n        obj['cfg'] = self.cfg\n        return obj\n\n    def get_derivation(self):\n        return self.derivation\n\n    def get_client(self):\n        return self.plugin.get_client(self).dongleObject\n\n    def get_client_electrum(self):\n        return self.plugin.get_client(self)\n\n    def give_error(self, message, clear_client = False):\n        print_error(message)\n        if not self.signing:\n            self.handler.show_error(message)\n        else:\n            self.signing = False\n        if clear_client:\n            self.client = None\n        raise Exception(message)\n\n    def address_id_stripped(self, address):\n        # Strip the leading \"m/\"\n        change, index = self.get_address_index(address)\n        derivation = self.derivation\n        address_path = \"%s/%d/%d\"%(derivation, change, index)\n        return address_path[2:]\n\n    def decrypt_message(self, pubkey, message, password):\n        raise RuntimeError(_('Encryption and decryption are currently not supported for %s') % self.device)\n\n    def sign_message(self, sequence, message, password):\n        self.signing = True\n        message = message.encode('utf8')\n        message_hash = hashlib.sha256(message).hexdigest().upper()\n        # prompt for the PIN before displaying the dialog if necessary\n        client = self.get_client()\n        address_path = self.get_derivation()[2:] + \"/%d/%d\"%sequence\n        self.handler.show_message(\"Signing message ...\\r\\nMessage hash: \"+message_hash)\n        try:\n            info = self.get_client().signMessagePrepare(address_path, message)\n            pin = \"\"\n            if info['confirmationNeeded']:\n                pin = self.handler.get_auth( info ) # does the authenticate dialog and returns pin\n                if not pin:\n                    raise UserWarning(_('Cancelled by user'))\n                pin = str(pin).encode()\n            signature = self.get_client().signMessageSign(pin)\n        except BTChipException as e:\n            if e.sw == 0x6a80:\n                self.give_error(\"Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.\")\n            else:\n                self.give_error(e, True)\n        except UserWarning:\n            self.handler.show_error(_('Cancelled by user'))\n            return ''\n        except Exception as e:\n            self.give_error(e, True)\n        finally:\n            self.handler.finished()\n        self.signing = False\n        # Parse the ASN.1 signature\n        rLength = signature[3]\n        r = signature[4 : 4 + rLength]\n        sLength = signature[4 + rLength + 1]\n        s = signature[4 + rLength + 2:]\n        if rLength == 33:\n            r = r[1:]\n        if sLength == 33:\n            s = s[1:]\n        # And convert it\n        return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s\n\n\n    def sign_transaction(self, tx, password):\n        if tx.is_complete():\n            return\n        client = self.get_client()\n        self.signing = True\n        inputs = []\n        inputsPaths = []\n        pubKeys = []\n        chipInputs = []\n        redeemScripts = []\n        signatures = []\n        preparedTrustedInputs = []\n        changePath = \"\"\n        changeAmount = None\n        output = None\n        outputAmount = None\n        p2shTransaction = False\n        segwitTransaction = False\n        pin = \"\"\n        self.get_client() # prompt for the PIN before displaying the dialog if necessary\n\n        # Fetch inputs of the transaction to sign\n        derivations = self.get_tx_derivations(tx)\n        for txin in tx.inputs():\n            if txin['type'] == 'coinbase':\n                self.give_error(\"Coinbase not supported\")     # should never happen\n\n            if txin['type'] in ['p2sh']:\n                p2shTransaction = True\n\n            if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:\n                if not self.get_client_electrum().supports_segwit():\n                    self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)\n                segwitTransaction = True\n\n            if txin['type'] in ['p2wpkh', 'p2wsh']:\n                if not self.get_client_electrum().supports_native_segwit():\n                    self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)\n                segwitTransaction = True\n\n            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)\n            for i, x_pubkey in enumerate(x_pubkeys):\n                if x_pubkey in derivations:\n                    signingPos = i\n                    s = derivations.get(x_pubkey)\n                    hwAddress = \"%s/%d/%d\" % (self.get_derivation()[2:], s[0], s[1])\n                    break\n            else:\n                self.give_error(\"No matching x_key for sign_transaction\") # should never happen\n\n            redeemScript = Transaction.get_preimage_script(txin)\n            inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1) ])\n            inputsPaths.append(hwAddress)\n            pubKeys.append(pubkeys)\n\n        # Sanity check\n        if p2shTransaction:\n            for txin in tx.inputs():\n                if txin['type'] != 'p2sh':\n                    self.give_error(\"P2SH / regular input mixed in same transaction not supported\") # should never happen\n\n        txOutput = var_int(len(tx.outputs()))\n        for txout in tx.outputs():\n            output_type, addr, amount = txout\n            txOutput += int_to_hex(amount, 8)\n            script = tx.pay_script(output_type, addr)\n            txOutput += var_int(len(script)//2)\n            txOutput += script\n        txOutput = bfh(txOutput)\n\n        # Recognize outputs - only one output and one change is authorized\n        if not p2shTransaction:\n            if not self.get_client_electrum().supports_multi_output():\n                if len(tx.outputs()) > 2:\n                    self.give_error(\"Transaction with more than 2 outputs not supported\")\n            for _type, address, amount in tx.outputs():\n                assert _type == TYPE_ADDRESS\n                info = tx.output_info.get(address)\n                if (info is not None) and (len(tx.outputs()) != 1):\n                    index, xpubs, m = info\n                    changePath = self.get_derivation()[2:] + \"/%d/%d\"%index\n                    changeAmount = amount\n                else:\n                    output = address\n                    outputAmount = amount\n\n        self.handler.show_message(_(\"Confirm Transaction on your Ledger device...\"))\n        try:\n            # Get trusted inputs from the original transactions\n            for utxo in inputs:\n                sequence = int_to_hex(utxo[5], 4)\n                if segwitTransaction:\n                    txtmp = bitcoinTransaction(bfh(utxo[0]))\n                    tmp = bfh(utxo[3])[::-1]\n                    tmp += bfh(int_to_hex(utxo[1], 4))\n                    tmp += txtmp.outputs[utxo[1]].amount\n                    chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence})\n                    redeemScripts.append(bfh(utxo[2]))\n                elif not p2shTransaction:\n                    txtmp = bitcoinTransaction(bfh(utxo[0]))\n                    trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1])\n                    trustedInput['sequence'] = sequence\n                    chipInputs.append(trustedInput)\n                    redeemScripts.append(txtmp.outputs[utxo[1]].script)\n                else:\n                    tmp = bfh(utxo[3])[::-1]\n                    tmp += bfh(int_to_hex(utxo[1], 4))\n                    chipInputs.append({'value' : tmp, 'sequence' : sequence})\n                    redeemScripts.append(bfh(utxo[2]))\n\n            # Sign all inputs\n            firstTransaction = True\n            inputIndex = 0\n            rawTx = tx.serialize()\n            self.get_client().enableAlternate2fa(False)\n            if segwitTransaction:\n                self.get_client().startUntrustedTransaction(True, inputIndex,\n                                                            chipInputs, redeemScripts[inputIndex])\n                outputData = self.get_client().finalizeInputFull(txOutput)\n                outputData['outputData'] = txOutput\n                transactionOutput = outputData['outputData']\n                if outputData['confirmationNeeded']:\n                    outputData['address'] = output\n                    self.handler.finished()\n                    pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin\n                    if not pin:\n                        raise UserWarning()\n                    if pin != 'paired':\n                        self.handler.show_message(_(\"Confirmed. Signing Transaction...\"))\n                while inputIndex < len(inputs):\n                    singleInput = [ chipInputs[inputIndex] ]\n                    self.get_client().startUntrustedTransaction(False, 0,\n                                                            singleInput, redeemScripts[inputIndex])\n                    inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)\n                    inputSignature[0] = 0x30 # force for 1.4.9+\n                    signatures.append(inputSignature)\n                    inputIndex = inputIndex + 1\n            else:\n                while inputIndex < len(inputs):\n                    self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,\n                                                            chipInputs, redeemScripts[inputIndex])\n                    outputData = self.get_client().finalizeInputFull(txOutput)\n                    outputData['outputData'] = txOutput\n                    if firstTransaction:\n                        transactionOutput = outputData['outputData']\n                    if outputData['confirmationNeeded']:\n                        outputData['address'] = output\n                        self.handler.finished()\n                        pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin\n                        if not pin:\n                            raise UserWarning()\n                        if pin != 'paired':\n                            self.handler.show_message(_(\"Confirmed. Signing Transaction...\"))\n                    else:\n                        # Sign input with the provided PIN\n                        inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)\n                        inputSignature[0] = 0x30 # force for 1.4.9+\n                        signatures.append(inputSignature)\n                        inputIndex = inputIndex + 1\n                    if pin != 'paired':\n                        firstTransaction = False\n        except UserWarning:\n            self.handler.show_error(_('Cancelled by user'))\n            return\n        except BaseException as e:\n            traceback.print_exc(file=sys.stdout)\n            self.give_error(e, True)\n        finally:\n            self.handler.finished()\n\n        for i, txin in enumerate(tx.inputs()):\n            signingPos = inputs[i][4]\n            txin['signatures'][signingPos] = bh2u(signatures[i])\n        tx.raw = tx.serialize()\n        self.signing = False\n\n    def show_address(self, sequence, txin_type):\n        self.signing = True\n        client = self.get_client()\n        address_path = self.get_derivation()[2:] + \"/%d/%d\"%sequence\n        self.handler.show_message(_(\"Showing address ...\"))\n        segwit = Transaction.is_segwit_inputtype(txin_type)\n        try:\n            client.getWalletPublicKey(address_path, showOnScreen=True, segwit=segwit)\n        except BTChipException as e:\n            if e.sw == 0x6985:  # cancelled by user\n                pass\n            else:\n                traceback.print_exc(file=sys.stderr)\n                self.handler.show_error(e)\n        except BaseException as e:\n            traceback.print_exc(file=sys.stderr)\n            self.handler.show_error(e)\n        finally:\n            self.handler.finished()\n        self.signing = False\n\nclass LedgerPlugin(HW_PluginBase):\n    libraries_available = BTCHIP\n    keystore_class = Ledger_KeyStore\n    client = None\n    DEVICE_IDS = [\n                   (0x2581, 0x1807), # HW.1 legacy btchip\n                   (0x2581, 0x2b7c), # HW.1 transitional production\n                   (0x2581, 0x3b7c), # HW.1 ledger production\n                   (0x2581, 0x4b7c), # HW.1 ledger test\n                   (0x2c97, 0x0000), # Blue\n                   (0x2c97, 0x0001)  # Nano-S\n                   (0x2c97, 0x0004), # Nano-X\n                   (0x2c97, 0x0005), # RFU\n                   (0x2c97, 0x0006), # RFU\n                   (0x2c97, 0x0007), # RFU\n                   (0x2c97, 0x0008), # RFU\n                   (0x2c97, 0x0009), # RFU\n                   (0x2c97, 0x000a)  # RFU\n                 ]\n\n    def __init__(self, parent, config, name):\n        self.segwit = config.get(\"segwit\")\n        HW_PluginBase.__init__(self, parent, config, name)\n        if self.libraries_available:\n            self.device_manager().register_devices(self.DEVICE_IDS)\n\n    def btchip_is_connected(self, keystore):\n        try:\n            self.get_client(keystore).getFirmwareVersion()\n        except Exception as e:\n            return False\n        return True\n\n    def get_btchip_device(self, device):\n        ledger = False\n        if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97):\n           ledger = True\n        dev = hid.device()\n        dev.open_path(device.path)\n        dev.set_nonblocking(True)\n        return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)\n\n    def create_client(self, device, handler):\n        self.handler = handler\n\n        client = self.get_btchip_device(device)\n        if client is not None:\n            client = Ledger_Client(client)\n        return client\n\n    def setup_device(self, device_info, wizard):\n        devmgr = self.device_manager()\n        device_id = device_info.device.id_\n        client = devmgr.client_by_id(device_id)\n        client.handler = self.create_handler(wizard)\n        client.get_xpub(\"m/44'/0'\", 'standard') # TODO replace by direct derivation once Nano S > 1.1\n\n    def get_xpub(self, device_id, derivation, xtype, wizard):\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n        client.handler = self.create_handler(wizard)\n        client.checkDevice()\n        xpub = client.get_xpub(derivation, xtype)\n        return xpub\n\n    def get_client(self, keystore, force_pair=True):\n        # All client interaction should not be in the main GUI thread\n        #assert self.main_thread != threading.current_thread()\n        devmgr = self.device_manager()\n        handler = keystore.handler\n        with devmgr.hid_lock:\n            client = devmgr.client_for_keystore(self, handler, keystore, force_pair)\n        # returns the client for a given keystore. can use xpub\n        #if client:\n        #    client.used()\n        if client is not None:\n            client.checkDevice()\n        return client\n\n    def show_address(self, wallet, address):\n        sequence = wallet.get_address_index(address)\n        txin_type = wallet.get_txin_type(address)\n        wallet.get_keystore().show_address(sequence, txin_type)\n"
  },
  {
    "path": "plugins/ledger/qt.py",
    "content": "import threading\n\nfrom PyQt5.Qt import QInputDialog, QLineEdit, QVBoxLayout, QLabel\n\nfrom electrum.i18n import _\nfrom electrum.plugins import hook\nfrom electrum.wallet import Standard_Wallet\nfrom .ledger import LedgerPlugin\nfrom ..hw_wallet.qt import QtHandlerBase, QtPluginBase\nfrom electrum_gui.qt.util import *\n\n#from btchip.btchipPersoWizard import StartBTChipPersoDialog\n\nclass Plugin(LedgerPlugin, QtPluginBase):\n    icon_unpaired = \":icons/ledger_unpaired.png\"\n    icon_paired = \":icons/ledger.png\"\n\n    def create_handler(self, window):\n        return Ledger_Handler(window)\n\n    @hook\n    def receive_menu(self, menu, addrs, wallet):\n        if type(wallet) is not Standard_Wallet:\n            return\n        keystore = wallet.get_keystore()\n        if type(keystore) == self.keystore_class and len(addrs) == 1:\n            def show_address():\n                keystore.thread.add(partial(self.show_address, wallet, addrs[0]))\n            menu.addAction(_(\"Show on Ledger\"), show_address)\n\nclass Ledger_Handler(QtHandlerBase):\n    setup_signal = pyqtSignal()\n    auth_signal = pyqtSignal(object)\n\n    def __init__(self, win):\n        super(Ledger_Handler, self).__init__(win, 'Ledger')\n        self.setup_signal.connect(self.setup_dialog)\n        self.auth_signal.connect(self.auth_dialog)\n\n    def word_dialog(self, msg):\n        response = QInputDialog.getText(self.top_level_window(), \"Ledger Wallet Authentication\", msg, QLineEdit.Password)\n        if not response[1]:\n            self.word = None\n        else:\n            self.word = str(response[0])\n        self.done.set()\n    \n    def message_dialog(self, msg):\n        self.clear_dialog()\n        self.dialog = dialog = WindowModalDialog(self.top_level_window(), _(\"Ledger Status\"))\n        l = QLabel(msg)\n        vbox = QVBoxLayout(dialog)\n        vbox.addWidget(l)\n        dialog.show()\n\n    def auth_dialog(self, data):\n        try:\n            from .auth2fa import LedgerAuthDialog\n        except ImportError as e:\n            self.message_dialog(str(e))\n            return\n        dialog = LedgerAuthDialog(self, data)\n        dialog.exec_()\n        self.word = dialog.pin\n        self.done.set()\n                    \n    def get_auth(self, data):\n        self.done.clear()\n        self.auth_signal.emit(data)\n        self.done.wait()\n        return self.word\n        \n    def get_setup(self):\n        self.done.clear()\n        self.setup_signal.emit()\n        self.done.wait()\n        return \n        \n    def setup_dialog(self):\n        dialog = StartBTChipPersoDialog()\n        dialog.exec_()\n\n\n        \n        \n        \n        \n"
  },
  {
    "path": "plugins/trezor/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'TREZOR Wallet'\ndescription = _('Provides support for TREZOR hardware wallet')\nrequires = [('trezorlib','github.com/trezor/python-trezor')]\nregisters_keystore = ('hardware', 'trezor', _(\"TREZOR wallet\"))\navailable_for = ['qt', 'cmdline']\n\n"
  },
  {
    "path": "plugins/trezor/client.py",
    "content": "from trezorlib.client import proto, BaseClient, ProtocolMixin\nfrom .clientbase import TrezorClientBase\n\nclass TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient):\n    def __init__(self, transport, handler, plugin):\n        BaseClient.__init__(self, transport)\n        ProtocolMixin.__init__(self, transport)\n        TrezorClientBase.__init__(self, handler, plugin, proto)\n\n\nTrezorClientBase.wrap_methods(TrezorClient)\n"
  },
  {
    "path": "plugins/trezor/clientbase.py",
    "content": "import time\nfrom struct import pack\n\nfrom electrum.i18n import _\nfrom electrum.util import PrintError, UserCancelled\nfrom electrum.keystore import bip39_normalize_passphrase\nfrom electrum.bitcoin import serialize_xpub\n\n\nclass GuiMixin(object):\n    # Requires: self.proto, self.device\n\n    messages = {\n        3: _(\"Confirm the transaction output on your %s device\"),\n        4: _(\"Confirm internal entropy on your %s device to begin\"),\n        5: _(\"Write down the seed word shown on your %s\"),\n        6: _(\"Confirm on your %s that you want to wipe it clean\"),\n        7: _(\"Confirm on your %s device the message to sign\"),\n        8: _(\"Confirm the total amount spent and the transaction fee on your \"\n             \"%s device\"),\n        10: _(\"Confirm wallet address on your %s device\"),\n        'default': _(\"Check your %s device to continue\"),\n    }\n\n    def callback_Failure(self, msg):\n        # BaseClient's unfortunate call() implementation forces us to\n        # raise exceptions on failure in order to unwind the stack.\n        # However, making the user acknowledge they cancelled\n        # gets old very quickly, so we suppress those.  The NotInitialized\n        # one is misnamed and indicates a passphrase request was cancelled.\n        if msg.code in (self.types.FailureType.PinCancelled,\n                        self.types.FailureType.ActionCancelled,\n                        self.types.FailureType.NotInitialized):\n            raise UserCancelled()\n        raise RuntimeError(msg.message)\n\n    def callback_ButtonRequest(self, msg):\n        message = self.msg\n        if not message:\n            message = self.messages.get(msg.code, self.messages['default'])\n        self.handler.show_message(message % self.device, self.cancel)\n        return self.proto.ButtonAck()\n\n    def callback_PinMatrixRequest(self, msg):\n        if msg.type == 2:\n            msg = _(\"Enter a new PIN for your %s:\")\n        elif msg.type == 3:\n            msg = (_(\"Re-enter the new PIN for your %s.\\n\\n\"\n                     \"NOTE: the positions of the numbers have changed!\"))\n        else:\n            msg = _(\"Enter your current %s PIN:\")\n        pin = self.handler.get_pin(msg % self.device)\n        if not pin:\n            return self.proto.Cancel()\n        return self.proto.PinMatrixAck(pin=pin)\n\n    def callback_PassphraseRequest(self, req):\n        if self.creating_wallet:\n            msg = _(\"Enter a passphrase to generate this wallet.  Each time \"\n                    \"you use this wallet your %s will prompt you for the \"\n                    \"passphrase.  If you forget the passphrase you cannot \"\n                    \"access the bitcoins in the wallet.\") % self.device\n        else:\n            msg = _(\"Enter the passphrase to unlock this wallet:\")\n        passphrase = self.handler.get_passphrase(msg, self.creating_wallet)\n        if passphrase is None:\n            return self.proto.Cancel()\n        passphrase = bip39_normalize_passphrase(passphrase)\n        return self.proto.PassphraseAck(passphrase=passphrase)\n\n    def callback_WordRequest(self, msg):\n        self.step += 1\n        msg = _(\"Step %d/24.  Enter seed word as explained on \"\n                \"your %s:\") % (self.step, self.device)\n        word = self.handler.get_word(msg)\n        # Unfortunately the device can't handle self.proto.Cancel()\n        return self.proto.WordAck(word=word)\n\n    def callback_CharacterRequest(self, msg):\n        char_info = self.handler.get_char(msg)\n        if not char_info:\n            return self.proto.Cancel()\n        return self.proto.CharacterAck(**char_info)\n\n\nclass TrezorClientBase(GuiMixin, PrintError):\n\n    def __init__(self, handler, plugin, proto):\n        assert hasattr(self, 'tx_api')  # ProtocolMixin already constructed?\n        self.proto = proto\n        self.device = plugin.device\n        self.handler = handler\n        self.tx_api = plugin\n        self.types = plugin.types\n        self.msg = None\n        self.creating_wallet = False\n        self.used()\n\n    def __str__(self):\n        return \"%s/%s\" % (self.label(), self.features.device_id)\n\n    def label(self):\n        '''The name given by the user to the device.'''\n        return self.features.label\n\n    def is_initialized(self):\n        '''True if initialized, False if wiped.'''\n        return self.features.initialized\n\n    def is_pairable(self):\n        return not self.features.bootloader_mode\n\n    def used(self):\n        self.last_operation = time.time()\n\n    def prevent_timeouts(self):\n        self.last_operation = float('inf')\n\n    def timeout(self, cutoff):\n        '''Time out the client if the last operation was before cutoff.'''\n        if self.last_operation < cutoff:\n            self.print_error(\"timed out\")\n            self.clear_session()\n\n    @staticmethod\n    def expand_path(n):\n        '''Convert bip32 path to list of uint32 integers with prime flags\n        0/-1/1' -> [0, 0x80000001, 0x80000001]'''\n        # This code is similar to code in trezorlib where it unforunately\n        # is not declared as a staticmethod.  Our n has an extra element.\n        PRIME_DERIVATION_FLAG = 0x80000000\n        path = []\n        for x in n.split('/')[1:]:\n            prime = 0\n            if x.endswith(\"'\"):\n                x = x.replace('\\'', '')\n                prime = PRIME_DERIVATION_FLAG\n            if x.startswith('-'):\n                prime = PRIME_DERIVATION_FLAG\n            path.append(abs(int(x)) | prime)\n        return path\n\n    def cancel(self):\n        '''Provided here as in keepkeylib but not trezorlib.'''\n        self.transport.write(self.proto.Cancel())\n\n    def i4b(self, x):\n        return pack('>I', x)\n\n    def get_xpub(self, bip32_path, xtype):\n        address_n = self.expand_path(bip32_path)\n        creating = False\n        node = self.get_public_node(address_n, creating).node\n        return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))\n\n    def toggle_passphrase(self):\n        if self.features.passphrase_protection:\n            self.msg = _(\"Confirm on your %s device to disable passphrases\")\n        else:\n            self.msg = _(\"Confirm on your %s device to enable passphrases\")\n        enabled = not self.features.passphrase_protection\n        self.apply_settings(use_passphrase=enabled)\n\n    def change_label(self, label):\n        self.msg = _(\"Confirm the new label on your %s device\")\n        self.apply_settings(label=label)\n\n    def change_homescreen(self, homescreen):\n        self.msg = _(\"Confirm on your %s device to change your home screen\")\n        self.apply_settings(homescreen=homescreen)\n\n    def set_pin(self, remove):\n        if remove:\n            self.msg = _(\"Confirm on your %s device to disable PIN protection\")\n        elif self.features.pin_protection:\n            self.msg = _(\"Confirm on your %s device to change your PIN\")\n        else:\n            self.msg = _(\"Confirm on your %s device to set a PIN\")\n        self.change_pin(remove)\n\n    def clear_session(self):\n        '''Clear the session to force pin (and passphrase if enabled)\n        re-entry.  Does not leak exceptions.'''\n        self.print_error(\"clear session:\", self)\n        self.prevent_timeouts()\n        try:\n            super(TrezorClientBase, self).clear_session()\n        except BaseException as e:\n            # If the device was removed it has the same effect...\n            self.print_error(\"clear_session: ignoring error\", str(e))\n            pass\n\n    def get_public_node(self, address_n, creating):\n        self.creating_wallet = creating\n        return super(TrezorClientBase, self).get_public_node(address_n)\n\n    def close(self):\n        '''Called when Our wallet was closed or the device removed.'''\n        self.print_error(\"closing client\")\n        self.clear_session()\n        # Release the device\n        self.transport.close()\n\n    def firmware_version(self):\n        f = self.features\n        return (f.major_version, f.minor_version, f.patch_version)\n\n    def atleast_version(self, major, minor=0, patch=0):\n        return self.firmware_version() >= (major, minor, patch)\n\n    @staticmethod\n    def wrapper(func):\n        '''Wrap methods to clear any message box they opened.'''\n\n        def wrapped(self, *args, **kwargs):\n            try:\n                self.prevent_timeouts()\n                return func(self, *args, **kwargs)\n            finally:\n                self.used()\n                self.handler.finished()\n                self.creating_wallet = False\n                self.msg = None\n\n        return wrapped\n\n    @staticmethod\n    def wrap_methods(cls):\n        for method in ['apply_settings', 'change_pin',\n                       'get_address', 'get_public_node',\n                       'load_device_by_mnemonic', 'load_device_by_xprv',\n                       'recovery_device', 'reset_device', 'sign_message',\n                       'sign_tx', 'wipe_device']:\n            setattr(cls, method, cls.wrapper(getattr(cls, method)))\n"
  },
  {
    "path": "plugins/trezor/cmdline.py",
    "content": "from electrum.plugins import hook\nfrom .trezor import TrezorPlugin\nfrom ..hw_wallet import CmdLineHandler\n\nclass Plugin(TrezorPlugin):\n    handler = CmdLineHandler()\n    @hook\n    def init_keystore(self, keystore):\n        if not isinstance(keystore, self.keystore_class):\n            return\n        keystore.handler = self.handler\n"
  },
  {
    "path": "plugins/trezor/plugin.py",
    "content": "import threading\n\nfrom binascii import hexlify, unhexlify\n\nfrom electrum.util import bfh, bh2u\nfrom electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey,\n                              TYPE_ADDRESS, TYPE_SCRIPT, NetworkConstants)\nfrom electrum.i18n import _\nfrom electrum.plugins import BasePlugin\nfrom electrum.transaction import deserialize\nfrom electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey\n\nfrom ..hw_wallet import HW_PluginBase\n\n\n# TREZOR initialization methods\nTIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)\n\n# script \"generation\"\nSCRIPT_GEN_LEGACY, SCRIPT_GEN_P2SH_SEGWIT, SCRIPT_GEN_NATIVE_SEGWIT = range(0, 3)\n\nclass TrezorCompatibleKeyStore(Hardware_KeyStore):\n\n    def get_derivation(self):\n        return self.derivation\n\n    def get_script_gen(self):\n        def is_p2sh_segwit():\n            return self.derivation.startswith(\"m/49'/\")\n\n        def is_native_segwit():\n            return self.derivation.startswith(\"m/84'/\")\n\n        if is_native_segwit():\n            return SCRIPT_GEN_NATIVE_SEGWIT\n        elif is_p2sh_segwit():\n            return SCRIPT_GEN_P2SH_SEGWIT\n        else:\n            return SCRIPT_GEN_LEGACY\n\n    def get_client(self, force_pair=True):\n        return self.plugin.get_client(self, force_pair)\n\n    def decrypt_message(self, sequence, message, password):\n        raise RuntimeError(_('Encryption and decryption are not implemented by %s') % self.device)\n\n    def sign_message(self, sequence, message, password):\n        client = self.get_client()\n        address_path = self.get_derivation() + \"/%d/%d\"%sequence\n        address_n = client.expand_path(address_path)\n        msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)\n        return msg_sig.signature\n\n    def sign_transaction(self, tx, password):\n        if tx.is_complete():\n            return\n        # previous transactions used as inputs\n        prev_tx = {}\n        # path of the xpubs that are involved\n        xpub_path = {}\n        for txin in tx.inputs():\n            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)\n            tx_hash = txin['prevout_hash']\n            prev_tx[tx_hash] = txin['prev_tx']\n            for x_pubkey in x_pubkeys:\n                if not is_xpubkey(x_pubkey):\n                    continue\n                xpub, s = parse_xpubkey(x_pubkey)\n                if xpub == self.get_master_public_key():\n                    xpub_path[xpub] = self.get_derivation()\n\n        self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)\n\n\nclass TrezorCompatiblePlugin(HW_PluginBase):\n    # Derived classes provide:\n    #\n    #  class-static variables: client_class, firmware_URL, handler_class,\n    #     libraries_available, libraries_URL, minimum_firmware,\n    #     wallet_class, ckd_public, types, HidTransport\n\n    MAX_LABEL_LEN = 32\n\n    def __init__(self, parent, config, name):\n        HW_PluginBase.__init__(self, parent, config, name)\n        self.main_thread = threading.current_thread()\n        # FIXME: move to base class when Ledger is fixed\n        if self.libraries_available:\n            self.device_manager().register_devices(self.DEVICE_IDS)\n\n    def _try_hid(self, device):\n        self.print_error(\"Trying to connect over USB...\")\n        try:\n            return self.hid_transport(device)\n        except BaseException as e:\n            # see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114\n            # raise\n            self.print_error(\"cannot connect at\", device.path, str(e))\n            return None\n\n    def _try_bridge(self, device):\n        self.print_error(\"Trying to connect over Trezor Bridge...\")\n        try:\n            return self.bridge_transport({'path': hexlify(device.path)})\n        except BaseException as e:\n            self.print_error(\"cannot connect to bridge\", str(e))\n            return None\n\n    def create_client(self, device, handler):\n        # disable bridge because it seems to never returns if keepkey is plugged\n        #transport = self._try_bridge(device) or self._try_hid(device)\n        transport = self._try_hid(device)\n        if not transport:\n            self.print_error(\"cannot connect to device\")\n            return\n\n        self.print_error(\"connected to device at\", device.path)\n\n        client = self.client_class(transport, handler, self)\n\n        # Try a ping for device sanity\n        try:\n            client.ping('t')\n        except BaseException as e:\n            self.print_error(\"ping failed\", str(e))\n            return None\n\n        if not client.atleast_version(*self.minimum_firmware):\n            msg = (_('Outdated %s firmware for device labelled %s. Please '\n                     'download the updated firmware from %s') %\n                   (self.device, client.label(), self.firmware_URL))\n            self.print_error(msg)\n            handler.show_error(msg)\n            return None\n\n        return client\n\n    def get_client(self, keystore, force_pair=True):\n        devmgr = self.device_manager()\n        handler = keystore.handler\n        with devmgr.hid_lock:\n            client = devmgr.client_for_keystore(self, handler, keystore, force_pair)\n        # returns the client for a given keystore. can use xpub\n        if client:\n            client.used()\n        return client\n\n    def get_coin_name(self):\n        return \"Testnet\" if NetworkConstants.TESTNET else \"Bitcoin\"\n\n    def initialize_device(self, device_id, wizard, handler):\n        # Initialization method\n        msg = _(\"Choose how you want to initialize your %s.\\n\\n\"\n                \"The first two methods are secure as no secret information \"\n                \"is entered into your computer.\\n\\n\"\n                \"For the last two methods you input secrets on your keyboard \"\n                \"and upload them to your %s, and so you should \"\n                \"only do those on a computer you know to be trustworthy \"\n                \"and free of malware.\"\n        ) % (self.device, self.device)\n        choices = [\n            # Must be short as QT doesn't word-wrap radio button text\n            (TIM_NEW, _(\"Let the device generate a completely new seed randomly\")),\n            (TIM_RECOVER, _(\"Recover from a seed you have previously written down\")),\n            (TIM_MNEMONIC, _(\"Upload a BIP39 mnemonic to generate the seed\")),\n            (TIM_PRIVKEY, _(\"Upload a master private key\"))\n        ]\n        def f(method):\n            import threading\n            settings = self.request_trezor_init_settings(wizard, method, self.device)\n            t = threading.Thread(target = self._initialize_device, args=(settings, method, device_id, wizard, handler))\n            t.setDaemon(True)\n            t.start()\n            wizard.loop.exec_()\n        wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f)\n\n    def _initialize_device(self, settings, method, device_id, wizard, handler):\n        item, label, pin_protection, passphrase_protection = settings\n\n        if method == TIM_RECOVER:\n            # FIXME the PIN prompt will appear over this message\n            # which makes this unreadable\n            handler.show_error(_(\n                \"You will be asked to enter 24 words regardless of your \"\n                \"seed's actual length.  If you enter a word incorrectly or \"\n                \"misspell it, you cannot change it or go back - you will need \"\n                \"to start again from the beginning.\\n\\nSo please enter \"\n                \"the words carefully!\"))\n\n        language = 'english'\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n\n        if method == TIM_NEW:\n            strength = 64 * (item + 2)  # 128, 192 or 256\n            u2f_counter = 0\n            skip_backup = False\n            client.reset_device(True, strength, passphrase_protection,\n                                pin_protection, label, language,\n                                u2f_counter, skip_backup)\n        elif method == TIM_RECOVER:\n            word_count = 6 * (item + 2)  # 12, 18 or 24\n            client.step = 0\n            client.recovery_device(word_count, passphrase_protection,\n                                       pin_protection, label, language)\n        elif method == TIM_MNEMONIC:\n            pin = pin_protection  # It's the pin, not a boolean\n            client.load_device_by_mnemonic(str(item), pin,\n                                           passphrase_protection,\n                                           label, language)\n        else:\n            pin = pin_protection  # It's the pin, not a boolean\n            client.load_device_by_xprv(item, pin, passphrase_protection,\n                                       label, language)\n        wizard.loop.exit(0)\n\n    def setup_device(self, device_info, wizard):\n        '''Called when creating a new wallet.  Select the device to use.  If\n        the device is uninitialized, go through the intialization\n        process.'''\n        devmgr = self.device_manager()\n        device_id = device_info.device.id_\n        client = devmgr.client_by_id(device_id)\n        # fixme: we should use: client.handler = wizard\n        client.handler = self.create_handler(wizard)\n        if not device_info.initialized:\n            self.initialize_device(device_id, wizard, client.handler)\n        client.get_xpub('m', 'standard')\n        client.used()\n\n    def get_xpub(self, device_id, derivation, xtype, wizard):\n        devmgr = self.device_manager()\n        client = devmgr.client_by_id(device_id)\n        client.handler = wizard\n        xpub = client.get_xpub(derivation, xtype)\n        client.used()\n        return xpub\n\n    def sign_transaction(self, keystore, tx, prev_tx, xpub_path):\n        self.prev_tx = prev_tx\n        self.xpub_path = xpub_path\n        client = self.get_client(keystore)\n        inputs = self.tx_inputs(tx, True, keystore.get_script_gen())\n        outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.get_script_gen())\n        signed_tx = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[1]\n        raw = bh2u(signed_tx)\n        tx.update_signatures(raw)\n\n    def show_address(self, wallet, address):\n        client = self.get_client(wallet.keystore)\n        if not client.atleast_version(1, 3):\n            wallet.keystore.handler.show_error(_(\"Your device firmware is too old\"))\n            return\n        change, index = wallet.get_address_index(address)\n        derivation = wallet.keystore.derivation\n        address_path = \"%s/%d/%d\"%(derivation, change, index)\n        address_n = client.expand_path(address_path)\n        script_gen = wallet.keystore.get_script_gen()\n        if script_gen == SCRIPT_GEN_NATIVE_SEGWIT:\n            script_type = self.types.InputScriptType.SPENDWITNESS\n        elif script_gen == SCRIPT_GEN_P2SH_SEGWIT:\n            script_type = self.types.InputScriptType.SPENDP2SHWITNESS\n        else:\n            script_type = self.types.InputScriptType.SPENDADDRESS\n        client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)\n\n    def tx_inputs(self, tx, for_sig=False, script_gen=SCRIPT_GEN_LEGACY):\n        inputs = []\n        for txin in tx.inputs():\n            txinputtype = self.types.TxInputType()\n            if txin['type'] == 'coinbase':\n                prev_hash = \"\\0\"*32\n                prev_index = 0xffffffff  # signed int -1\n            else:\n                if for_sig:\n                    x_pubkeys = txin['x_pubkeys']\n                    if len(x_pubkeys) == 1:\n                        x_pubkey = x_pubkeys[0]\n                        xpub, s = parse_xpubkey(x_pubkey)\n                        xpub_n = self.client_class.expand_path(self.xpub_path[xpub])\n                        txinputtype._extend_address_n(xpub_n + s)\n                        if script_gen == SCRIPT_GEN_NATIVE_SEGWIT:\n                            txinputtype.script_type = self.types.InputScriptType.SPENDWITNESS\n                        elif script_gen == SCRIPT_GEN_P2SH_SEGWIT:\n                            txinputtype.script_type = self.types.InputScriptType.SPENDP2SHWITNESS\n                        else:\n                            txinputtype.script_type = self.types.InputScriptType.SPENDADDRESS\n                    else:\n                        def f(x_pubkey):\n                            if is_xpubkey(x_pubkey):\n                                xpub, s = parse_xpubkey(x_pubkey)\n                            else:\n                                xpub = xpub_from_pubkey(0, bfh(x_pubkey))\n                                s = []\n                            node = self.ckd_public.deserialize(xpub)\n                            return self.types.HDNodePathType(node=node, address_n=s)\n                        pubkeys = list(map(f, x_pubkeys))\n                        multisig = self.types.MultisigRedeemScriptType(\n                            pubkeys=pubkeys,\n                            signatures=list(map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))),\n                            m=txin.get('num_sig'),\n                        )\n                        if script_gen == SCRIPT_GEN_NATIVE_SEGWIT:\n                            script_type = self.types.InputScriptType.SPENDWITNESS\n                        elif script_gen == SCRIPT_GEN_P2SH_SEGWIT:\n                            script_type = self.types.InputScriptType.SPENDP2SHWITNESS\n                        else:\n                            script_type = self.types.InputScriptType.SPENDMULTISIG\n                        txinputtype = self.types.TxInputType(\n                            script_type=script_type,\n                            multisig=multisig\n                        )\n                        # find which key is mine\n                        for x_pubkey in x_pubkeys:\n                            if is_xpubkey(x_pubkey):\n                                xpub, s = parse_xpubkey(x_pubkey)\n                                if xpub in self.xpub_path:\n                                    xpub_n = self.client_class.expand_path(self.xpub_path[xpub])\n                                    txinputtype._extend_address_n(xpub_n + s)\n                                    break\n\n                prev_hash = unhexlify(txin['prevout_hash'])\n                prev_index = txin['prevout_n']\n\n            if 'value' in txin:\n                txinputtype.amount = txin['value']\n            txinputtype.prev_hash = prev_hash\n            txinputtype.prev_index = prev_index\n\n            if 'scriptSig' in txin:\n                script_sig = bfh(txin['scriptSig'])\n                txinputtype.script_sig = script_sig\n\n            txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)\n\n            inputs.append(txinputtype)\n\n        return inputs\n\n    def tx_outputs(self, derivation, tx, script_gen=SCRIPT_GEN_LEGACY):\n        outputs = []\n        has_change = False\n\n        for _type, address, amount in tx.outputs():\n            info = tx.output_info.get(address)\n            if info is not None and not has_change:\n                has_change = True # no more than one change address\n                index, xpubs, m = info\n                if len(xpubs) == 1:\n                    if script_gen == SCRIPT_GEN_NATIVE_SEGWIT:\n                        script_type = self.types.OutputScriptType.PAYTOWITNESS\n                    elif script_gen == SCRIPT_GEN_P2SH_SEGWIT:\n                        script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS\n                    else:\n                        script_type = self.types.OutputScriptType.PAYTOADDRESS\n                    address_n = self.client_class.expand_path(derivation + \"/%d/%d\"%index)\n                    txoutputtype = self.types.TxOutputType(\n                        amount = amount,\n                        script_type = script_type,\n                        address_n = address_n,\n                    )\n                else:\n                    if script_gen == SCRIPT_GEN_NATIVE_SEGWIT:\n                        script_type = self.types.OutputScriptType.PAYTOWITNESS\n                    elif script_gen == SCRIPT_GEN_P2SH_SEGWIT:\n                        script_type = self.types.OutputScriptType.PAYTOP2SHWITNESS\n                    else:\n                        script_type = self.types.OutputScriptType.PAYTOMULTISIG\n                    address_n = self.client_class.expand_path(\"/%d/%d\"%index)\n                    nodes = map(self.ckd_public.deserialize, xpubs)\n                    pubkeys = [ self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes]\n                    multisig = self.types.MultisigRedeemScriptType(\n                        pubkeys = pubkeys,\n                        signatures = [b''] * len(pubkeys),\n                        m = m)\n                    txoutputtype = self.types.TxOutputType(\n                        multisig = multisig,\n                        amount = amount,\n                        address_n = self.client_class.expand_path(derivation + \"/%d/%d\"%index),\n                        script_type = script_type)\n            else:\n                txoutputtype = self.types.TxOutputType()\n                txoutputtype.amount = amount\n                if _type == TYPE_SCRIPT:\n                    txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN\n                    txoutputtype.op_return_data = address[2:]\n                elif _type == TYPE_ADDRESS:\n                    txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS\n                    txoutputtype.address = address\n\n            outputs.append(txoutputtype)\n\n        return outputs\n\n    def electrum_tx_to_txtype(self, tx):\n        t = self.types.TransactionType()\n        d = deserialize(tx.raw)\n        t.version = d['version']\n        t.lock_time = d['lockTime']\n        inputs = self.tx_inputs(tx)\n        t._extend_inputs(inputs)\n        for vout in d['outputs']:\n            o = t._add_bin_outputs()\n            o.amount = vout['value']\n            o.script_pubkey = bfh(vout['scriptPubKey'])\n        return t\n\n    # This function is called from the trezor libraries (via tx_api)\n    def get_tx(self, tx_hash):\n        tx = self.prev_tx[tx_hash]\n        return self.electrum_tx_to_txtype(tx)\n"
  },
  {
    "path": "plugins/trezor/qt.py",
    "content": "from ..trezor.qt_generic import QtPlugin\nfrom .trezor import TrezorPlugin\n\n\nclass Plugin(TrezorPlugin, QtPlugin):\n    icon_unpaired = \":icons/trezor_unpaired.png\"\n    icon_paired = \":icons/trezor.png\"\n\n    @classmethod\n    def pin_matrix_widget_class(self):\n        from trezorlib.qt.pinmatrix import PinMatrixWidget\n        return PinMatrixWidget\n"
  },
  {
    "path": "plugins/trezor/qt_generic.py",
    "content": "from functools import partial\nimport threading\n\nfrom PyQt5.Qt import Qt\nfrom PyQt5.Qt import QGridLayout, QInputDialog, QPushButton\nfrom PyQt5.Qt import QVBoxLayout, QLabel\nfrom electrum_gui.qt.util import *\nfrom .plugin import TIM_NEW, TIM_RECOVER, TIM_MNEMONIC\nfrom ..hw_wallet.qt import QtHandlerBase, QtPluginBase\n\nfrom electrum.i18n import _\nfrom electrum.plugins import hook, DeviceMgr\nfrom electrum.util import PrintError, UserCancelled, bh2u\nfrom electrum.wallet import Wallet, Standard_Wallet\n\nPASSPHRASE_HELP_SHORT =_(\n    \"Passphrases allow you to access new wallets, each \"\n    \"hidden behind a particular case-sensitive passphrase.\")\nPASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + \"  \" + _(\n    \"You need to create a separate Electrum wallet for each passphrase \"\n    \"you use as they each generate different addresses.  Changing \"\n    \"your passphrase does not lose other wallets, each is still \"\n    \"accessible behind its own passphrase.\")\nRECOMMEND_PIN = _(\n    \"You should enable PIN protection.  Your PIN is the only protection \"\n    \"for your bitcoins if your device is lost or stolen.\")\nPASSPHRASE_NOT_PIN = _(\n    \"If you forget a passphrase you will be unable to access any \"\n    \"bitcoins in the wallet behind it.  A passphrase is not a PIN. \"\n    \"Only change this if you are sure you understand it.\")\nCHARACTER_RECOVERY = (\n    \"Use the recovery cipher shown on your device to input your seed words.  \"\n    \"The cipher changes with every keypress.\\n\"\n    \"After at most 4 letters the device will auto-complete a word.\\n\"\n    \"Press SPACE or the Accept Word button to accept the device's auto-\"\n    \"completed word and advance to the next one.\\n\"\n    \"Press BACKSPACE to go back a character or word.\\n\"\n    \"Press ENTER or the Seed Entered button once the last word in your \"\n    \"seed is auto-completed.\")\n\nclass CharacterButton(QPushButton):\n    def __init__(self, text=None):\n        QPushButton.__init__(self, text)\n\n    def keyPressEvent(self, event):\n        event.setAccepted(False)   # Pass through Enter and Space keys\n\n\nclass CharacterDialog(WindowModalDialog):\n\n    def __init__(self, parent):\n        super(CharacterDialog, self).__init__(parent)\n        self.setWindowTitle(_(\"KeepKey Seed Recovery\"))\n        self.character_pos = 0\n        self.word_pos = 0\n        self.loop = QEventLoop()\n        self.word_help = QLabel()\n        self.char_buttons = []\n\n        vbox = QVBoxLayout(self)\n        vbox.addWidget(WWLabel(CHARACTER_RECOVERY))\n        hbox = QHBoxLayout()\n        hbox.addWidget(self.word_help)\n        for i in range(4):\n            char_button = CharacterButton('*')\n            char_button.setMaximumWidth(36)\n            self.char_buttons.append(char_button)\n            hbox.addWidget(char_button)\n        self.accept_button = CharacterButton(_(\"Accept Word\"))\n        self.accept_button.clicked.connect(partial(self.process_key, 32))\n        self.rejected.connect(partial(self.loop.exit, 1))\n        hbox.addWidget(self.accept_button)\n        hbox.addStretch(1)\n        vbox.addLayout(hbox)\n\n        self.finished_button = QPushButton(_(\"Seed Entered\"))\n        self.cancel_button = QPushButton(_(\"Cancel\"))\n        self.finished_button.clicked.connect(partial(self.process_key,\n                                                     Qt.Key_Return))\n        self.cancel_button.clicked.connect(self.rejected)\n        buttons = Buttons(self.finished_button, self.cancel_button)\n        vbox.addSpacing(40)\n        vbox.addLayout(buttons)\n        self.refresh()\n        self.show()\n\n    def refresh(self):\n        self.word_help.setText(\"Enter seed word %2d:\" % (self.word_pos + 1))\n        self.accept_button.setEnabled(self.character_pos >= 3)\n        self.finished_button.setEnabled((self.word_pos in (11, 17, 23)\n                                         and self.character_pos >= 3))\n        for n, button in enumerate(self.char_buttons):\n            button.setEnabled(n == self.character_pos)\n            if n == self.character_pos:\n                button.setFocus()\n\n    def is_valid_alpha_space(self, key):\n        # Auto-completion requires at least 3 characters\n        if key == ord(' ') and self.character_pos >= 3:\n            return True\n        # Firmware aborts protocol if the 5th character is non-space\n        if self.character_pos >= 4:\n            return False\n        return (key >= ord('a') and key <= ord('z')\n                or (key >= ord('A') and key <= ord('Z')))\n\n    def process_key(self, key):\n        self.data = None\n        if key == Qt.Key_Return and self.finished_button.isEnabled():\n            self.data = {'done': True}\n        elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos):\n            self.data = {'delete': True}\n        elif self.is_valid_alpha_space(key):\n            self.data = {'character': chr(key).lower()}\n        if self.data:\n            self.loop.exit(0)\n\n    def keyPressEvent(self, event):\n        self.process_key(event.key())\n        if not self.data:\n            QDialog.keyPressEvent(self, event)\n\n    def get_char(self, word_pos, character_pos):\n        self.word_pos = word_pos\n        self.character_pos = character_pos\n        self.refresh()\n        if self.loop.exec_():\n            self.data = None  # User cancelled\n\n\nclass QtHandler(QtHandlerBase):\n\n    char_signal = pyqtSignal(object)\n    pin_signal = pyqtSignal(object)\n\n    def __init__(self, win, pin_matrix_widget_class, device):\n        super(QtHandler, self).__init__(win, device)\n        self.char_signal.connect(self.update_character_dialog)\n        self.pin_signal.connect(self.pin_dialog)\n        self.pin_matrix_widget_class = pin_matrix_widget_class\n        self.character_dialog = None\n\n    def get_char(self, msg):\n        self.done.clear()\n        self.char_signal.emit(msg)\n        self.done.wait()\n        data = self.character_dialog.data\n        if not data or 'done' in data:\n            self.character_dialog.accept()\n            self.character_dialog = None\n        return data\n\n    def get_pin(self, msg):\n        self.done.clear()\n        self.pin_signal.emit(msg)\n        self.done.wait()\n        return self.response\n\n    def pin_dialog(self, msg):\n        # Needed e.g. when resetting a device\n        self.clear_dialog()\n        dialog = WindowModalDialog(self.top_level_window(), _(\"Enter PIN\"))\n        matrix = self.pin_matrix_widget_class()\n        vbox = QVBoxLayout()\n        vbox.addWidget(QLabel(msg))\n        vbox.addWidget(matrix)\n        vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))\n        dialog.setLayout(vbox)\n        dialog.exec_()\n        self.response = str(matrix.get_value())\n        self.done.set()\n\n    def update_character_dialog(self, msg):\n        if not self.character_dialog:\n            self.character_dialog = CharacterDialog(self.top_level_window())\n        self.character_dialog.get_char(msg.word_pos, msg.character_pos)\n        self.done.set()\n\n\n\nclass QtPlugin(QtPluginBase):\n    # Derived classes must provide the following class-static variables:\n    #   icon_file\n    #   pin_matrix_widget_class\n\n    def create_handler(self, window):\n        return QtHandler(window, self.pin_matrix_widget_class(), self.device)\n\n    @hook\n    def receive_menu(self, menu, addrs, wallet):\n        if type(wallet) is not Standard_Wallet:\n            return\n        keystore = wallet.get_keystore()\n        if type(keystore) == self.keystore_class and len(addrs) == 1:\n            def show_address():\n                keystore.thread.add(partial(self.show_address, wallet, addrs[0]))\n            menu.addAction(_(\"Show on %s\") % self.device, show_address)\n\n    def show_settings_dialog(self, window, keystore):\n        device_id = self.choose_device(window, keystore)\n        if device_id:\n            SettingsDialog(window, self, keystore, device_id).exec_()\n\n    def request_trezor_init_settings(self, wizard, method, device):\n        vbox = QVBoxLayout()\n        next_enabled = True\n        label = QLabel(_(\"Enter a label to name your device:\"))\n        name = QLineEdit()\n        hl = QHBoxLayout()\n        hl.addWidget(label)\n        hl.addWidget(name)\n        hl.addStretch(1)\n        vbox.addLayout(hl)\n\n        def clean_text(widget):\n            text = widget.toPlainText().strip()\n            return ' '.join(text.split())\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            gb = QGroupBox()\n            hbox1 = QHBoxLayout()\n            gb.setLayout(hbox1)\n            # KeepKey recovery doesn't need a word count\n            if method == TIM_NEW or self.device == 'TREZOR':\n                vbox.addWidget(gb)\n            gb.setTitle(_(\"Select your seed length:\"))\n            bg = QButtonGroup()\n            for i, count in enumerate([12, 18, 24]):\n                rb = QRadioButton(gb)\n                rb.setText(_(\"%d words\") % count)\n                bg.addButton(rb)\n                bg.setId(rb, i)\n                hbox1.addWidget(rb)\n                rb.setChecked(True)\n            cb_pin = QCheckBox(_('Enable PIN protection'))\n            cb_pin.setChecked(True)\n        else:\n            text = QTextEdit()\n            text.setMaximumHeight(60)\n            if method == TIM_MNEMONIC:\n                msg = _(\"Enter your BIP39 mnemonic:\")\n            else:\n                msg = _(\"Enter the master private key beginning with xprv:\")\n                def set_enabled():\n                    from electrum.keystore import is_xprv\n                    wizard.next_button.setEnabled(is_xprv(clean_text(text)))\n                text.textChanged.connect(set_enabled)\n                next_enabled = False\n\n            vbox.addWidget(QLabel(msg))\n            vbox.addWidget(text)\n            pin = QLineEdit()\n            pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}')))\n            pin.setMaximumWidth(100)\n            hbox_pin = QHBoxLayout()\n            hbox_pin.addWidget(QLabel(_(\"Enter your PIN (digits 1-9):\")))\n            hbox_pin.addWidget(pin)\n            hbox_pin.addStretch(1)\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            vbox.addWidget(WWLabel(RECOMMEND_PIN))\n            vbox.addWidget(cb_pin)\n        else:\n            vbox.addLayout(hbox_pin)\n\n        passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)\n        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)\n        passphrase_warning.setStyleSheet(\"color: red\")\n        cb_phrase = QCheckBox(_('Enable passphrases'))\n        cb_phrase.setChecked(False)\n        vbox.addWidget(passphrase_msg)\n        vbox.addWidget(passphrase_warning)\n        vbox.addWidget(cb_phrase)\n\n        wizard.exec_layout(vbox, next_enabled=next_enabled)\n\n        if method in [TIM_NEW, TIM_RECOVER]:\n            item = bg.checkedId()\n            pin = cb_pin.isChecked()\n        else:\n            item = ' '.join(str(clean_text(text)).split())\n            pin = str(pin.text())\n\n        return (item, name.text(), pin, cb_phrase.isChecked())\n\n\n\n\nclass SettingsDialog(WindowModalDialog):\n    '''This dialog doesn't require a device be paired with a wallet.\n    We want users to be able to wipe a device even if they've forgotten\n    their PIN.'''\n\n    def __init__(self, window, plugin, keystore, device_id):\n        title = _(\"%s Settings\") % plugin.device\n        super(SettingsDialog, self).__init__(window, title)\n        self.setMaximumWidth(540)\n\n        devmgr = plugin.device_manager()\n        config = devmgr.config\n        handler = keystore.handler\n        thread = keystore.thread\n        hs_rows, hs_cols = (64, 128)\n\n        def invoke_client(method, *args, **kw_args):\n            unpair_after = kw_args.pop('unpair_after', False)\n\n            def task():\n                client = devmgr.client_by_id(device_id)\n                if not client:\n                    raise RuntimeError(\"Device not connected\")\n                if method:\n                    getattr(client, method)(*args, **kw_args)\n                if unpair_after:\n                    devmgr.unpair_id(device_id)\n                return client.features\n\n            thread.add(task, on_success=update)\n\n        def update(features):\n            self.features = features\n            set_label_enabled()\n            bl_hash = bh2u(features.bootloader_hash)\n            bl_hash = \"\\n\".join([bl_hash[:32], bl_hash[32:]])\n            noyes = [_(\"No\"), _(\"Yes\")]\n            endis = [_(\"Enable Passphrases\"), _(\"Disable Passphrases\")]\n            disen = [_(\"Disabled\"), _(\"Enabled\")]\n            setchange = [_(\"Set a PIN\"), _(\"Change PIN\")]\n\n            version = \"%d.%d.%d\" % (features.major_version,\n                                    features.minor_version,\n                                    features.patch_version)\n            coins = \", \".join(coin.coin_name for coin in features.coins)\n\n            device_label.setText(features.label)\n            pin_set_label.setText(noyes[features.pin_protection])\n            passphrases_label.setText(disen[features.passphrase_protection])\n            bl_hash_label.setText(bl_hash)\n            label_edit.setText(features.label)\n            device_id_label.setText(features.device_id)\n            initialized_label.setText(noyes[features.initialized])\n            version_label.setText(version)\n            coins_label.setText(coins)\n            clear_pin_button.setVisible(features.pin_protection)\n            clear_pin_warning.setVisible(features.pin_protection)\n            pin_button.setText(setchange[features.pin_protection])\n            pin_msg.setVisible(not features.pin_protection)\n            passphrase_button.setText(endis[features.passphrase_protection])\n            language_label.setText(features.language)\n\n        def set_label_enabled():\n            label_apply.setEnabled(label_edit.text() != self.features.label)\n\n        def rename():\n            invoke_client('change_label', label_edit.text())\n\n        def toggle_passphrase():\n            title = _(\"Confirm Toggle Passphrase Protection\")\n            currently_enabled = self.features.passphrase_protection\n            if currently_enabled:\n                msg = _(\"After disabling passphrases, you can only pair this \"\n                        \"Electrum wallet if it had an empty passphrase.  \"\n                        \"If its passphrase was not empty, you will need to \"\n                        \"create a new wallet with the install wizard.  You \"\n                        \"can use this wallet again at any time by re-enabling \"\n                        \"passphrases and entering its passphrase.\")\n            else:\n                msg = _(\"Your current Electrum wallet can only be used with \"\n                        \"an empty passphrase.  You must create a separate \"\n                        \"wallet with the install wizard for other passphrases \"\n                        \"as each one generates a new set of addresses.\")\n            msg += \"\\n\\n\" + _(\"Are you sure you want to proceed?\")\n            if not self.question(msg, title=title):\n                return\n            invoke_client('toggle_passphrase', unpair_after=currently_enabled)\n\n        def change_homescreen():\n            dialog = QFileDialog(self, _(\"Choose Homescreen\"))\n            filename, __ = dialog.getOpenFileName()\n\n            if filename.endswith('.toif'):\n                img = open(filename, 'rb').read()\n                if img[:8] != b'TOIf\\x90\\x00\\x90\\x00':\n                    raise Exception('File is not a TOIF file with size of 144x144')\n            else:\n                from PIL import Image # FIXME\n                im = Image.open(filename)\n                if im.size != (128, 64):\n                    raise Exception('Image must be 128 x 64 pixels')\n                im = im.convert('1')\n                pix = im.load()\n                img = bytearray(1024)\n                for j in range(64):\n                    for i in range(128):\n                        if pix[i, j]:\n                            o = (i + j * 128)\n                            img[o // 8] |= (1 << (7 - o % 8))\n                img = bytes(img)\n                invoke_client('change_homescreen', img)\n\n        def clear_homescreen():\n            invoke_client('change_homescreen', b'\\x00')\n\n        def set_pin():\n            invoke_client('set_pin', remove=False)\n\n        def clear_pin():\n            invoke_client('set_pin', remove=True)\n\n        def wipe_device():\n            wallet = window.wallet\n            if wallet and sum(wallet.get_balance()):\n                title = _(\"Confirm Device Wipe\")\n                msg = _(\"Are you SURE you want to wipe the device?\\n\"\n                        \"Your wallet still has bitcoins in it!\")\n                if not self.question(msg, title=title,\n                                     icon=QMessageBox.Critical):\n                    return\n            invoke_client('wipe_device', unpair_after=True)\n\n        def slider_moved():\n            mins = timeout_slider.sliderPosition()\n            timeout_minutes.setText(_(\"%2d minutes\") % mins)\n\n        def slider_released():\n            config.set_session_timeout(timeout_slider.sliderPosition() * 60)\n\n        # Information tab\n        info_tab = QWidget()\n        info_layout = QVBoxLayout(info_tab)\n        info_glayout = QGridLayout()\n        info_glayout.setColumnStretch(2, 1)\n        device_label = QLabel()\n        pin_set_label = QLabel()\n        passphrases_label = QLabel()\n        version_label = QLabel()\n        device_id_label = QLabel()\n        bl_hash_label = QLabel()\n        bl_hash_label.setWordWrap(True)\n        coins_label = QLabel()\n        coins_label.setWordWrap(True)\n        language_label = QLabel()\n        initialized_label = QLabel()\n        rows = [\n            (_(\"Device Label\"), device_label),\n            (_(\"PIN set\"), pin_set_label),\n            (_(\"Passphrases\"), passphrases_label),\n            (_(\"Firmware Version\"), version_label),\n            (_(\"Device ID\"), device_id_label),\n            (_(\"Bootloader Hash\"), bl_hash_label),\n            (_(\"Supported Coins\"), coins_label),\n            (_(\"Language\"), language_label),\n            (_(\"Initialized\"), initialized_label),\n        ]\n        for row_num, (label, widget) in enumerate(rows):\n            info_glayout.addWidget(QLabel(label), row_num, 0)\n            info_glayout.addWidget(widget, row_num, 1)\n        info_layout.addLayout(info_glayout)\n\n        # Settings tab\n        settings_tab = QWidget()\n        settings_layout = QVBoxLayout(settings_tab)\n        settings_glayout = QGridLayout()\n\n        # Settings tab - Label\n        label_msg = QLabel(_(\"Name this %s.  If you have mutiple devices \"\n                             \"their labels help distinguish them.\")\n                           % plugin.device)\n        label_msg.setWordWrap(True)\n        label_label = QLabel(_(\"Device Label\"))\n        label_edit = QLineEdit()\n        label_edit.setMinimumWidth(150)\n        label_edit.setMaxLength(plugin.MAX_LABEL_LEN)\n        label_apply = QPushButton(_(\"Apply\"))\n        label_apply.clicked.connect(rename)\n        label_edit.textChanged.connect(set_label_enabled)\n        settings_glayout.addWidget(label_label, 0, 0)\n        settings_glayout.addWidget(label_edit, 0, 1, 1, 2)\n        settings_glayout.addWidget(label_apply, 0, 3)\n        settings_glayout.addWidget(label_msg, 1, 1, 1, -1)\n\n        # Settings tab - PIN\n        pin_label = QLabel(_(\"PIN Protection\"))\n        pin_button = QPushButton()\n        pin_button.clicked.connect(set_pin)\n        settings_glayout.addWidget(pin_label, 2, 0)\n        settings_glayout.addWidget(pin_button, 2, 1)\n        pin_msg = QLabel(_(\"PIN protection is strongly recommended.  \"\n                           \"A PIN is your only protection against someone \"\n                           \"stealing your bitcoins if they obtain physical \"\n                           \"access to your %s.\") % plugin.device)\n        pin_msg.setWordWrap(True)\n        pin_msg.setStyleSheet(\"color: red\")\n        settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)\n\n        # Settings tab - Homescreen\n        if plugin.device != 'KeepKey':   # Not yet supported by KK firmware\n            homescreen_layout = QHBoxLayout()\n            homescreen_label = QLabel(_(\"Homescreen\"))\n            homescreen_change_button = QPushButton(_(\"Change...\"))\n            homescreen_clear_button = QPushButton(_(\"Reset\"))\n            homescreen_change_button.clicked.connect(change_homescreen)\n            homescreen_clear_button.clicked.connect(clear_homescreen)\n            homescreen_msg = QLabel(_(\"You can set the homescreen on your \"\n                                      \"device to personalize it.  You must \"\n                                      \"choose a %d x %d monochrome black and \"\n                                      \"white image.\") % (hs_rows, hs_cols))\n            homescreen_msg.setWordWrap(True)\n            settings_glayout.addWidget(homescreen_label, 4, 0)\n            settings_glayout.addWidget(homescreen_change_button, 4, 1)\n            settings_glayout.addWidget(homescreen_clear_button, 4, 2)\n            settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)\n\n        # Settings tab - Session Timeout\n        timeout_label = QLabel(_(\"Session Timeout\"))\n        timeout_minutes = QLabel()\n        timeout_slider = QSlider(Qt.Horizontal)\n        timeout_slider.setRange(1, 60)\n        timeout_slider.setSingleStep(1)\n        timeout_slider.setTickInterval(5)\n        timeout_slider.setTickPosition(QSlider.TicksBelow)\n        timeout_slider.setTracking(True)\n        timeout_msg = QLabel(\n            _(\"Clear the session after the specified period \"\n              \"of inactivity.  Once a session has timed out, \"\n              \"your PIN and passphrase (if enabled) must be \"\n              \"re-entered to use the device.\"))\n        timeout_msg.setWordWrap(True)\n        timeout_slider.setSliderPosition(config.get_session_timeout() // 60)\n        slider_moved()\n        timeout_slider.valueChanged.connect(slider_moved)\n        timeout_slider.sliderReleased.connect(slider_released)\n        settings_glayout.addWidget(timeout_label, 6, 0)\n        settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)\n        settings_glayout.addWidget(timeout_minutes, 6, 4)\n        settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)\n        settings_layout.addLayout(settings_glayout)\n        settings_layout.addStretch(1)\n\n        # Advanced tab\n        advanced_tab = QWidget()\n        advanced_layout = QVBoxLayout(advanced_tab)\n        advanced_glayout = QGridLayout()\n\n        # Advanced tab - clear PIN\n        clear_pin_button = QPushButton(_(\"Disable PIN\"))\n        clear_pin_button.clicked.connect(clear_pin)\n        clear_pin_warning = QLabel(\n            _(\"If you disable your PIN, anyone with physical access to your \"\n              \"%s device can spend your bitcoins.\") % plugin.device)\n        clear_pin_warning.setWordWrap(True)\n        clear_pin_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(clear_pin_button, 0, 2)\n        advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)\n\n        # Advanced tab - toggle passphrase protection\n        passphrase_button = QPushButton()\n        passphrase_button.clicked.connect(toggle_passphrase)\n        passphrase_msg = WWLabel(PASSPHRASE_HELP)\n        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)\n        passphrase_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(passphrase_button, 3, 2)\n        advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)\n        advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)\n\n        # Advanced tab - wipe device\n        wipe_device_button = QPushButton(_(\"Wipe Device\"))\n        wipe_device_button.clicked.connect(wipe_device)\n        wipe_device_msg = QLabel(\n            _(\"Wipe the device, removing all data from it.  The firmware \"\n              \"is left unchanged.\"))\n        wipe_device_msg.setWordWrap(True)\n        wipe_device_warning = QLabel(\n            _(\"Only wipe a device if you have the recovery seed written down \"\n              \"and the device wallet(s) are empty, otherwise the bitcoins \"\n              \"will be lost forever.\"))\n        wipe_device_warning.setWordWrap(True)\n        wipe_device_warning.setStyleSheet(\"color: red\")\n        advanced_glayout.addWidget(wipe_device_button, 6, 2)\n        advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)\n        advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)\n        advanced_layout.addLayout(advanced_glayout)\n        advanced_layout.addStretch(1)\n\n        tabs = QTabWidget(self)\n        tabs.addTab(info_tab, _(\"Information\"))\n        tabs.addTab(settings_tab, _(\"Settings\"))\n        tabs.addTab(advanced_tab, _(\"Advanced\"))\n        dialog_vbox = QVBoxLayout(self)\n        dialog_vbox.addWidget(tabs)\n        dialog_vbox.addLayout(Buttons(CloseButton(self)))\n\n        # Update information\n        invoke_client(None)\n"
  },
  {
    "path": "plugins/trezor/trezor.py",
    "content": "from .plugin import TrezorCompatiblePlugin, TrezorCompatibleKeyStore\n\n\nclass TrezorKeyStore(TrezorCompatibleKeyStore):\n    hw_type = 'trezor'\n    device = 'TREZOR'\n\nclass TrezorPlugin(TrezorCompatiblePlugin):\n    firmware_URL = 'https://wallet.trezor.io'\n    libraries_URL = 'https://github.com/trezor/python-trezor'\n    minimum_firmware = (1, 5, 2)\n    keystore_class = TrezorKeyStore\n\n    def __init__(self, *args):\n        try:\n            from . import client\n            import trezorlib\n            import trezorlib.ckd_public\n            import trezorlib.transport_hid\n            import trezorlib.messages\n            self.client_class = client.TrezorClient\n            self.ckd_public = trezorlib.ckd_public\n            self.types = trezorlib.messages\n            self.DEVICE_IDS = (trezorlib.transport_hid.DEV_TREZOR1, trezorlib.transport_hid.DEV_TREZOR2)\n            self.libraries_available = True\n        except ImportError:\n            self.libraries_available = False\n        TrezorCompatiblePlugin.__init__(self, *args)\n\n    def hid_transport(self, device):\n        from trezorlib.transport_hid import HidTransport\n        return HidTransport.find_by_path(device.path)\n\n    def bridge_transport(self, d):\n        from trezorlib.transport_bridge import BridgeTransport\n        return BridgeTransport(d)\n"
  },
  {
    "path": "plugins/trustedcoin/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = _('Two Factor Authentication')\ndescription = ''.join([\n    _(\"This plugin adds two-factor authentication to your wallet.\"), '<br/>',\n    _(\"For more information, visit\"),\n    \" <a href=\\\"https://api.trustedcoin.com/#/electrum-help\\\">https://api.trustedcoin.com/#/electrum-help</a>\"\n])\nrequires_wallet_type = ['2fa']\nregisters_wallet_type = '2fa'\navailable_for = ['qt', 'cmdline']\n"
  },
  {
    "path": "plugins/trustedcoin/cmdline.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - Lightweight Bitcoin Client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom electrum.i18n import _\nfrom electrum.plugins import hook\nfrom .trustedcoin import TrustedCoinPlugin\n\nclass Plugin(TrustedCoinPlugin):\n\n    @hook\n    def sign_tx(self, wallet, tx):\n        if not isinstance(wallet, self.wallet_class):\n            return\n        if not wallet.can_sign_without_server():\n            self.print_error(\"twofactor:sign_tx\")\n            auth_code = None\n            if wallet.keystores['x3/'].get_tx_derivations(tx):\n                msg = _('Please enter your Google Authenticator code:')\n                auth_code = int(input(msg))\n            else:\n                self.print_error(\"twofactor: xpub3 not needed\")\n            wallet.auth_code = auth_code\n\n"
  },
  {
    "path": "plugins/trustedcoin/qt.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - Lightweight Bitcoin Client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nfrom functools import partial\nfrom threading import Thread\nimport re\nfrom decimal import Decimal\n\nfrom PyQt5.QtGui import *\nfrom PyQt5.QtCore import *\n\nfrom electrum_gui.qt.util import *\nfrom electrum_gui.qt.qrcodewidget import QRCodeWidget\nfrom electrum_gui.qt.amountedit import AmountEdit\nfrom electrum_gui.qt.main_window import StatusBarButton\nfrom electrum.i18n import _\nfrom electrum.plugins import hook\nfrom .trustedcoin import TrustedCoinPlugin, server\n\n\nclass TOS(QTextEdit):\n    tos_signal = pyqtSignal()\n    error_signal = pyqtSignal(object)\n\n\nclass Plugin(TrustedCoinPlugin):\n\n    def __init__(self, parent, config, name):\n        super().__init__(parent, config, name)\n\n    @hook\n    def on_new_window(self, window):\n        wallet = window.wallet\n        if not isinstance(wallet, self.wallet_class):\n            return\n        if wallet.can_sign_without_server():\n            msg = ' '.join([\n                _('This wallet was restored from seed, and it contains two master private keys.'),\n                _('Therefore, two-factor authentication is disabled.')\n            ])\n            action = lambda: window.show_message(msg)\n        else:\n            action = partial(self.settings_dialog, window)\n        button = StatusBarButton(QIcon(\":icons/trustedcoin-status.png\"),\n                                 _(\"TrustedCoin\"), action)\n        window.statusBar().addPermanentWidget(button)\n        self.start_request_thread(window.wallet)\n\n    def auth_dialog(self, window):\n        d = WindowModalDialog(window, _(\"Authorization\"))\n        vbox = QVBoxLayout(d)\n        pw = AmountEdit(None, is_int = True)\n        msg = _('Please enter your Google Authenticator code')\n        vbox.addWidget(QLabel(msg))\n        grid = QGridLayout()\n        grid.setSpacing(8)\n        grid.addWidget(QLabel(_('Code')), 1, 0)\n        grid.addWidget(pw, 1, 1)\n        vbox.addLayout(grid)\n        msg = _('If you have lost your second factor, you need to restore your wallet from seed in order to request a new code.')\n        label = QLabel(msg)\n        label.setWordWrap(1)\n        vbox.addWidget(label)\n        vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))\n        if not d.exec_():\n            return\n        return pw.get_amount()\n\n    @hook\n    def sign_tx(self, window, tx):\n        wallet = window.wallet\n        if not isinstance(wallet, self.wallet_class):\n            return\n        if not wallet.can_sign_without_server():\n            self.print_error(\"twofactor:sign_tx\")\n            auth_code = None\n            if wallet.keystores['x3/'].get_tx_derivations(tx):\n                auth_code = self.auth_dialog(window)\n            else:\n                self.print_error(\"twofactor: xpub3 not needed\")\n            window.wallet.auth_code = auth_code\n\n    def waiting_dialog(self, window, on_finished=None):\n        task = partial(self.request_billing_info, window.wallet)\n        return WaitingDialog(window, 'Getting billing information...', task,\n                             on_finished)\n\n    @hook\n    def abort_send(self, window):\n        wallet = window.wallet\n        if not isinstance(wallet, self.wallet_class):\n            return\n        if wallet.can_sign_without_server():\n            return\n        if wallet.billing_info is None:\n            return True\n        return False\n\n\n    def settings_dialog(self, window):\n        self.waiting_dialog(window, partial(self.show_settings_dialog, window))\n\n    def show_settings_dialog(self, window, success):\n        if not success:\n            window.show_message(_('Server not reachable.'))\n            return\n\n        wallet = window.wallet\n        d = WindowModalDialog(window, _(\"TrustedCoin Information\"))\n        d.setMinimumSize(500, 200)\n        vbox = QVBoxLayout(d)\n        hbox = QHBoxLayout()\n\n        logo = QLabel()\n        logo.setPixmap(QPixmap(\":icons/trustedcoin-status.png\"))\n        msg = _('This wallet is protected by TrustedCoin\\'s two-factor authentication.') + '<br/>'\\\n              + _(\"For more information, visit\") + \" <a href=\\\"https://api.trustedcoin.com/#/electrum-help\\\">https://api.trustedcoin.com/#/electrum-help</a>\"\n        label = QLabel(msg)\n        label.setOpenExternalLinks(1)\n\n        hbox.addStretch(10)\n        hbox.addWidget(logo)\n        hbox.addStretch(10)\n        hbox.addWidget(label)\n        hbox.addStretch(10)\n\n        vbox.addLayout(hbox)\n        vbox.addStretch(10)\n\n        msg = _('TrustedCoin charges a small fee to co-sign transactions. The fee depends on how many prepaid transactions you buy. An extra output is added to your transaction everytime you run out of prepaid transactions.') + '<br/>'\n        label = QLabel(msg)\n        label.setWordWrap(1)\n        vbox.addWidget(label)\n\n        vbox.addStretch(10)\n        grid = QGridLayout()\n        vbox.addLayout(grid)\n\n        price_per_tx = wallet.price_per_tx\n        n_prepay = wallet.num_prepay(self.config)\n        i = 0\n        for k, v in sorted(price_per_tx.items()):\n            if k == 1:\n                continue\n            grid.addWidget(QLabel(\"Pay every %d transactions:\"%k), i, 0)\n            grid.addWidget(QLabel(window.format_amount(v/k) + ' ' + window.base_unit() + \"/tx\"), i, 1)\n            b = QRadioButton()\n            b.setChecked(k == n_prepay)\n            b.clicked.connect(lambda b, k=k: self.config.set_key('trustedcoin_prepay', k, True))\n            grid.addWidget(b, i, 2)\n            i += 1\n\n        n = wallet.billing_info.get('tx_remaining', 0)\n        grid.addWidget(QLabel(_(\"Your wallet has %d prepaid transactions.\")%n), i, 0)\n        vbox.addLayout(Buttons(CloseButton(d)))\n        d.exec_()\n\n    def on_buy(self, window, k, v, d):\n        d.close()\n        if window.pluginsdialog:\n            window.pluginsdialog.close()\n        wallet = window.wallet\n        uri = \"bitcoin:\" + wallet.billing_info['billing_address'] + \"?message=TrustedCoin %d Prepaid Transactions&amount=\"%k + str(Decimal(v)/100000000)\n        wallet.is_billing = True\n        window.pay_to_URI(uri)\n        window.payto_e.setFrozen(True)\n        window.message_e.setFrozen(True)\n        window.amount_e.setFrozen(True)\n\n    def accept_terms_of_use(self, window):\n        vbox = QVBoxLayout()\n        vbox.addWidget(QLabel(_(\"Terms of Service\")))\n\n        tos_e = TOS()\n        tos_e.setReadOnly(True)\n        vbox.addWidget(tos_e)\n        tos_received = False\n\n        vbox.addWidget(QLabel(_(\"Please enter your e-mail address\")))\n        email_e = QLineEdit()\n        vbox.addWidget(email_e)\n\n        next_button = window.next_button\n        prior_button_text = next_button.text()\n        next_button.setText(_('Accept'))\n\n        def request_TOS():\n            try:\n                tos = server.get_terms_of_service()\n            except Exception as e:\n                import traceback\n                traceback.print_exc(file=sys.stderr)\n                tos_e.error_signal.emit(_('Could not retrieve Terms of Service:')\n                                        + '\\n' + str(e))\n                return\n            self.TOS = tos\n            tos_e.tos_signal.emit()\n\n        def on_result():\n            tos_e.setText(self.TOS)\n            nonlocal tos_received\n            tos_received = True\n            set_enabled()\n\n        def on_error(msg):\n            window.show_error(str(msg))\n            window.terminate()\n\n        def set_enabled():\n            valid_email = re.match(regexp, email_e.text()) is not None\n            next_button.setEnabled(tos_received and valid_email)\n\n        tos_e.tos_signal.connect(on_result)\n        tos_e.error_signal.connect(on_error)\n        t = Thread(target=request_TOS)\n        t.setDaemon(True)\n        t.start()\n\n        regexp = r\"[^@]+@[^@]+\\.[^@]+\"\n        email_e.textChanged.connect(set_enabled)\n        email_e.setFocus(True)\n\n        window.exec_layout(vbox, next_enabled=False)\n        next_button.setText(prior_button_text)\n        return str(email_e.text())\n\n    def request_otp_dialog(self, window, _id, otp_secret):\n        vbox = QVBoxLayout()\n        if otp_secret is not None:\n            uri = \"otpauth://totp/%s?secret=%s\"%('trustedcoin.com', otp_secret)\n            l = QLabel(\"Please scan the following QR code in Google Authenticator. You may as well use the following key: %s\"%otp_secret)\n            l.setWordWrap(True)\n            vbox.addWidget(l)\n            qrw = QRCodeWidget(uri)\n            vbox.addWidget(qrw, 1)\n            msg = _('Then, enter your Google Authenticator code:')\n        else:\n            label = QLabel(\n                \"This wallet is already registered with Trustedcoin. \"\n                \"To finalize wallet creation, please enter your Google Authenticator Code. \"\n            )\n            label.setWordWrap(1)\n            vbox.addWidget(label)\n            msg = _('Google Authenticator code:')\n\n        hbox = QHBoxLayout()\n        hbox.addWidget(WWLabel(msg))\n        pw = AmountEdit(None, is_int = True)\n        pw.setFocus(True)\n        pw.setMaximumWidth(50)\n        hbox.addWidget(pw)\n        vbox.addLayout(hbox)\n\n        cb_lost = QCheckBox(_(\"I have lost my Google Authenticator account\"))\n        cb_lost.setToolTip(_(\"Check this box to request a new secret. You will need to retype your seed.\"))\n        vbox.addWidget(cb_lost)\n        cb_lost.setVisible(otp_secret is None)\n\n        def set_enabled():\n            b = True if cb_lost.isChecked() else len(pw.text()) == 6\n            window.next_button.setEnabled(b)\n\n        pw.textChanged.connect(set_enabled)\n        cb_lost.toggled.connect(set_enabled)\n\n        window.exec_layout(vbox, next_enabled=False,\n                               raise_on_cancel=False)\n        return pw.get_amount(), cb_lost.isChecked()\n\n\n"
  },
  {
    "path": "plugins/trustedcoin/trustedcoin.py",
    "content": "#!/usr/bin/env python\n#\n# Electrum - Lightweight Bitcoin Client\n# Copyright (C) 2015 Thomas Voegtlin\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation files\n# (the \"Software\"), to deal in the Software without restriction,\n# including without limitation the rights to use, copy, modify, merge,\n# publish, distribute, sublicense, and/or sell copies of the Software,\n# and to permit persons to whom the Software is furnished to do so,\n# subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nimport socket\nimport os\nimport requests\nimport json\nfrom urllib.parse import urljoin\nfrom urllib.parse import quote\n\nimport electrum\nfrom electrum import bitcoin\nfrom electrum import keystore\nfrom electrum.bitcoin import *\nfrom electrum.mnemonic import Mnemonic\nfrom electrum import version\nfrom electrum.wallet import Multisig_Wallet, Deterministic_Wallet\nfrom electrum.i18n import _\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.util import NotEnoughFunds\n\n# signing_xpub is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server\nsigning_xpub = \"xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL\"\nbilling_xpub = \"xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU\"\n\nSEED_PREFIX = version.SEED_PREFIX_2FA\n\nDISCLAIMER = [\n    _(\"Two-factor authentication is a service provided by TrustedCoin.  \"\n      \"It uses a multi-signature wallet, where you own 2 of 3 keys.  \"\n      \"The third key is stored on a remote server that signs transactions on \"\n      \"your behalf.  To use this service, you will need a smartphone with \"\n      \"Google Authenticator installed.\"),\n    _(\"A small fee will be charged on each transaction that uses the \"\n      \"remote server.  You may check and modify your billing preferences \"\n      \"once the installation is complete.\"),\n    _(\"Note that your coins are not locked in this service.  You may withdraw \"\n      \"your funds at any time and at no cost, without the remote server, by \"\n      \"using the 'restore wallet' option with your wallet seed.\"),\n    _(\"The next step will generate the seed of your wallet.  This seed will \"\n      \"NOT be saved in your computer, and it must be stored on paper.  \"\n      \"To be safe from malware, you may want to do this on an offline \"\n      \"computer, and move your wallet later to an online computer.\"),\n]\nRESTORE_MSG = _(\"Enter the seed for your 2-factor wallet:\")\n\nclass TrustedCoinException(Exception):\n    def __init__(self, message, status_code=0):\n        Exception.__init__(self, message)\n        self.status_code = status_code\n\nclass TrustedCoinCosignerClient(object):\n    def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/'):\n        self.base_url = base_url\n        self.debug = False\n        self.user_agent = user_agent\n\n    def send_request(self, method, relative_url, data=None):\n        kwargs = {'headers': {}}\n        if self.user_agent:\n            kwargs['headers']['user-agent'] = self.user_agent\n        if method == 'get' and data:\n            kwargs['params'] = data\n        elif method == 'post' and data:\n            kwargs['data'] = json.dumps(data)\n            kwargs['headers']['content-type'] = 'application/json'\n        url = urljoin(self.base_url, relative_url)\n        if self.debug:\n            print('%s %s %s' % (method, url, data))\n        response = requests.request(method, url, **kwargs)\n        if self.debug:\n            print(response.text)\n        if response.status_code != 200:\n            message = str(response.text)\n            if response.headers.get('content-type') == 'application/json':\n                r = response.json()\n                if 'message' in r:\n                    message = r['message']\n            raise TrustedCoinException(message, response.status_code)\n        if response.headers.get('content-type') == 'application/json':\n            return response.json()\n        else:\n            return response.text\n\n    def get_terms_of_service(self, billing_plan='electrum-per-tx-otp'):\n        \"\"\"\n        Returns the TOS for the given billing plan as a plain/text unicode string.\n        :param billing_plan: the plan to return the terms for\n        \"\"\"\n        payload = {'billing_plan': billing_plan}\n        return self.send_request('get', 'tos', payload)\n\n    def create(self, xpubkey1, xpubkey2, email, billing_plan='electrum-per-tx-otp'):\n        \"\"\"\n        Creates a new cosigner resource.\n        :param xpubkey1: a bip32 extended public key (customarily the hot key)\n        :param xpubkey2: a bip32 extended public key (customarily the cold key)\n        :param email: a contact email\n        :param billing_plan: the billing plan for the cosigner\n        \"\"\"\n        payload = {\n            'email': email,\n            'xpubkey1': xpubkey1,\n            'xpubkey2': xpubkey2,\n            'billing_plan': billing_plan,\n        }\n        return self.send_request('post', 'cosigner', payload)\n\n    def auth(self, id, otp):\n        \"\"\"\n        Attempt to authenticate for a particular cosigner.\n        :param id: the id of the cosigner\n        :param otp: the one time password\n        \"\"\"\n        payload = {'otp': otp}\n        return self.send_request('post', 'cosigner/%s/auth' % quote(id), payload)\n\n    def get(self, id):\n        \"\"\" Get billing info \"\"\"\n        return self.send_request('get', 'cosigner/%s' % quote(id))\n\n    def get_challenge(self, id):\n        \"\"\" Get challenge to reset Google Auth secret \"\"\"\n        return self.send_request('get', 'cosigner/%s/otp_secret' % quote(id))\n\n    def reset_auth(self, id, challenge, signatures):\n        \"\"\" Reset Google Auth secret \"\"\"\n        payload = {'challenge':challenge, 'signatures':signatures}\n        return self.send_request('post', 'cosigner/%s/otp_secret' % quote(id), payload)\n\n    def sign(self, id, transaction, otp):\n        \"\"\"\n        Attempt to authenticate for a particular cosigner.\n        :param id: the id of the cosigner\n        :param transaction: the hex encoded [partially signed] compact transaction to sign\n        :param otp: the one time password\n        \"\"\"\n        payload = {\n            'otp': otp,\n            'transaction': transaction\n        }\n        return self.send_request('post', 'cosigner/%s/sign' % quote(id), payload)\n\n    def transfer_credit(self, id, recipient, otp, signature_callback):\n        \"\"\"\n        Tranfer a cosigner's credits to another cosigner.\n        :param id: the id of the sending cosigner\n        :param recipient: the id of the recipient cosigner\n        :param otp: the one time password (of the sender)\n        :param signature_callback: a callback that signs a text message using xpubkey1/0/0 returning a compact sig\n        \"\"\"\n        payload = {\n            'otp': otp,\n            'recipient': recipient,\n            'timestamp': int(time.time()),\n\n        }\n        relative_url = 'cosigner/%s/transfer' % quote(id)\n        full_url = urljoin(self.base_url, relative_url)\n        headers = {\n            'x-signature': signature_callback(full_url + '\\n' + json.dumps(payload))\n        }\n        return self.send_request('post', relative_url, payload, headers)\n\n\nserver = TrustedCoinCosignerClient(user_agent=\"Electrum/\" + version.ELECTRUM_VERSION)\n\nclass Wallet_2fa(Multisig_Wallet):\n\n    wallet_type = '2fa'\n\n    def __init__(self, storage):\n        self.m, self.n = 2, 3\n        Deterministic_Wallet.__init__(self, storage)\n        self.is_billing = False\n        self.billing_info = None\n\n    def can_sign_without_server(self):\n        return not self.keystores['x2/'].is_watching_only()\n\n    def get_user_id(self):\n        return get_user_id(self.storage)\n\n    def get_max_amount(self, config, inputs, recipient, fee):\n        from electrum.transaction import Transaction\n        sendable = sum(map(lambda x:x['value'], inputs))\n        for i in inputs:\n            self.add_input_info(i)\n        xf = self.extra_fee(config)\n        _type, addr = recipient\n        if xf and sendable >= xf:\n            billing_address = self.billing_info['billing_address']\n            sendable -= xf\n            outputs = [(_type, addr, sendable),\n                       (TYPE_ADDRESS, billing_address, xf)]\n        else:\n            outputs = [(_type, addr, sendable)]\n        dummy_tx = Transaction.from_io(inputs, outputs)\n        if fee is None:\n            fee = self.estimate_fee(config, dummy_tx.estimated_size())\n        amount = max(0, sendable - fee)\n        return amount, fee\n\n    def min_prepay(self):\n        return min(self.price_per_tx.keys())\n\n    def num_prepay(self, config):\n        default = self.min_prepay()\n        n = config.get('trustedcoin_prepay', default)\n        if n not in self.price_per_tx:\n            n = default\n        return n\n\n    def extra_fee(self, config):\n        if self.can_sign_without_server():\n            return 0\n        if self.billing_info is None:\n            self.plugin.start_request_thread(self)\n            return 0\n        if self.billing_info.get('tx_remaining'):\n            return 0\n        if self.is_billing:\n            return 0\n        n = self.num_prepay(config)\n        price = int(self.price_per_tx[n])\n        assert price <= 100000 * n\n        return price\n\n    def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,\n                                  change_addr=None, is_sweep=False):\n        mk_tx = lambda o: Multisig_Wallet.make_unsigned_transaction(\n            self, coins, o, config, fixed_fee, change_addr)\n        fee = self.extra_fee(config) if not is_sweep else 0\n        if fee:\n            address = self.billing_info['billing_address']\n            fee_output = (TYPE_ADDRESS, address, fee)\n            try:\n                tx = mk_tx(outputs + [fee_output])\n            except NotEnoughFunds:\n                # trustedcoin won't charge if the total inputs is\n                # lower than their fee\n                tx = mk_tx(outputs)\n                if tx.input_value() >= fee:\n                    raise\n                self.print_error(\"not charging for this tx\")\n        else:\n            tx = mk_tx(outputs)\n        return tx\n\n    def sign_transaction(self, tx, password):\n        Multisig_Wallet.sign_transaction(self, tx, password)\n        if tx.is_complete():\n            return\n        if not self.auth_code:\n            self.print_error(\"sign_transaction: no auth code\")\n            return\n        long_user_id, short_id = self.get_user_id()\n        tx_dict = tx.as_dict()\n        raw_tx = tx_dict[\"hex\"]\n        r = server.sign(short_id, raw_tx, self.auth_code)\n        if r:\n            raw_tx = r.get('transaction')\n            tx.update(raw_tx)\n        self.print_error(\"twofactor: is complete\", tx.is_complete())\n        # reset billing_info\n        self.billing_info = None\n\n\n# Utility functions\n\ndef get_user_id(storage):\n    def make_long_id(xpub_hot, xpub_cold):\n        return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))\n    xpub1 = storage.get('x1/')['xpub']\n    xpub2 = storage.get('x2/')['xpub']\n    long_id = make_long_id(xpub1, xpub2)\n    short_id = hashlib.sha256(long_id).hexdigest()\n    return long_id, short_id\n\ndef make_xpub(xpub, s):\n    version, _, _, _, c, cK = deserialize_xpub(xpub)\n    cK2, c2 = bitcoin._CKD_pub(cK, c, s)\n    return bitcoin.serialize_xpub(version, c2, cK2)\n\ndef make_billing_address(wallet, num):\n    long_id, short_id = wallet.get_user_id()\n    xpub = make_xpub(billing_xpub, long_id)\n    version, _, _, _, c, cK = deserialize_xpub(xpub)\n    cK, c = bitcoin.CKD_pub(cK, c, num)\n    return bitcoin.public_key_to_p2pkh(cK)\n\n\nclass TrustedCoinPlugin(BasePlugin):\n    wallet_class = Wallet_2fa\n\n    def __init__(self, parent, config, name):\n        BasePlugin.__init__(self, parent, config, name)\n        self.wallet_class.plugin = self\n        self.requesting = False\n\n    @staticmethod\n    def is_valid_seed(seed):\n        return bitcoin.is_new_seed(seed, SEED_PREFIX)\n\n    def is_available(self):\n        return True\n\n    def is_enabled(self):\n        return True\n\n    def can_user_disable(self):\n        return False\n\n    @hook\n    def get_tx_extra_fee(self, wallet, tx):\n        if type(wallet) != Wallet_2fa:\n            return\n        address = wallet.billing_info['billing_address']\n        for _type, addr, amount in tx.outputs():\n            if _type == TYPE_ADDRESS and addr == address:\n                return address, amount\n\n    def request_billing_info(self, wallet):\n        self.print_error(\"request billing info\")\n        billing_info = server.get(wallet.get_user_id()[1])\n        billing_address = make_billing_address(wallet, billing_info['billing_index'])\n        assert billing_address == billing_info['billing_address']\n        wallet.billing_info = billing_info\n        wallet.price_per_tx = dict(billing_info['price_per_tx'])\n        wallet.price_per_tx.pop(1)\n        self.requesting = False\n        return True\n\n    def start_request_thread(self, wallet):\n        from threading import Thread\n        if self.requesting is False:\n            self.requesting = True\n            t = Thread(target=self.request_billing_info, args=(wallet,))\n            t.setDaemon(True)\n            t.start()\n            return t\n\n    def make_seed(self):\n        return Mnemonic('english').make_seed(seed_type='2fa', num_bits=128)\n\n    @hook\n    def do_clear(self, window):\n        window.wallet.is_billing = False\n\n    def show_disclaimer(self, wizard):\n        wizard.set_icon(':icons/trustedcoin-wizard.png')\n        wizard.stack = []\n        wizard.confirm_dialog(title='Disclaimer', message='\\n\\n'.join(DISCLAIMER), run_next = lambda x: wizard.run('choose_seed'))\n\n    def choose_seed(self, wizard):\n        title = _('Create or restore')\n        message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')\n        choices = [\n            ('create_seed', _('Create a new seed')),\n            ('restore_wallet', _('I already have a seed')),\n        ]\n        wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)\n\n    def create_seed(self, wizard):\n        seed = self.make_seed()\n        f = lambda x: wizard.request_passphrase(seed, x)\n        wizard.show_seed_dialog(run_next=f, seed_text=seed)\n\n    @classmethod\n    def get_xkeys(self, seed, passphrase, derivation):\n        from electrum.mnemonic import Mnemonic\n        from electrum.keystore import bip32_root, bip32_private_derivation\n        bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)\n        xprv, xpub = bip32_root(bip32_seed, 'standard')\n        xprv, xpub = bip32_private_derivation(xprv, \"m/\", derivation)\n        return xprv, xpub\n\n    @classmethod\n    def xkeys_from_seed(self, seed, passphrase):\n        words = seed.split()\n        n = len(words)\n        # old version use long seed phrases\n        if n >= 24:\n            assert passphrase == ''\n            xprv1, xpub1 = self.get_xkeys(' '.join(words[0:12]), '', \"m/\")\n            xprv2, xpub2 = self.get_xkeys(' '.join(words[12:]), '', \"m/\")\n        elif n==12:\n            xprv1, xpub1 = self.get_xkeys(seed, passphrase, \"m/0'/\")\n            xprv2, xpub2 = self.get_xkeys(seed, passphrase, \"m/1'/\")\n        else:\n            raise BaseException('unrecognized seed length')\n        return xprv1, xpub1, xprv2, xpub2\n\n    def create_keystore(self, wizard, seed, passphrase):\n        # this overloads the wizard's method\n        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)\n        k1 = keystore.from_xprv(xprv1)\n        k2 = keystore.from_xpub(xpub2)\n        wizard.request_password(run_next=lambda pw, encrypt: self.on_password(wizard, pw, encrypt, k1, k2))\n\n    def on_password(self, wizard, password, encrypt, k1, k2):\n        k1.update_password(None, password)\n        wizard.storage.set_password(password, encrypt)\n        wizard.storage.put('x1/', k1.dump())\n        wizard.storage.put('x2/', k2.dump())\n        wizard.storage.write()\n        msg = [\n            _(\"Your wallet file is: %s.\")%os.path.abspath(wizard.storage.path),\n            _(\"You need to be online in order to complete the creation of \"\n              \"your wallet.  If you generated your seed on an offline \"\n              'computer, click on \"%s\" to close this window, move your '\n              \"wallet file to an online computer, and reopen it with \"\n              \"Electrum.\") % _('Cancel'),\n            _('If you are online, click on \"%s\" to continue.') % _('Next')\n        ]\n        msg = '\\n\\n'.join(msg)\n        wizard.stack = []\n        wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('create_remote_key'))\n\n    def restore_wallet(self, wizard):\n        wizard.opt_bip39 = False\n        wizard.opt_ext = True\n        title = _(\"Restore two-factor Wallet\")\n        f = lambda seed, is_bip39, is_ext: wizard.run('on_restore_seed', seed, is_ext)\n        wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)\n\n    def on_restore_seed(self, wizard, seed, is_ext):\n        f = lambda x: self.restore_choice(wizard, seed, x)\n        wizard.passphrase_dialog(run_next=f) if is_ext else f('')\n\n    def restore_choice(self, wizard, seed, passphrase):\n        wizard.set_icon(':icons/trustedcoin-wizard.png')\n        wizard.stack = []\n        title = _('Restore 2FA wallet')\n        msg = ' '.join([\n            'You are going to restore a wallet protected with two-factor authentication.',\n            'Do you want to keep using two-factor authentication with this wallet,',\n            'or do you want to disable it, and have two master private keys in your wallet?'\n        ])\n        choices = [('keep', 'Keep'), ('disable', 'Disable')]\n        f = lambda x: self.on_choice(wizard, seed, passphrase, x)\n        wizard.choice_dialog(choices=choices, message=msg, title=title, run_next=f)\n\n    def on_choice(self, wizard, seed, passphrase, x):\n        if x == 'disable':\n            f = lambda pw, encrypt: wizard.run('on_restore_pw', seed, passphrase, pw, encrypt)\n            wizard.request_password(run_next=f)\n        else:\n            self.create_keystore(wizard, seed, passphrase)\n\n    def on_restore_pw(self, wizard, seed, passphrase, password, encrypt):\n        storage = wizard.storage\n        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)\n        k1 = keystore.from_xprv(xprv1)\n        k2 = keystore.from_xprv(xprv2)\n        k1.add_seed(seed)\n        k1.update_password(None, password)\n        k2.update_password(None, password)\n        storage.put('x1/', k1.dump())\n        storage.put('x2/', k2.dump())\n        long_user_id, short_id = get_user_id(storage)\n        xpub3 = make_xpub(signing_xpub, long_user_id)\n        k3 = keystore.from_xpub(xpub3)\n        storage.put('x3/', k3.dump())\n        storage.set_password(password, encrypt)\n        wizard.wallet = Wallet_2fa(storage)\n        wizard.create_addresses()\n\n    def create_remote_key(self, wizard):\n        email = self.accept_terms_of_use(wizard)\n        xpub1 = wizard.storage.get('x1/')['xpub']\n        xpub2 = wizard.storage.get('x2/')['xpub']\n        # Generate third key deterministically.\n        long_user_id, short_id = get_user_id(wizard.storage)\n        xpub3 = make_xpub(signing_xpub, long_user_id)\n        # secret must be sent by the server\n        try:\n            r = server.create(xpub1, xpub2, email)\n        except socket.error:\n            wizard.show_message('Server not reachable, aborting')\n            return\n        except TrustedCoinException as e:\n            if e.status_code == 409:\n                r = None\n            else:\n                wizard.show_message(str(e))\n                return\n        if r is None:\n            otp_secret = None\n        else:\n            otp_secret = r.get('otp_secret')\n            if not otp_secret:\n                wizard.show_message(_('Error'))\n                return\n            _xpub3 = r['xpubkey_cosigner']\n            _id = r['id']\n            try:\n                assert _id == short_id, (\"user id error\", _id, short_id)\n                assert xpub3 == _xpub3, (\"xpub3 error\", xpub3, _xpub3)\n            except Exception as e:\n                wizard.show_message(str(e))\n                return\n        self.check_otp(wizard, short_id, otp_secret, xpub3)\n\n    def check_otp(self, wizard, short_id, otp_secret, xpub3):\n        otp, reset = self.request_otp_dialog(wizard, short_id, otp_secret)\n        if otp:\n            self.do_auth(wizard, short_id, otp, xpub3)\n        elif reset:\n            wizard.opt_bip39 = False\n            wizard.opt_ext = True\n            f = lambda seed, is_bip39, is_ext: wizard.run('on_reset_seed', short_id, seed, is_ext, xpub3)\n            wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)\n\n    def on_reset_seed(self, wizard, short_id, seed, is_ext, xpub3):\n        f = lambda passphrase: wizard.run('on_reset_auth', short_id, seed, passphrase, xpub3)\n        wizard.passphrase_dialog(run_next=f) if is_ext else f('')\n\n    def do_auth(self, wizard, short_id, otp, xpub3):\n        try:\n            server.auth(short_id, otp)\n        except:\n            wizard.show_message(_('Incorrect password'))\n            return\n        k3 = keystore.from_xpub(xpub3)\n        wizard.storage.put('x3/', k3.dump())\n        wizard.storage.put('use_trustedcoin', True)\n        wizard.storage.write()\n        wizard.wallet = Wallet_2fa(wizard.storage)\n        wizard.run('create_addresses')\n\n    def on_reset_auth(self, wizard, short_id, seed, passphrase, xpub3):\n        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)\n        try:\n            assert xpub1 == wizard.storage.get('x1/')['xpub']\n            assert xpub2 == wizard.storage.get('x2/')['xpub']\n        except:\n            wizard.show_message(_('Incorrect seed'))\n            return\n        r = server.get_challenge(short_id)\n        challenge = r.get('challenge')\n        message = 'TRUSTEDCOIN CHALLENGE: ' + challenge\n        def f(xprv):\n            _, _, _, _, c, k = deserialize_xprv(xprv)\n            pk = bip32_private_key([0, 0], k, c)\n            key = regenerate_key(pk)\n            sig = key.sign_message(message, True)\n            return base64.b64encode(sig).decode()\n\n        signatures = [f(x) for x in [xprv1, xprv2]]\n        r = server.reset_auth(short_id, challenge, signatures)\n        new_secret = r.get('otp_secret')\n        if not new_secret:\n            wizard.show_message(_('Request rejected by server'))\n            return\n        self.check_otp(wizard, short_id, new_secret, xpub3)\n\n    @hook\n    def get_action(self, storage):\n        if storage.get('wallet_type') != '2fa':\n            return\n        if not storage.get('x1/'):\n            return self, 'show_disclaimer'\n        if not storage.get('x2/'):\n            return self, 'show_disclaimer'\n        if not storage.get('x3/'):\n            return self, 'create_remote_key'\n"
  },
  {
    "path": "plugins/virtualkeyboard/__init__.py",
    "content": "from electrum.i18n import _\n\nfullname = 'Virtual Keyboard'\ndescription = '%s\\n%s' % (_(\"Add an optional virtual keyboard to the password dialog.\"), _(\"Warning: do not use this if it makes you pick a weaker password.\"))\navailable_for = ['qt']\n"
  },
  {
    "path": "plugins/virtualkeyboard/qt.py",
    "content": "from PyQt5.QtGui import *\nfrom PyQt5.QtWidgets import (QVBoxLayout, QGridLayout, QPushButton)\nfrom electrum.plugins import BasePlugin, hook\nfrom electrum.i18n import _\nimport random\n\n\nclass Plugin(BasePlugin):\n    vkb = None\n    vkb_index = 0\n\n    @hook\n    def password_dialog(self, pw, grid, pos):\n        vkb_button = QPushButton(_(\"+\"))\n        vkb_button.setFixedWidth(20)\n        vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw))\n        grid.addWidget(vkb_button, pos, 2)\n        self.kb_pos = 2\n        self.vkb = None\n\n    def toggle_vkb(self, grid, pw):\n        if self.vkb:\n            grid.removeItem(self.vkb)\n        self.vkb = self.virtual_keyboard(self.vkb_index, pw)\n        grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3)\n        self.vkb_index += 1\n\n    def virtual_keyboard(self, i, pw):\n        i = i % 3\n        if i == 0:\n            chars = 'abcdefghijklmnopqrstuvwxyz '\n        elif i == 1:\n            chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ '\n        elif i == 2:\n            chars = '1234567890!?.,;:/%&()[]{}+-'\n\n        n = len(chars)\n        s = []\n        for i in range(n):\n            while True:\n                k = random.randint(0, n - 1)\n                if k not in s:\n                    s.append(k)\n                    break\n\n        def add_target(t):\n            return lambda: pw.setText(str(pw.text()) + t)\n\n        vbox = QVBoxLayout()\n        grid = QGridLayout()\n        grid.setSpacing(2)\n        for i in range(n):\n            l_button = QPushButton(chars[s[i]])\n            l_button.setFixedWidth(25)\n            l_button.setFixedHeight(25)\n            l_button.clicked.connect(add_target(chars[s[i]]))\n            grid.addWidget(l_button, i // 6, i % 6)\n\n        vbox.addLayout(grid)\n\n        return vbox\n"
  },
  {
    "path": "pubkeys/Animazing.asc",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: SKS 1.1.4\nComment: Hostname: keys.fedoraproject.org\n\nmQENBFD1gzgBCADGVp8wVMk0viUTR3FBzCfdGGULYcyiKFNdGlKxORyrdWJF6/g5JuYPU0NX\nCRJ7hqX4hopOiID+oxE7p/vP/i/Sm6B6xZMq8bJ+PiJ2h8ZqnourgL8tkAqlDV+zLana+XeQ\nPsPhJPeARAQDtl5QhQbvWm0idMyd1zuWdt4OVIYnhJ7w7Mw0CdUmBbkTc1P23J8vwqiyyuHq\nV3JimkNJLm4vyWGFig6ElgRMbV5YWci41OTH3x8qkWHFdB1h0ODP/28bBwpVVXqnObrp/Lsr\n9aQSP5VujGIL/cOdel/7xD2Dc5qS/HXYZgJUk6x2WkLmO47NSpW6rone3W2A82gkKRwXABEB\nAAG0H0FuaW1hemluZyA8YW5pbWF6aW5nQGdtYWlsLmNvbT6JATgEEwECACIFAlD1gzgCGwMG\nCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECJFMARpVQb95i8IAISo2NvZW8Zln4ivuo5l\n6PirIrzSIlec3Xa/xmkgrHTfBVT3WYeGp54NkEcpgarD1qjt5ZFF7wExn9WLErGlsIkPmegO\nZjx2mn3ZyB+Y5y0cJeGPM72aD0Z3jaBi+htv37GMb2mV6yO700dG1x3vwS1YYyRLgR6pVwlR\nw+0l+5B/5zHsKdRvpRcuTpmrg8rtgTxZOsRyORn6ck6w7lUbno3+1XMBTFTbVf4/SymMU3Wl\n9eru2agYRfkB3puXd8DMYlSzZiiUWTHV6bOsB0N001sJtmFf6KmM1xF8Q/eqMlWevS2sd0XG\nOWFQu4RGyAJqN7h1Z3cym278WVCMhYzJn/SJAhwEEAECAAYFAlHaodMACgkQuW8jAK0Ry+6x\nohAAj1Cka1AoIDmPcU5IlPLPwsYoyfLmkAbcpeNKZA6Yf4pm96i3tKXbLgCxSrejSmraVIYZ\noNGFtmAx+rpwdZtDWyFWGlMkDhjRY1rQ6nlkO8CtPU/brbyZJci2J67U7hUsJt+O5gaftclG\n2RcC61BQz3Ee0Fl8JKSE5V2KqOD2W1BTCTYrnpY5YdMB8qSalR8txZaSqkA3xpSrIwJ04EHT\n1uyQAJiacQ0ynHpcU/xfVBnzj3Hc0mRxBTI5Nznk8NAHleY5OSiZX5YJb3mvycdV34mGXnna\nyk9S8HotWs5kLpMQ2TpXOiZK4/lJBlp+q3ksj+01qoqTrRXijI+6z9QRS6y7AkSWO0PaV9Ui\nuARH5bBV5qzzT2q9vtYTEkoGHj1IyoW70l2TxyfQLQBVNcYp864+TQJmxiVIVUEMSsJ7Vnoi\nfzIvgBz8/d62POQH7zvmnWA7VvYD8Pn3dXVOF7sSOxqJ+YLxcOkjpgqI8ef0Jiypa0w5Tguj\npJ/SVWpeP410SlK4w+ICmxc3shR/pdJfVhgRGS6e8A4GCHnDHg8ksLJ8qoUmPlOWJjdfjakz\nQD5vvAV0KwWQ0mu9ca3C3NjivYSWZdiHzeQE5hXqVV8wlghshFKOcm81vdB/hsay5hthilSS\nL9trmnNdYNppZHc0bV6PYpYpHWaD1NpWQSbIeiW5AQ0EUPWDOAEIAMzeN8NjFRB53Fid/Iag\nQxxQZeD1Y9cn9S6fJ+nEoZWMPQ+iVCictH3gmqm+eZ0qOrqSTxkXgyIh7Uiu2lwjm7nmeBK3\n28k2ei1e3bpp0I2oMiShVAehxbXx5Jh29nlPDS8US/xehj6WnUeojR2qXVOSAtK7+V7taSyi\ngOpYr/+5fcq+/8Z+d+NaVp97MUmoH5LVNU2jCR+qyIM07qX2G4Vx+hU4tjc79Kl6m1equ6/s\nHwnkmMIsGvLHZG+Pq+1wHIZwt1p39MhFvob9B2RFqbFtihTal36D0NBQMdlp9PtKtQZSQR5h\nWsmWG+m17/vyux7ZtwnTRbR+p/adFx6fsTcAEQEAAYkBHwQYAQIACQUCUPWDOAIbDAAKCRAi\nRTAEaVUG/aARB/9pYfkB4rDe4OulrUXBusTxOGKm1yPjZaXveOqQkD8/2Vwxu7tYFMVMLDKY\ntvxpmrUdb6oz9s8XrTkMIW7ZM3FmhNQhxsfEj9g6UGJnCXcf+Zw9wbqmzONNCupfN5wPOtoG\nd+KFUR5dVl3+BsUoIEcAcPR5AdP9gbqDSbrDyuk8+j1CfiLVAQXud3u2rkt8GWbTJ/LzKtF1\nUM0X3YnE7fxi61DDgX6A1EJfss3aFj2lwmb38Yp041WKik2ZZEQpyQ8rXeYG8Wl0wSEYaMPD\nedEHRDoIAYRshRHglZYCD80LF7c31koGLGzVWio3erJgUYUwhy1QUltdVL+5PcVyUlm1\n=4eNp\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "pubkeys/ThomasV.asc",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1.4.11 (GNU/Linux)\n\nmQINBE34z9wBEACT31iv9i8Jx/6MhywWmytSGWojS7aJwGiH/wlHQcjeleGnW8HF\nZ8R73ICgvpcWM2mfx0R/YIzRIbbT+E2PJ+iTw0BTGU7irRKrdLXReH130K3bDg05\n+DaYFf0qY/t/e4WDXRVnr8L28hRQ4/9SnvgNcUBzd0IDOUiicZvhkIm6TikL+xSr\n5Gcn/PaJFS1VpbWklXaLfvci9l4fINL3vMyLiV/75b1laSP5LPEvbfd7W9T6HeCX\n63epTHmGBmB4ycGqkwOgq6NxxaLHxRWlfylRXRWpI/9B66x8vOUd70jjjyqG+mhQ\n+1+qfydeSW3R6Dr2vzDyDrBXbdVMTL2VFXqNG03FYcv191H7zJgPlJGyaO4IZxj+\n+O8LaoJuFqAr8/+NX4K4UfWPvcrJ2i+eUkbkDJHo4GQK712/DtSLAA+YGeIF9HAn\nzKvaMkZDMwY8z3gBSE/jMV2IcONvpUUOFPQgTmCvlJZAFTPeLTDv+HX8GfhmjAJY\nT5rTcvyPEkoq9fWhQiFp5HRpYrD36yLVrpznh2Mx7B1Iy8Rq/7avadwVn87C6scJ\nouPu+0PF3IeVmYfCScbfxtx1FaEczm8wGBlaB/jkDEhx0RR8PYKKTIEM7T2LH2p6\ns/+Ei4V7mqkcveF/DPnScMPBprJwuoGNFdx2qKmgCKLycWlSnwec+hdyTwARAQAB\ntBlUaG9tYXNWIDx0aG9tYXN2MUBnbXguZGU+iQI4BBMBAgAiBQJN+M/cAhsDBgsJ\nCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAr1YJLf5Rw5hlhD/9T4I/sBCleS9nH\nnjTJqcOnG28c9C3CRYIizjEui/pKmXz9fB1N9QrCaruPUQx2UacDVCl6dKxac+7s\ns3/a6lsjaRn0/2OM/sCVLScyxNPNPQs2b6jkodSNPIM8zv51g+flhwtfrO6h6B4j\nIhZgSjFdvqtZd5jaly9rA0uMX045CC4K6HGnq8n4F2p31z0L0LaHBf5EcsCM0MMp\nQVkY0aUrNg9uVMGXBHn3osHnOtQaODqcIbpa/OG+Tlt6pVOiDJ7i8TkpQKT7sOaM\nVdL//TEoDIOC7qVCN82q2q/gtiBXbziaERVs/eU0O52aX5qUhXu3VIjXTp/riRim\nR/f9BPB1dgDZbF2aPZ/rJm26v82ft7gP1Sf52E9MrAaZATTfI0/TUHXeBzN93EA9\nxb6/ENAMTX74u+NjlynWPD+hl64eBzJ2ionZF1bJFTgBkMfRYnhllvleCjcq9YfX\nmd5HKCwtxfygBIujUQSwyUzn0f5DbVCJ7/B19bKdvHGSSBgBEjxqXWQskm2wc0In\nww63goZAGDQliKhIT8xnwOBbLkqSobq4tD9zpQyxvMA2rhy7/gfFRp7TTak7MZHf\nlTJ37S5LvcWHm/ccWUZDUN7akoEDc+m6jX3uIEPMD3PQvcHhWv0amco3zDr1qb/+\nrXM7TJKd7DPX0E2dRzKu6aYRMTbklbkCDQRN+M/cARAAvktgmJflLg8bz3VMyKF6\nOHkpWBzfr9HOBZgmQTborznm35Z0BrqykcpHVGalNOydpNdoL9s3Elmr7S+L0YqX\nD3i3AS567S8KeKKenLoAV7J294KL6p2vldDiHHUNjXWSe0YEvbAw62tnigB4Wee0\ndhwAxhowFjmnyB3dZVHZ2Ai+k5x7NAEvCGgwec3oD4kRDbtkdyiXAM72Jz63hgC2\nXnnYM/XEptxPgPUkPQnpboVtQkSU5dF5KM0YK5ItllOwLMyDQ/pEfBZQq0O9Eqsr\n8zc2H8vYSxbmYBCdimzpr6HJWRV32QuyiC0c8TjG/fenNsMHliP7bOp2NhSo784N\n+Q/4eM1G/KO4cvpbduNToWe4lBdfiWWySWziGUoM9rBrdy5aex1p+i4Bk7FbAGUb\nKxFnsFoc4URM6UB5Q3URh8WtyRx+RjNNfs0MDRL31pfvfTP9z1eueoznY470utIV\nGdhn4rczzPYUW+j39m2qpMMfT+gf5rLQLC2jBKi9lSiWMBDzOaaLwK08MUeDiytp\n7xjFbh962ftC0/qs5VHBALI/XYvO0LuhgY55y1Tkb0aRUO5s1bjxW57HT+6k/1E5\nGMo3xq0GuhJyaiZHmFKHGaB0kHLgj9MORUVtmr+FE9JMAeCdyIQLrtNiGzPNc/ed\nOfn8CHTAIYJ3cd+I1fobsx0AEQEAAYkCHwQYAQIACQUCTfjP3AIbDAAKCRAr1YJL\nf5Rw5olED/90sdEGN4CQ1tGxjkY1NZRnNevdULm82qnCk/2srhBMCamXeJTkX7Cy\nAwbvyJLZc5X6KNkDwq+KyGV61Amg0zFLU9TiwDhjfiFhvrWm0ez4+bA2lx2CMBf6\nYKH3FDTzXobIhpe3G3FpwklbcrTt+ooiQmMhI87JMtJ37CXUu1ETZ8Drukyakwcb\ny35E+rV2n97eqnovuNzpfdT5ufabu7ZyARVu4CdJooagxIyex3vUu1/Vvr8PC9nP\n8cG20SByBKY+V4dRXIEPgsOtyNMMrvN0QzDtv9w8Ge7oKZNyiAUSq8Hif1ih2j7i\nKPsCd1RPPgmPWny8WWce+FGDzYwN8ZQ8n66MGNthdE6z5YCZoJDxOCNnd0AU0Ws6\noBti7pCVjbF+7u+P6u5zoCt2YGs6WwkWY2zEVEl+B1S6DkkUhQ3vsZuOKweNZ6bx\nxjezO7ISR7wfamLzTcIC5W2Rqg2Y0MewQGJ5+3fhmjRX4D+rbCSLHdL2/JGM4qqE\natbfSrGdXpkV7G55a47eo5DRlTn9MgfSR+3afjn9F7F4Gr8innbMTW8S9ORCYEJ8\nIZmapO771GjyqyN5zFU3HJU+XCGWKavKJD509olho5J+LAQQyHYzJXlzq3qo32fq\nMV2IIs6WaYdkUDzEOh2yh9ASaW/4j/HIRnudrqfFullW48LcHQkW1pkBDQRQ9YM4\nAQgAxlafMFTJNL4lE0dxQcwn3RhlC2HMoihTXRpSsTkcq3ViRev4OSbmD1NDVwkS\ne4al+IaKToiA/qMRO6f7z/4v0pugesWTKvGyfj4idofGap6Lq4C/LZAKpQ1fsy2p\n2vl3kD7D4ST3gEQEA7ZeUIUG71ptInTMndc7lnbeDlSGJ4Se8OzMNAnVJgW5E3NT\n9tyfL8Kossrh6ldyYppDSS5uL8lhhYoOhJYETG1eWFnIuNTkx98fKpFhxXQdYdDg\nz/9vGwcKVVV6pzm66fy7K/WkEj+VboxiC/3DnXpf+8Q9g3Oakvx12GYCVJOsdlpC\n5juOzUqVuq6J3t1tgPNoJCkcFwARAQABtB9BbmltYXppbmcgPGFuaW1hemluZ0Bn\nbWFpbC5jb20+iQE4BBMBAgAiBQJQ9YM4AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe\nAQIXgAAKCRAiRTAEaVUG/eYvCACEqNjb2VvGZZ+Ir7qOZej4qyK80iJXnN12v8Zp\nIKx03wVU91mHhqeeDZBHKYGqw9ao7eWRRe8BMZ/VixKxpbCJD5noDmY8dpp92cgf\nmOctHCXhjzO9mg9Gd42gYvobb9+xjG9plesju9NHRtcd78EtWGMkS4EeqVcJUcPt\nJfuQf+cx7CnUb6UXLk6Zq4PK7YE8WTrEcjkZ+nJOsO5VG56N/tVzAUxU21X+P0sp\njFN1pfXq7tmoGEX5Ad6bl3fAzGJUs2YolFkx1emzrAdDdNNbCbZhX+ipjNcRfEP3\nqjJVnr0trHdFxjlhULuERsgCaje4dWd3Mptu/FlQjIWMyZ/0iQIcBBABAgAGBQJR\n2qHTAAoJELlvIwCtEcvusaIQAI9QpGtQKCA5j3FOSJTyz8LGKMny5pAG3KXjSmQO\nmH+KZveot7Sl2y4AsUq3o0pq2lSGGaDRhbZgMfq6cHWbQ1shVhpTJA4Y0WNa0Op5\nZDvArT1P2628mSXItieu1O4VLCbfjuYGn7XJRtkXAutQUM9xHtBZfCSkhOVdiqjg\n9ltQUwk2K56WOWHTAfKkmpUfLcWWkqpAN8aUqyMCdOBB09bskACYmnENMpx6XFP8\nX1QZ849x3NJkcQUyOTc55PDQB5XmOTkomV+WCW95r8nHVd+Jhl552spPUvB6LVrO\nZC6TENk6VzomSuP5SQZafqt5LI/tNaqKk60V4oyPus/UEUusuwJEljtD2lfVIrgE\nR+WwVeas809qvb7WExJKBh49SMqFu9Jdk8cn0C0AVTXGKfOuPk0CZsYlSFVBDErC\ne1Z6In8yL4Ac/P3etjzkB+875p1gO1b2A/D593V1The7EjsaifmC8XDpI6YKiPHn\n9CYsqWtMOU4Lo6Sf0lVqXj+NdEpSuMPiApsXN7IUf6XSX1YYERkunvAOBgh5wx4P\nJLCyfKqFJj5TliY3X42pM0A+b7wFdCsFkNJrvXGtwtzY4r2ElmXYh83kBOYV6lVf\nMJYIbIRSjnJvNb3Qf4bGsuYbYYpUki/ba5pzXWDaaWR3NG1ej2KWKR1mg9TaVkEm\nyHoluQENBFD1gzgBCADM3jfDYxUQedxYnfyGoEMcUGXg9WPXJ/UunyfpxKGVjD0P\nolQonLR94JqpvnmdKjq6kk8ZF4MiIe1IrtpcI5u55ngSt9vJNnotXt26adCNqDIk\noVQHocW18eSYdvZ5Tw0vFEv8XoY+lp1HqI0dql1TkgLSu/le7WksooDqWK//uX3K\nvv/GfnfjWlafezFJqB+S1TVNowkfqsiDNO6l9huFcfoVOLY3O/SpeptXqruv7B8J\n5JjCLBryx2Rvj6vtcByGcLdad/TIRb6G/QdkRamxbYoU2pd+g9DQUDHZafT7SrUG\nUkEeYVrJlhvpte/78rse2bcJ00W0fqf2nRcen7E3ABEBAAGJAR8EGAECAAkFAlD1\ngzgCGwwACgkQIkUwBGlVBv2gEQf/aWH5AeKw3uDrpa1FwbrE8Thiptcj42Wl73jq\nkJA/P9lcMbu7WBTFTCwymLb8aZq1HW+qM/bPF605DCFu2TNxZoTUIcbHxI/YOlBi\nZwl3H/mcPcG6pszjTQrqXzecDzraBnfihVEeXVZd/gbFKCBHAHD0eQHT/YG6g0m6\nw8rpPPo9Qn4i1QEF7nd7tq5LfBlm0yfy8yrRdVDNF92JxO38YutQw4F+gNRCX7LN\n2hY9pcJm9/GKdONViopNmWREKckPK13mBvFpdMEhGGjDw3nRB0Q6CAGEbIUR4JWW\nAg/NCxe3N9ZKBixs1VoqN3qyYFGFMIctUFJbXVS/uT3FclJZtQ==\n=U0io\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "pubkeys/kyuupichan.asc",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v1\n\nmQGiBD7CrZkRBADjXAk5ONxSGO01JMqUjwQFZAwGynWcEVF093g6SezPXsMN/HEP\nPoxndqVhtOOI5M7ZezusYzUEH4RJNEDc/JmrQh9d6Q+CanaLuOn3c84XvgKVAmID\nogMrxeQ7biCn9NyZf6EjqP4QGBwWQdsrb2CvK+NO8h2oLo2Aycv+hciDlwCgoLPl\nscZLBvfwNRdcajG45g5NfmMEANqZLvax6e5Wr9lHpfZFQch3ai5hP5dDetA0jeR4\nnzeWdbwD4Aph3AXrZt4c64qwkSi3ElS5UKzNRsO6APF3+COR1VXJHyN5ve6jxpuY\n69zq9ZM3KwYHztoKfHlXJqTtOx9U0DSK/xwphI+Q1neMBr3RyL8muKQvlESNuN+3\nn47+A/9+5pWs8m060jRcs7W1zAYOBCv5fb3Zeot6TgkprDrrRFitcyXbB6k1zyMh\niqUMce5Ht785AWYZuCTjvbxJ2yuT37s26OswyXDoBGwwcPgaFyEO3SBRGaoxvTrW\nNQ+gpiW8ILjXCBoJdgPcOlxjj+mhRp3OQ1TJZojMBgio56IWbbQtTmVpbCBCb290\naCAoYWtpaGFiYXJhKSA8bmVpbEBkYWlrb2t1eWEuY28udWs+iFkEExECABkFAj7C\nrZkECwcDAgMVAgMDFgIBAh4BAheAAAoJEHV+VfRE0xInLkgAn0c/8n77dUl71F7q\naMolNZ8KmxZcAJ93ESJQ6UWhnyUxHm8l4OFdXXu4RohGBBMRAgAGBQI+0it6AAoJ\nEMXAxcchjRjXF7MAoM88e9oNMPdUpeu/hUMmZw6AL+AqAKCPgRmICbdH2fQYR99E\nLyw1wGwii4hGBBMRAgAGBQI+0jp/AAoJEDiaVjzCcqEmYpoAn1kwGm12ovyG4PwS\n5rN+Z46FE5wUAJ9tQkDrxF4yhTkCU6KVOje/tufBdohGBBMRAgAGBQI+0la2AAoJ\nELfOmxk3oYfG1yoAoIhyQvVnzA3AUPmpJZP/sfCqzRrNAJ9LmVN+b63BfgyXkJbu\nK0w792EDS4hGBBIRAgAGBQI+0pzOAAoJECIYyB6OfAP/1G0An0XtPhXIf+Z8VHrb\nu9d+e7tJodQ1AJ4v1rH9v/NopQtHdcnxFvI9alEPH4hGBBMRAgAGBQI+06DGAAoJ\nEC4s9nt3lqYLLuoAnRm+RnRj3RJJxE42yTzLP7GslzazAKDO3CHEtgPbj8DseXKS\n2jiwi8g9RYhGBBMRAgAGBQI+1BTBAAoJEElFpTfXe0P7LQcAn23wIm2Pg6nO+dBO\n8oBrmARV0+ImAKCcO5k/5ByeqUMHy7lKx3HVzOET8YhGBBIRAgAGBQI+1K4rAAoJ\nENGVGa1MfyvuLTIAoITJh0RbLqqsoyKMIPA3DWWs8iUOAJ4g4HO/rL4foavPOyzn\nBDaHoDBJ1IhGBBIRAgAGBQI+1Y1MAAoJEFC7KXQtWafSrP8An17bXzC3iyywvnC1\nW7RfvjohMzzQAJ9v6BJn9xRORSIHY4ifqfU6BPtVUIhGBBMRAgAGBQI+1M8FAAoJ\nEEXlkGj5G7efrxoAoIKXAOjFCrxhlNSIFUpDkgtxaPfyAJ9LYM88b7Jpz/octbEH\n6o6lx8B5lIhGBBMRAgAGBQI+1lmrAAoJEFI0hF3yuSD1WDgAn0CjIP3eHGFoNTMF\nBTefl5KQWFjlAJ0bt40eHF2hp46HKS6Bbl2p09FFyohGBBMRAgAGBQI+1mfTAAoJ\nEG4Dj17go4N34MMAn0GSwAMasmCfBUhFLRxy4uYSy2mhAKDZ2uklcaBauLQyjFoL\nM1NMhMCJ/ohGBBMRAgAGBQI+1r14AAoJECTxPj/mjACSpH8AoIxJsUJr3iUoYCzZ\n0220/CbBO9kyAJ49dc3iiBArycmnGjbkSLnktpiqwYhGBBIRAgAGBQI+1itWAAoJ\nECn45GVniJZfNQoAoIfjMXEsjsnaHI83sNdrBmN4knQDAJ9NAsEIxzaGFfQmc33E\nMhxRpo2cE4hGBBMRAgAGBQI+2BXeAAoJEFlRJ0yBj+NA09QAoLN99g2hp7h7onqW\nMIc4T9WYWKgiAKCD/4r1f7vrqR/sSYKHrahI5PbysYhGBBARAgAGBQI+3GFvAAoJ\nEGcvIifCwHAobfQAn01wzHEdfIeWy1X44PF3EDA1izBAAJ9oLQ3ES3Tv7R1rPPlo\nrSCPocO324hGBBARAgAGBQI+4mQfAAoJEHFzfab4xNFPBgUAoOk8J3Xe3ko2SPjM\nd84JfXbIijW+AKD5b7NfeVAkz9mNhYePqU53SpQvQYhGBBMRAgAGBQI+2U6qAAoJ\nEFHGMyB5fcdf47MAnAprssI6tke7MQuiqf+xOPO2XxS7AKDOn8YOQDEJtlm8qRW/\nSGG0DJ5MBIhGBBMRAgAGBQI+44T5AAoJEN5HUcxjjSIaSpcAn0pWC2xioUmTdjnJ\nOVJPV6m3h7TYAKCm3tIRQ/n1e3+SjrZ6B6u9arKRiYhGBBMRAgAGBQI/ASS3AAoJ\nEDC3rnBH3fqFUAoAnA5k7w/Y6FCkgSBDqjC8Mz9e+DQ7AJ4npy3wXIW6gk9UD484\n6CFTk7rwHIhGBBMRAgAGBQI/ATFzAAoJEF1s/WZ+hdAzbf4AoMjm6YMw4TfxTASb\n85EfShS6LdH5AJ4zDq7QAvGkPjTY2JLlwHph3afyHIhGBBMRAgAGBQI/K8oEAAoJ\nEHx6uUUZG8DsJYQAn0VwLcYxa3f7Ovk6m+xJUzcpcMMIAJ9F0JRKAS2w//tkMPab\n8YZ+sYECv7QhTmVpbCBCb290aCA8a3l1dXBpY2hhbkBnbWFpbC5jb20+iGIEExEC\nACIFAk/485MCGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHV+VfRE0xIn\nmTYAnid43IhUSJp4f35yw7V48uWI/PHiAJ9O0+8kcnVukXQOLpKgQekH8GIevbkB\nDQQ+wq2lEAQAlZjNXxfrUYxJ8ewd0LIGmig3HSKjREHc7vuN76P56KzH7pdEENaa\nrhY7XggGQ826MyFgkpRp7LLNR8LNJb6JR4tPeeUFpS6Xyz2tj3UHIkLTbLeub3em\nW3/oinoO3bbzuZ2hVx2GyR2yVlM5hbjUFayne0D5KfSHzCEJ5SLMn0cABAsD/i6c\n62u9tAkMRsrWAjcd8z5PLf+Gp5MuviE396gP0CCdcwo3K7RAYcUyiJRObv/zktbN\ncE+ZwYclB5zJT7kiJlaDmMfwyHBDYond4bV5VKeveGcr8Xy/cEh+cN7CceWoPC8P\nwjcnylxM5UZHMrTBlaoLqKvNq/JZk1sDAau/9g9kiEYEGBECAAYFAj7CraUACgkQ\ndX5V9ETTEidutgCdERuNxOlRNFORwpSVJcvOgeItgMsAn3RblJxgwKC8S+z1NpV/\nq9K8C944\n=MRTd\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "pubkeys/wozz.asc",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: GPGTools - http://gpgtools.org\n\nmQINBFFI4SABEAC7U8hKaLjTBQb05k067LzsT+CTfEVvCA675fxp2413SKCLJUL5\nsfBZF+2fsXGqZA3SoA79/y83414YYBogOUxM8OFhIQknSSbukpELcbJ2f6w4GQze\nxVDak0FH58mLepu4dx27cP+txvC0JCSXFETe4yvXd0nM0MPj/bbHyhWDZ8qp11qW\np1GtOyFL7Xtkt9Wcbhx2/1pA92H1TYnnfsEqXWgH+Og294Vsjp6novaNerjJsRYD\nUQucpG6scNw7DNQgRJE/tdWvj8n9Q/RSy60ZekZlLpFd3reNN67nV0ojI25Y5zOu\nH0yz0XSFx4ZZakkP3d8oDsMdWPDycFhQjJGUTj4TWfBKo0cn4DzeoPBlFxPoyO8g\nruemw01fawlOA9YXLe6y3J50l3aZOvME0JzCGh5M32MvWmAywVlc47cjr6YA0uOS\nuNuQzO4XGIPwVbfIHUz1+9BBffgpeBA97mm60e+UtuAM50fTWx5ddiGlkERXCdps\nNCBfptvsM5b0Efa7v4fhRuAj0lOBE5KbgWaOUGLOAnGJ6ZxkCjzmCcGKPICufxLU\n2errH6xk9z1poRsJvVH3l5SsIBbFmuntJwiKCHRy/tPzdnGuFHcQLYihcrjSXaer\nsiiPcOEHjgAezUvNvAy6MnIW7Erl+zYYUS1k0QqAslul6iPZ61JW2hjwrQARAQAB\ntBtNaWNoYWVsIFdvem5pYWsgPG13QGtvaC5tcz6JAkAEEwEKACoCGy8FCQeGH4AF\nCwkIBwMFFQoJCAsFFgIDAQACHgECF4AFAlFI4tkCGQEACgkQA4wJ9GLCT8c5XxAA\ntWNVR1pQhpsBq8J2H0xkCxfeRtuNb5gEq9fmusPW+7Dad/CL+KVIfpyfUSii1+Te\nzE9cNCb6I7xf7jGWPUrKohl5IPLHo+bYDWIL27EjoDYIW05v0RYyTHA53fsHNX37\nmWd39xyu6DZLV2PDfAqILra3VLH/luDI7kpYfvuHAr/AqFAGByKgMr/ZLLOoDcfV\nsI9k9jiyp77VHbPjvTIHan9hhTT7Ee8oCSmxTsZ/ySJcMf1ROKUeCq4hGTXchHSX\nFXQNveCKChEL862nVuCQ0betcXYKPolQG3DJVfihMaEpQYP3bkyerSTpE1TRbZTt\ndoeNvwFSvhXfzhOC8UcuwuD4ET9dS+brq/r2IvM8FrcZt8dEfReiu4NbIXGb+V0A\n/fqKatYs5cIyy7hY0U/aILUI7WEaxA1EQ4wOcLudgM6Y4hF/df17RMzofCrXrh/2\neYLVVdVMj9pfj/MHdlwJX7K9OCmzsN2e7PNjwu/wgqY//aG2tyRdhjmeI/ybwLYj\ny5sJ8GJkekCrnZbkiSgfBxfO0wNPzq+JGF95qVB4RrhMPiOzLOHyU1RUyP8cJqFr\nSt1fyHtfNa9XN++r7nRTIMFWQL5nyQ9czJEOSKnBMbADtcSs9kxlFYTAMnfpETGu\nXLAONS3wufi9qEN4vIwG/xRqc7I4f6CUPMtSwj7AzQaJASIEEAECAAwFAlF1UFQF\nAwASdQAACgkQlxC4m8pXrXyCHggAp8qekPNR3N78WrCiEVqama9iqoeHZxhzoNaB\n9ELyH2Y8PP8FAcpFSQK+OmoAOwCPBX9+b+jTXwZYhjoMI7yrSdxcUDvE0PUpO3q2\nVV5A2fKQuljSmzYuhnjUgA5ZKbj1lwPjbWpXUR6q8schnu4zDzasuz53bqYdDVlb\nqO2IBaPhss5Hc98vTIRMHp9rxbET3Ict9a6XtMYhTKye5jf4O5JUOsNPLZWZeSn1\nQGxaxbJ4ko+/NXvHN2ZdNoFyZEm4Am2v2m/tKq2JLMVF5SXUtS1c8MCEOO9byjsH\nCE/X7GHQoC3IVxR32wdkwzZ2uZcVG7Zm+RvLLzy14WSPjjRNPIkCHAQSAQIABgUC\nUYc4zwAKCRAyidZ6Uw5GxjE2EACNU7LHVUpAQFKjxKN03IM0cd/C6coHObvGmpXY\nsMFMta5iyXafmo+MGrR52kEr8xrQ5xz7Ozw2RMC83PyBN3P9nEirQT7gm+gA4m8I\n3a/jg/py7iwRh49XXY5Xh7eMAlkvy12DMsmkCHx9yQHVv+Rdt5/N+ELvgeL4aZR9\nN8/J+6Lf/MV5ZapxXyxessdOQPF7Ik/EMi8iAPZYkjXlA5qsfGONqS/hs8sJJ4sF\nrwndgqMTtsqmYbqwKnZgvSPpMj1CclbXorSVjzBn49/9zsCiv2twsVFr2kS7HnsX\na/Xu+4OLdOCVQ7w4kHkrIIuRQ9Xuz5uFNOTADvpjvwbxeu7q1+SORd+kEbPIoT17\necQoM6/POXpUv7Xcja67PCL1O8xeLLjsxco68uFCtKGSmnztOGtw95+KrLgzLC2a\nron9D6BcXSOrWwlyrxLP+0CWBnXBDRY39yfXF5RZ1tIlKPRBJEyBPJgIPoS5Fxlq\nrN+J3TzvVK8bK5QXTi7rGnSxrTM6teE88jOESBuUfT3KS7Lof3k8n+GWUOdqbf/k\nK2fb3YG49w0grQq2cBCs5gaaJLao37xQMPAb9ub0dWQMImEvwJmVn5m9ekcM2KcY\nKb+qpRPZcwJEZOPW7nyl4DbqCKpuiL8BSl5vg2T/+8BdlRIVqj8oH5Fwmbz7OR4f\n+LEchokBIgQQAQIADAUCUYeHuQUDABJ1AAAKCRCXELibyletfMv9CACcBOWrLaV7\ncXnb3NcBt9EBUchTH8J3JCI2zCE4oFNw3VSVzBA/ocLAm51llvmjdyR1A3/6RmOZ\nmF9pLKU0opOXBLjZ9paD3D05xlaOesuTNm++lPgolnMjFXBiPX3t5ulUMW998OHP\nguQYaxXIOBIGqqZ3SP8p0OMZcpZCNKwfexELmFknyLW2RcHnax5GcCIB+yDl39JU\nEbWx85jGcIwGgwSXtxINnhYYjz5zJMjGZfb4h7MdnV3fQxt3+nNVvA5ePTeS7v9E\nsQmnjYd6BFekr8crb6z7GQNrJk8DUjipe+PgA93EskHDMjiYrHAhIS+VV6MVGn5F\nMJrKjx9homCliQIcBBIBCAAGBQJRlYDaAAoJEO+dUAE5sB2JlIcP/2KY5EDXlXwo\niSe6OhGZNIyC7qU8SIz06WWAPj9xlhjyjl54sWce1yOrwv1ZeytAEsVyXEQKvNGZ\nIcTto/RJqiSag0f7J7mZDcw1s48y2EPYHDk5yV96AT5uSaRUkmMpWPMK8Key0hH6\nEMfhZp4H/ziEn9VnKgpIpS9nQ/K5a6JjDcwZiM0TsEtfaDEC1/SFtrUA6Rf+yzK3\nxLHXhbPnLRlphM/fUoXqS/Zr0ft2eDwUlhbB6dP1ZfrxCbndNq97dOnxnmgPIxZ2\nRVX1pek7XOyi+qgWC+TpJwfzGX5d91do9tPjuC6+l8tR+CGrslHW7Bd3i8AcekdX\nL74JbQr5CXUUTEEFPZuKY6iOWGoPHyEy/o8VYztuOeWcZ4dBgrvQMBFGbu0wciPe\nQ/d6mZw57KuYfBBs8JpBmzi8MfCCh37VAPBMkYMfWk0FuaelTW0eL7prjEn+4Zlg\n+iXDOWFp4OtY06aPMc+h0GXK64HBX3YJsYTS7iZNtQxSJK7Li3toREPy5kkhsYxE\n3dRTUdM5px5ekZWEGrRORsxOenQ+TStDGqfQwbCj5noUTy3WLBNwNeC7vxpb2NYI\nVkNPv/GS/29NBIkBbwZGIS5j82+SaeEo2/9ZfXiYXV2Va2lbH8OTIyhXLFXS6FQo\nBzkCFbsCyLICe5PLtwASnYKa4po+nKcWiQEcBBABAgAGBQJRrggZAAoJENtpSKdF\nkwASi9gH/3CIAuxg+oBYUDly4tM+oeFiWknDEUcroY6i2BdHq6n0O2mhvmd0+BuM\nOGHi2ilYZ9SeKazgFvGBIlD0wdpApW0EWnS6b4CNa87+ckTpBDqXTXNTyWhKuGS/\nbWnUDkAxAT5TxfX0jmTDVsjUT5XjL0acgL7TVxJZXbDFRMS4Qt8z6V2Q8JUY7wwD\nArWri5QaYF9OnMm8rj0a+XnAdLvgTIbAy3w12aq0MEiI45++r47iyL0kXxqAFpRo\nYywSthtwvbeMDlGsz0HQfNLUszxWaroAyqARBtHvUlYWXmO9Vhr8ycFuZwrZ/GLm\nF56CHTUCQUXN37qq6jN0tkBAzN8Z+EqIRgQQEQIABgUCUbytgAAKCRAD0kzvlgTT\n5Mv9AJ0dknWViyDyho0xBvdU8otET/DlIwCfZYMlpofCbtxM3k9NY0FClstdBg2I\nRgQTEQIABgUCUbywoQAKCRAoUZ/piO1lEM/sAJwO4ULlzNfIKurfByPELQq5kEzU\nuQCeJmBPZJZouZgJIZxWtRKwku+ORKqJARwEEAECAAYFAlG8tIkACgkQS6iSTApa\nKQMkSQf8DKe+KKk5H0X4r3c5FSsaU4FchP+5BhiE1zhB//US80kJGOcdxLOgY6Wx\nQID0csneIhz/RrPwmHBoamlKlBEwMJKVPHeTlEJ1tBS62DyBsfTMZdPLxts9Z3da\nPy/sKSuG1bHuDOlm6quzm3h4gEmjCvMnJmWnBGBq/rONz/r8AY2qsAvVWba9lLVw\n9Ih5wVLTtUwG7O7D/TYNRPpVNQpm0VOuNImAiAwj8A3iHtYLN0Zq1c8hrlJQ5eF4\naWaX1F/kSNIL4DL1bQMojVd/wmMp41gzI0WcqodFuqLalWC/hxMxKjJ1hP/CGUA1\n9jjJYKbZPryIaQzzlqHXe5EaZmvSP4kCIgQTAQIADAUCUbywVAWDB4YfgAAKCRC5\nnwhOJxzwubV0EACgd3vRfbH+QCDeB6y2JS6UldPahx4XGCtH24SAhqGwBNSrLKnu\nJ/8gl9UXc9xRoa5as3/dVR3wvalmQ9If0q2CzzpvhDjr+xFH3DI7oVr31EjzIrs2\nU558QBhKL0SnUre0Tzg0gSYGhBeVcaN3hc/iaYdn+qjDQKIbm1MPqion6zPt0EhP\nNRjXR6ttHJmr4os0bCG4Dl/GMKYTdfQRiKlhy1R4d1XvaoK4VrFXggt4gpw+YHxx\nYW5Mxk4M8+bxBHbLF63v6x1U/Op9v7xHJvUfysCHaZyi4QYy4H93fbbzeYeYEFS5\nrNX+/Y0pIbhvLDdhe+mtMSCpFJrssXSISmxkmPYSKZ6BwF8l9UYWBWF5Xwv+d2m5\ngyuaqi3QPQRhZKraMpomUbZjr8TZspDdQG3PCxFuu/H23QxijM7it9LOBzxUXe88\njTOKMtqwAHYZWDfvuGQSlkU0O0yz8OJH+sM1+FyAsjp9RM8nCJJNRFzTyxQmad7U\nnclErOWOKeV3wTpPS6iMOSv3wOqFIjSvF7xPvHNtL2Qfr8j3zxCMt62X6k8WJVok\n/2CyrbN3dXRuUiZZymIez9R4tF3Y+xf1NhqvEh2sew1WkMjtzhBsHWCMjePgqwod\nQtdQvP6c08H9tpXrKagph2WVnPFj4hQfGNI9Hc8bH166hDDrEwo/LWDQo4kCIgQT\nAQIADAUCUbywVAWDB4YfgAAKCRC5nwhOJxzwubV0EACgd3vRfbH+QCDeB6y2JS6U\nldPahx4XGCtH24SAhqGwBNSrLKnuJ/8gl9UXc9xRoa5as3/dVR3wvalmQ9If0q2C\nzzpvhDjr+xFH3DI7oVr31EjzIrs2U558QBhKL0SnUre0Tzg0gSYGhBeVcaN3hc/i\naYdn+qjDQKIbm1MPqion6zPt0EhPNRjXR6ttHJmr4os0bCG4Dl/GMKYTdfQRiKlh\ny1R4d1XvaoK4VrFXggt4gpw+YHxxYW5Mxk4M8+bxBHbLF63v6x1U/Op9v7xHJvUf\nysCHaZz/////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////\n/////////////////////4kCHAQQAQIABgUCUcM1sAAKCRDofrZNT3C3NcoFD/9O\nLaxOr5SjwOEdEWGcfnIYrM9W3JTnPv55Sz5zyKkgDd6p3fONnrSVz6UYMEFnkMPF\njn8KodOnyFvqVrkwko8FP8BctI4ztyNG6i9xv3xu9iOqt2n49ANRZM/tUtwTyb7q\nBXVQwcMmhVJ/i8MR1yYMraFA8YM6ovMHUszY3gx/x2Yr97S/xAtuauaRO0sZasA1\nUyxaZxWHGrek+/tinuuWp0Y1b/iSyW7tC0WV/tFO0C8Iv5tRcel/4b1EsV8he1EB\n8qOLBrAXcrJy3eC8H11h9rkfwL85qUf81oHYYg/vfmR4Y151VglclOZM6Y9kLC/X\n8yIhraa6dUG2prLxi2xEnl9eovs8A5xhcxPy2CHcJLRwR5xHA1IsrPK4aZsRC6cw\nRt+oLFxLCvGK8MjW8O2qMjn7u/xeBTnkEEOJpe3NQH+1jql3Tt/3ZkPtANR/Oly0\n/7tj/CyR37GUWxM6pIXIElhQ19u+5bGwgxdhD9Htwbe6j2LNqD0e3MUrQphojzIk\npjbmvPU8Xpk6upYFAu04+X/DfANWv61MZdiEHhEiySxIlrg2sthRooOURmmXld0L\ngzOCwJzMOjcXkaVDt3nubJGhfetHT4K36cAWBfRURMuC0hUQw+WgCBRzNT5nXBDi\n6Ng2l79fXlrY3/AQXDf9bH2EYpHBYXLySqBnxwBWS4hGBBARAgAGBQJRzJUrAAoJ\nEHbCDUAgGgwVAHgAn2Lm4q4fsoPwvy56i5NBWItY0ReGAJ4jpm+rsXdK+mW8gp4V\nr4RQD4bjO4kCHAQQAQIABgUCUcyVjQAKCRAoOKHZD9LVht9TEACeUgvjTWf/C9j3\n8fHj8zd5OrTy9PwXXfGvAZ17THgnzZNy1/U6zIhLqdUUwvyqtRU9EYkZ4YFquCPS\nqqZbyNBzpjdFARpoxL41xXSATjaaitHrywYYeWTb7USaUkAjOz/5krsGTuVsM2YG\nCLrYn2OVFobLZ0alO7D4mrAwLzTyat+WXuJA8V6wJW11r8E7awf3qdoI/VWGbQch\n7d3pYgfp6Q6ZRwt0dZDLkKNUYx41x5saM2wo8d/TgcHWuACnVV5eX9v80GToXUsm\n12ruI1ctRqEqsgiTLYpBdrevkzZgHTrY/VLTg5MZxWayUvSLn6abKalYNTvs6HFH\n2FuqgY0dSS88ozFbt/WzVG8u/2LPJ5loqYBREawJmdYjZCS7TI0gjq0rpL9Ppwhz\n3kkNGYKOlXpSiJVMBL2yiBkqLG7KYnzaJrFpG/aupxnxGPHjzdoLOd+Kpi/K+tnK\nFoTngfVBJxPdNG8z2a9TqxeLtXNPkJmwKQUQPibVFzKvvVkbSp7wQ0dfyuxPcHXu\nO0Jf3D+/AXl/V0BqrjIsoJBWhONavt3RnyiX1a2UDhTE3HFqblctg0E8wpG649dv\nvnZW57/a2PAqV6B7ztZ33tDUtLHnMPOT/kWQRdU+x3WKUi+j1GM4dWksLr2FeTUf\nC/5Tekmf0w7TjH32rHU3l4Pty8hhpIhGBBARAgAGBQJRzJW+AAoJEAqchymq1vv2\n/hsAoKBSCdynoKY1+RY8WF76WJJO0GxgAJ9p+/nu5kaId8j/mcVQfSULBl5MdYhr\nBBARAgArBQJR0eauBYMB4oUAHhpodHRwOi8vd3d3LmNhY2VydC5vcmcvY3BzLnBo\ncAAKCRDSuw0BZdD9WIHTAJ972DSGqdJ+MlRGxjhWtteRFdrJcgCeLLzVFbi6C380\nOb0T1kEaPnloUrGJAo0EEwEIAHcFAlIz079wGmh0dHA6Ly91bmRlcmdyaWQubmV0\nL2xlZ2FsL2dwZy9wb2xpY3kvMjAxMTEyMjQvNjdkYmUxM2I1YThkNDIwNDFmMGIw\nOWUyYjdkMjQ0Zjg0MmZjY2I4Y2EzYmZjNzBiNzkzZWVlM2U2NTI4NmRjMQAKCRAV\n0KYu0B4ZDKl1EACgPo7uNemY3DIuOpUfuR1gyxRLOy93/dyHZYyrjCA6qqFEfU7g\ni/2i2ZGqualW+ot/AtyHztY32e4KQFJ1msnLk9RKwrQUHJCF7HgNo1Hn3dSrmYxH\nvAaMUJ6MRSQF2kKJjgbTa754FzRO5BzMqKcgffn1Dl6JVhjNpVk9RYBx0sRGP8tf\nqggsSbEYTtf/s08IhZlSnBriUBFU96eabYzbuGdLTGDQusf4OgtkTcu0mhmNv+hV\nFgvB8/sMToZzhLC2LyklA9t3W4S+syFnhI7qutLdzgVZfdXe7ZyYT/SN0PZhZQwR\nEhci/hczbE2mO8k10HvUiOo6TDrOBxWGg4EAAXsRTKLvAwcbLuDDSdfALnPx4agq\ndX7pNYFMG11ct218p5AdwO8mUxIIvioN9TAs3ZFw2/OMA/I2L5Wm3etvmPHFWUgV\nZ++cxXGV/i0rD5VWUxdqtuw+l3Dx5MddzROlCyggzrTE7G8lkxGv/NoxE257/dJh\noeyCnvkdEfZeQJQweeUP14EHcm322AZpgQIqQMYcsrlVJJVEWXitocbfUuGbhIKo\nyNi+7y6zbixcNV+OCxhqeEVXJrC8ZVTaIOPuQiPYJCDWQEuClnLQ46lpBVfwPOu1\nVVgg2tBiI12jYlOTL6XaZt+Gkm2JRg8kX9CkwbUhtWh7GmYp9jFhUeZQV4kCjQQT\nAQgAdwUCUjPT1XAaaHR0cDovL3VuZGVyZ3JpZC5uZXQvbGVnYWwvZ3BnL3BvbGlj\neS8yMDExMTIyNC82N2RiZTEzYjVhOGQ0MjA0MWYwYjA5ZTJiN2QyNDRmODQyZmNj\nYjhjYTNiZmM3MGI3OTNlZWUzZTY1Mjg2ZGMxAAoJEP/OHJpPrfGXAr0P/3fJas1n\nEGTYJQDSyvVRREklecwqlEsP5tVru+yW7eswSeFPZnZMab+4Bl+D5pLmp9zZ3oFc\n2x6ARb/e9uUNd9SkpcOovvw+TxKRFHeRbIokJz82aLEPK8ascYdbHLG+0LMPFyfA\nxFYLQD7i18QX1HGN8FdoVuWUVp7/UO7qMcBc436RZhbRMfmSjsLaB9HDny7DOlIq\nScQYtHbQ/qraBUOrGgI0K/APN/17E/7tt0Amzg944YlfOXi54OwekB/i8g0+fIEU\nFQ4b8sjSWbOZwUiE6WC2g6TwYTzSmnKunBjuMXGH5se96VkRPdFi5yHYjt77TzEd\nb9xIFztpLwRlOEFD+fzzJ2zqqvd319RBn3IG0rwVOC9lr+pRHEYMpd8R7yAd/v1/\nkOQppzgHEqzcfNMQgbUlIyoXqnhFbMH+NIkOTH2Lda3GkFC93p+TOqADbwYOBo8g\nYfsUnEU/dASA18D/hshRg/Wjlq3sdJkLQfgrqk0CxVOgaIy6iDz4MerXxiLJG4uH\nchgmGJLuhEvle9QB6uGNEa48DOETMUDGwPeSPqgUsk5zxgdCq/qOGH2+76S/Tc1K\nDwI8MTf+/04dUbbj1whDuH7tfKdEHrCl3UoPV4gwKbmsoMoZG/sfaHqehkW1XlZy\nFtHEVf0XDmOEbruyBMjhRLkQc9apAIqqeVxPiLcEExEIAHcFAlIz099wGmh0dHA6\nLy91bmRlcmdyaWQubmV0L2xlZ2FsL2dwZy9wb2xpY3kvMjAxMTEyMjQvNjdkYmUx\nM2I1YThkNDIwNDFmMGIwOWUyYjdkMjQ0Zjg0MmZjY2I4Y2EzYmZjNzBiNzkzZWVl\nM2U2NTI4NmRjMQAKCRDVc9WxKatM3fF0AJ9FSrSWq9OUE8LRcA+QxbtoZB+DFQCf\nV2NIf0wyR8HWLjwLXiG8beEbj2mItwQTEQgAdwUCUjPT6XAaaHR0cDovL3VuZGVy\nZ3JpZC5uZXQvbGVnYWwvZ3BnL3BvbGljeS8yMDExMTIyNC82N2RiZTEzYjVhOGQ0\nMjA0MWYwYjA5ZTJiN2QyNDRmODQyZmNjYjhjYTNiZmM3MGI3OTNlZWUzZTY1Mjg2\nZGMxAAoJEFRMSGhi299iPBYAn3F7fsXWvWPXwQFM7/BBxU/PUvayAJ42XPHSSVWf\nZEXDT/gkpYG0xhYJ6bkCDQRRSOEgARAAyWjtpoXnq/G8veD10U0ZSG3OMLsHxLsk\nokLKigPxWMHSgFwRrdT+t7OQ+T5f4ETerfPBeripsYqtlcajSVhXEciUJRqXMCXN\nl1tyawCOvE4Vs+cQN780XzfxfiwUD/NeC5YzEcUBVMfdqNoVJVYtte/niv24PZaV\ndRKRWICX1J9wZj3oA8WS0JdKnvYjzQSzldrJ6iIrY7Hyb/Y4a1hCpEelJ9LombG3\n5O1f8bi1RNv5fHHLzJLU2Ngwj9ghb9XGD4n0NJAEqOrLpUm4VZ4UyJOj6kvEsOa1\nGlKzzizWblSelFtXvwXwtVgOK3kbpJ3rCYN8omckqk7T23rEAYy+4AO2Yqq+xdYL\n1qgloAEFtUCUFunnVAx1j5gqqd7TcjBPDqNOgHkfgeVhZ3GmbB/uUsK2PGocGLd/\nYTlXf3EGaJpuwYts4mHOF2Vovhww1+LswX5fKKnJ5CzbfC52y+7HMpO4kLUQYkF9\nsTXElNaUSh+3qNwsuUevI4QeFsyt21AEjUj+gcLSrbI7xqsJWG1gyfnfVuOj1KDC\nYL6hkq+/XmZSXOHbI/7TeCyEsgl6JGtfV6bhZ3Lgd5nEctaou8byDDL+yL9/aie4\njEtC3tWdHWWZxNqROyQEdCXtgEoRXC95MyrcWA0QADtv7KHBh7lekcLh7uF1gLEN\ngOUnLVB1r10AEQEAAYkERAQYAQoADwUCUUjhIAIbLgUJB4YfgAIpCRADjAn0YsJP\nx8FdIAQZAQoABgUCUUjhIAAKCRC9XExgS3u4A8pXEACd+Q6zyerRstasztj11Jyi\nNbAvaGJ/jIk6CEPttBQOrH4/OgeeaaabaVrQuzYtuYJJ9FwemHiRHKQ7xxg9L67k\na5tHwXuxpA0tjilZABsF0/VuENMH6yf4Z2vPrtmJtuL+ZzeXANijWwuzNfOsUMQS\nKK20XeHxUXI5WpboZzRBB9ZjYVgEYg2Yy4RuIV903wy90TFrfkCiNyxEctTbr5PB\nuq/Sbvv2cfa2n5K6UUWjWAMAJfeePyrEPjDZusUNonIFyQ+tUtpQCzFDSeLKi9MR\nHOplTa77BjWSkdcT58EtOJLYYRkJEHjw/Tv8MDMOjMOHAeo4toaWNNOadYV8Ml0a\nO7rULVQuTDeJJqZ5YAtcCl/TxAFv4mIVBKs7919KQobPPHUa68F6MQ/N65l27ly0\nVdc1V+O923NcP37Y59f960FmECKDHwrBwMtCKzLtmXDtP+F63s0Nh1S+Oxe47t7N\ng4W8qgILwzSqXyST0RzOafYpJWfvj3bhW7h0Sax7tCeDWltTagaxNKhi7Tzjqph7\nJAAauIeRzBSisPTdEJw1ww9mI7LlXz4HGp1rV+ynvraDyhC81tZ+xhIng9/Hj/7v\nC4oFOuZABohYXjg5sahFYuR2l+kYRz9hA0fKSFNUo07xqL+rDt0Nah0uhEhJG3MS\n8HL5UYRXD6gCqUiwimOF1jj5EACiIL1tjlgvftkcaCyLOFbPegUnbwrgcXNoGo0T\nD6D2ir5swV6GL2cLxPTGTwXjQ74zNna3vrnRb0p6cgjk0fVUuGvI2gBWewJ55mDO\ntTMZv5u06zN3VfnUVlKp+aiNpEhOwoMBhufOMCRk7U0xzV755BxJs2XjO9Fwv3fq\n7jnqNlK/sLPOd6KANJyHLNAtkpkUlF3KiMwHCdNZxzila8x7CBfzlfjJz9tMb1Ug\n7MSMZ3A4fOp9Uz7vWEie5gu8gPm4rr+ksAylWeNLd6K+ZlkK5Z+jQ3qRU9YBr3BJ\ndXePk5WqGuzav5Tmh2S1avEDfD1V4RzW8bdwDowsfsjqXz1dNeD8ahoZN32PwFiN\n7dgHKQtUs4NIbXCqeTaD14oNjmR/lCBjP/Vxdahmh7WuQcnKEx1/ZUa8a2JYWxFW\neol1U/rrXuX/CiTs2U4vYDQzpcDJPZCVs6+731q2qWo4HBG9jkQKjuNyxTGhBoTn\nkiBEwGug84/fXHq/+kcdYhsyekU8tR45ILH3FVuRuV89JhPGYorC7ThJOu72dYSv\nYDvc+GHc7v/f2cqHGUc/ZBWmuD41lZq3hbZMzypCYTBQglnqGj3ttGM6ZhlIW7KR\nt/zwAbMPLlFLnrMb7FkvSvHWH3BNAcuhqr4lC0sUd30jtaVsLt0Mwvi9sv+wZ1Gg\nrSxUQQ==\n=B9ld\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "requirements.txt",
    "content": "certifi\nchardet==3.0.4\ndnspython==1.15.0\necdsa==0.13\nidna==2.6\njsonrpclib-pelix==0.3.1\npbkdf2==1.3\nprotobuf==3.5.0.post1\npyaes==1.6.1\nPySocks==1.6.7\nqrcode==5.3\nrequests==2.18.4\nsix==1.11.0\nurllib3==1.22\ncython\npyqt5\ntrezor\nkeepkey\nconfigparser\nbtchip-python==0.1.23\npillow\npyblake2\n"
  },
  {
    "path": "requirements_travis.txt",
    "content": "tox\npython-coveralls\ntox-travis\n"
  },
  {
    "path": "run-docker.sh",
    "content": "#!/bin/bash\nXSOCK=/tmp/.X11-unix\nXAUTH=/tmp/.docker.xauth\nDATADIR=.electrum-btcp\nxauth nlist :0 | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -\ndocker run -ti -v $HOME/$DATADIR:/root/$DATADIR -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e XAUTHORITY=$XAUTH electrum-btcp:latest\n"
  },
  {
    "path": "scripts/bip70",
    "content": "#!/usr/bin/env python3\n# create a BIP70 payment request signed with a certificate\n\nimport tlslite\n\nfrom electrum.transaction import Transaction\nfrom electrum import paymentrequest\nfrom electrum import paymentrequest_pb2 as pb2\n\nchain_file = 'mychain.pem'\ncert_file = 'mycert.pem'\namount = 1000000\naddress = \"18U5kpCAU4s8weFF8Ps5n8HAfpdUjDVF64\"\nmemo = \"blah\"\nout_file = \"payreq\"\n\n\nwith open(chain_file, 'r') as f:\n    chain = tlslite.X509CertChain()\n    chain.parsePemList(f.read())\n\ncertificates = pb2.X509Certificates()\ncertificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List))\n\nwith open(cert_file, 'r') as f:\n    rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read())\n\nscript = Transaction.pay_script('address', address).decode('hex')\n\npr_string = paymentrequest.make_payment_request(amount, script, memo, rsakey)\n\nwith open(out_file,'wb') as f:\n    f.write(pr_string)\n\nprint(\"Payment request was written to file '%s'\"%out_file)\n"
  },
  {
    "path": "scripts/block_headers",
    "content": "#!/usr/bin/env python\n\n# A simple script that connects to a server and displays block headers\n\nimport sys\nimport time\nfrom electrum import SimpleConfig, Network\nfrom electrum.util import print_msg, json_encode\n\n# start network\nc = SimpleConfig()\nnetwork = Network(c)\nnetwork.start()\n\n# wait until connected\nwhile network.is_connecting():\n    time.sleep(0.1)\n\nif not network.is_connected():\n    print_msg(\"daemon is not connected\")\n    sys.exit(1)\n\n# 2. send the subscription\ncallback = lambda response: print_msg(json_encode(response.get('result')))\nnetwork.send([('blockchain.headers.subscribe',[])], callback)\n\n# 3. wait for results\nwhile network.is_connected():\n    time.sleep(1)\n"
  },
  {
    "path": "scripts/estimate_fee",
    "content": "#!/usr/bin/env python\nimport util, json\npeers = util.get_peers()\nresults = util.send_request(peers, 'blockchain.estimatefee', [2])\nprint(json.dumps(results, indent=4))\n"
  },
  {
    "path": "scripts/get_history",
    "content": "#!/usr/bin/env python3\n\nimport sys\nfrom electrum import Network\nfrom electrum.util import json_encode, print_msg\nfrom electrum import bitcoin\n\ntry:\n    addr = sys.argv[1]\nexcept Exception:\n    print(\"usage: get_history <bitcoin_address>\")\n    sys.exit(1)\n\nn = Network()\nn.start()\n_hash = bitcoin.address_to_scripthash(addr)\nh = n.synchronous_get(('blockchain.scripthash.get_history',[_hash]))\nprint_msg(json_encode(h))\n"
  },
  {
    "path": "scripts/peers",
    "content": "#!/usr/bin/env python3\n\nimport util\n\nfrom electrum.network import filter_protocol\nfrom electrum.blockchain import hash_header\n\npeers = util.get_peers()\npeers = filter_protocol(peers, 's')\n\nresults = util.send_request(peers, 'blockchain.headers.subscribe', [])\n\nfor n,v in sorted(results.items(), key=lambda x:x[1].get('block_height')):\n    print(\"%60s\"%n, v.get('block_height'), hash_header(v))\n"
  },
  {
    "path": "scripts/servers",
    "content": "#!/usr/bin/env python3\n\nfrom electrum import set_verbosity\nfrom electrum.network import filter_version\nimport util, json\nset_verbosity(False)\n\nservers = filter_version(util.get_peers())\nprint(json.dumps(servers, sort_keys = True, indent = 4))\n"
  },
  {
    "path": "scripts/txradar",
    "content": "#!/usr/bin/env python3\nimport util, sys\ntry:\n    tx = sys.argv[1]\nexcept:\n    print(\"usage: txradar txid\")\n    sys.exit(1)\n\npeers = util.get_peers()\nresults = util.send_request(peers, 'blockchain.transaction.get', [tx])\n\nr1 = []\nr2 = []\n\nfor k, v in results.items():\n    (r1 if v else r2).append(k)\n\nprint(\"Received %d answers\"%len(results))\nprint(\"Propagation rate: %.1f percent\" % (len(r1) *100./(len(r1)+ len(r2))))\n"
  },
  {
    "path": "scripts/util.py",
    "content": "import select, time, queue\n# import electrum\nfrom electrum import Connection, Interface, SimpleConfig\n\nfrom electrum.network import parse_servers\nfrom collections import defaultdict\n\n# electrum.util.set_verbosity(1)\ndef get_interfaces(servers, timeout=10):\n    '''Returns a map of servers to connected interfaces.  If any\n    connections fail or timeout, they will be missing from the map.\n    '''\n    socket_queue = queue.Queue()\n    config = SimpleConfig()\n    connecting = {}\n    for server in servers:\n        if server not in connecting:\n            connecting[server] = Connection(server, socket_queue, config.path)\n    interfaces = {}\n    timeout = time.time() + timeout\n    count = 0\n    while time.time() < timeout and count < len(servers):\n        try:\n            server, socket = socket_queue.get(True, 0.3)\n        except queue.Empty:\n            continue\n        if socket:\n            interfaces[server] = Interface(server, socket)\n        count += 1\n    return interfaces\n\ndef wait_on_interfaces(interfaces, timeout=10):\n    '''Return a map of servers to a list of (request, response) tuples.\n    Waits timeout seconds, or until each interface has a response'''\n    result = defaultdict(list)\n    timeout = time.time() + timeout\n    while len(result) < len(interfaces) and time.time() < timeout:\n        rin = [i for i in interfaces.values()]\n        win = [i for i in interfaces.values() if i.unsent_requests]\n        rout, wout, xout = select.select(rin, win, [], 1)\n        for interface in wout:\n            interface.send_requests()\n        for interface in rout:\n            responses = interface.get_responses()\n            if responses:\n                result[interface.server].extend(responses)\n    return result\n\ndef get_peers():\n    config = SimpleConfig()\n    peers = {}\n    # 1. get connected interfaces\n    server = config.get('server')\n    interfaces = get_interfaces([server])\n    if not interfaces:\n        print(\"No connection to\", server)\n        return []\n    # 2. get list of peers\n    interface = interfaces[server]\n    interface.queue_request('server.peers.subscribe', [], 0)\n    responses = wait_on_interfaces(interfaces).get(server)\n    if responses:\n        response = responses[0][1]  # One response, (req, response) tuple\n        peers = parse_servers(response.get('result'))\n    return peers\n\n\ndef send_request(peers, method, params):\n    print(\"Contacting %d servers\"%len(peers))\n    interfaces = get_interfaces(peers)\n    print(\"%d servers could be reached\" % len(interfaces))\n    for peer in peers:\n        if not peer in interfaces:\n            print(\"Connection failed:\", peer)\n    for msg_id, i in enumerate(interfaces.values()):\n        i.queue_request(method, params, msg_id)\n    responses = wait_on_interfaces(interfaces)\n    for peer in interfaces:\n        if not peer in responses:\n            print(peer, \"did not answer\")\n    results = dict(zip(responses.keys(), [t[0][1].get('result') for t in responses.values()]))\n    print(\"%d answers\"%len(results))\n    return results\n"
  },
  {
    "path": "scripts/watch_address",
    "content": "#!/usr/bin/env python3\n\nimport sys\nimport time\nfrom electrum import SimpleConfig, Network\nfrom electrum.util import print_msg, json_encode\n\ntry:\n    addr = sys.argv[1]\nexcept Exception:\n    print(\"usage: watch_address <bitcoin_address>\")\n    sys.exit(1)\n\n# start network\nc = SimpleConfig()\nnetwork = Network(c)\nnetwork.start()\n\n# wait until connected\nwhile network.is_connecting():\n    time.sleep(0.1)\n\nif not network.is_connected():\n    print_msg(\"daemon is not connected\")\n    sys.exit(1)\n\n# 2. send the subscription\ncallback = lambda response: print_msg(json_encode(response.get('result')))\nnetwork.send([('blockchain.address.subscribe',[addr])], callback)\n\n# 3. wait for results\nwhile network.is_connected():\n    time.sleep(1)\n"
  },
  {
    "path": "setup-mac.sh",
    "content": "#!/bin/bash\n\n# MacOS build instructions\n\n/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\n\n# Optionally (this is bad practice but works if you're stuck)\nsudo chown -R \"$USER\":admin /usr/local\nsudo chown -R \"$USER\":admin /Library/Caches/Homebrew\n\n# Python setuptools\ncurl https://bootstrap.pypa.io/ez_setup.py -o - | python3\n\n# Setup\npython3 setup.py install\n"
  },
  {
    "path": "setup-release.py",
    "content": "\"\"\"\npy2app build script for Electrum Bitcoin Private\n\nUsage (Mac OS X):\n     python setup.py py2app\n\"\"\"\n\nfrom setuptools import setup\nfrom plistlib import Plist\nimport requests\nimport os\nimport shutil\n\nfrom lib.version import ELECTRUM_VERSION as version\n\nCERT_PATH = requests.certs.where()\n\nname = \"Electrum BTCP\"\nmainscript = 'electrum-btcp'\n\nplist = Plist.fromFile('Info.plist')\nplist.update(dict(CFBundleIconFile='icons/electrum.icns'))\n\n\nos.environ[\"REQUESTS_CA_BUNDLE\"] = \"cacert.pem\"\nshutil.copy(mainscript, mainscript + '.py')\nmainscript += '.py'\nextra_options = dict(\n    setup_requires=['py2app'],\n    app=[mainscript],\n    packages=[\n        'electrum-btcp',\n        'electrum-btcp_gui',\n        'electrum-btcp_gui.qt',\n        'electrum-btcp_plugins',\n        'electrum-btcp_plugins.audio_modem',\n        'electrum-btcp_plugins.cosigner_pool',\n        'electrum-btcp_plugins.email_requests',\n        'electrum-btcp_plugins.greenaddress_instant',\n        'electrum-btcp_plugins.hw_wallet',\n        'electrum-btcp_plugins.keepkey',\n        'electrum-btcp_plugins.labels',\n        'electrum-btcp_plugins.ledger',\n        'electrum-btcp_plugins.trezor',\n        'electrum-btcp_plugins.digitalbitbox',\n        'electrum-btcp_plugins.trustedcoin',\n        'electrum-btcp_plugins.virtualkeyboard',\n\n    ],\n    package_dir={\n        'electrum-btcp': 'lib',\n        'electrum-btcp_gui': 'gui',\n        'electrum-btcp_plugins': 'plugins'\n    },\n    data_files=[CERT_PATH],\n    options=dict(py2app=dict(argv_emulation=False,\n                             includes=['sip'],\n                             packages=['lib', 'gui', 'plugins'],\n                             iconfile='icons/electrum.icns',\n                             plist=plist,\n                             resources=[\"icons\"])),\n)\n\nsetup(\n    name=name,\n    version=version,\n    **extra_options\n)\n\n# Remove the copied py file\nos.remove(mainscript)\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python3\n\n# python setup.py sdist --format=zip,gztar\n\nfrom setuptools import setup\nimport os\nimport sys\nimport platform\nimport imp\nimport argparse\n\nversion = imp.load_source('version', 'lib/version.py')\n\n\ndef readhere(path):\n    here = os.path.abspath(os.path.dirname(__file__))\n    with open(os.path.join(here, path), 'r') as fd:\n        return fd.read()\n\n\ndef readreqs(path):\n    return [req for req in\n            [line.strip() for line in readhere(path).split('\\n')]\n            if req and not req.startswith(('#', '-r'))]\n\n\ninstall_requires = readreqs('requirements.txt')\ntests_requires = install_requires + readreqs('requirements_travis.txt')\n\nif sys.version_info[:3] < (3, 4, 0):\n    sys.exit(\"Error: Electrum requires Python version >= 3.4.0...\")\n\ndata_files = []\n\nif platform.system() in ['Linux', 'FreeBSD', 'DragonFly']:\n    parser = argparse.ArgumentParser()\n    parser.add_argument('--root=', dest='root_path', metavar='dir', default='/')\n    opts, _ = parser.parse_known_args(sys.argv[1:])\n    usr_share = os.path.join(sys.prefix, \"share\")\n    if not os.access(opts.root_path + usr_share, os.W_OK) and \\\n       not os.access(opts.root_path, os.W_OK):\n        if 'XDG_DATA_HOME' in os.environ.keys():\n            usr_share = os.environ['XDG_DATA_HOME']\n        else:\n            usr_share = os.path.expanduser('~/.local/share')\n    data_files += [\n        (os.path.join(usr_share, 'applications/'), ['electrum.desktop']),\n        (os.path.join(usr_share, 'pixmaps/'), ['icons/electrum.png'])\n    ]\n\nsetup(\n    name=\"Electrum-BTCP\",\n    version=version.ELECTRUM_VERSION,\n    install_requires=install_requires,\n    tests_require=tests_requires,\n    packages=[\n        'electrum',\n        'electrum_gui',\n        'electrum_gui.qt',\n        'electrum_plugins',\n        'electrum_plugins.audio_modem',\n        'electrum_plugins.cosigner_pool',\n        'electrum_plugins.email_requests',\n        'electrum_plugins.greenaddress_instant',\n        'electrum_plugins.hw_wallet',\n        'electrum_plugins.keepkey',\n        'electrum_plugins.labels',\n        'electrum_plugins.ledger',\n        'electrum_plugins.trezor',\n        'electrum_plugins.digitalbitbox',\n        'electrum_plugins.trustedcoin',\n        'electrum_plugins.virtualkeyboard',\n    ],\n    package_dir={\n        'electrum': 'lib',\n        'electrum_gui': 'gui',\n        'electrum_plugins': 'plugins',\n    },\n    package_data={\n        'electrum': [\n            'servers.json',\n            'servers_testnet.json',\n            'currencies.json',\n            'checkpoints.json',\n            'checkpoints_testnet.json',\n            'www/index.html',\n            'wordlist/*.txt',\n            'locale/*/LC_MESSAGES/electrum.mo',\n        ]\n    },\n    scripts=['electrum-btcp'],\n    data_files=data_files,\n    description=\"Lightweight Bitcoin Private Wallet\",\n    author=\"BTCP Community\",\n    author_email=\"csulmone@gmail.com\",\n    license=\"MIT Licence\",\n    url=\"https://btcprivate.org\",\n    long_description=\"\"\"Lightweight Bitcoin Private Wallet\"\"\"\n)\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: electrum\nversion: master\nsummary: Bitcoin thin client\ndescription: |\n  Lightweight Bitcoin client\n\ngrade: devel # must be 'stable' to release into candidate/stable channels\nconfinement: strict\n\napps:\n  electrum:\n    command: desktop-launch electrum\n    plugs: [network, network-bind, x11, unity7]\n\nparts:\n  electrum:\n    source: .\n    plugin: python\n    python-version: python3\n    stage-packages: [python3-pyqt5]\n    build-packages: [pyqt5-dev-tools]\n    install: pyrcc5 icons.qrc -o $SNAPCRAFT_PART_INSTALL/lib/python3.5/site-packages/electrum_gui/qt/icons_rc.py\n    after: [desktop-qt5]\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py35, py36\n\n[testenv]\ndeps=\n\tpytest\n\tcoverage\ncommands=\n\tcoverage run --source=lib -m py.test -v\n\tcoverage report\n"
  }
]